fix: trakt local porgress pushing multiple watched markers

This commit is contained in:
tapframe 2026-05-26 23:08:39 +05:30
parent e0ca149b09
commit 9b5ea6e3b6
4 changed files with 81 additions and 8 deletions

View file

@ -269,6 +269,12 @@ fun PlayerScreen(
activeSeasonNumber,
activeEpisodeNumber,
) { mutableStateOf(false) }
var scrobbleStartRequestGeneration by remember(
activeSourceUrl,
activeVideoId,
activeSeasonNumber,
activeEpisodeNumber,
) { mutableStateOf(0L) }
var hasSentCompletionScrobbleForCurrentItem by remember(
activeVideoId,
activeSeasonNumber,
@ -422,6 +428,8 @@ fun PlayerScreen(
fun emitTraktScrobbleStart() {
if (hasRequestedScrobbleStartForCurrentItem) return
hasRequestedScrobbleStartForCurrentItem = true
val requestGeneration = scrobbleStartRequestGeneration + 1L
scrobbleStartRequestGeneration = requestGeneration
scope.launch {
val item = currentTraktScrobbleItem()
@ -429,6 +437,9 @@ fun PlayerScreen(
hasRequestedScrobbleStartForCurrentItem = false
return@launch
}
if (requestGeneration != scrobbleStartRequestGeneration || !hasRequestedScrobbleStartForCurrentItem) {
return@launch
}
currentTraktScrobbleItem = item
TraktScrobbleRepository.scrobbleStart(
item = item,
@ -452,6 +463,7 @@ fun PlayerScreen(
}
currentTraktScrobbleItem = null
hasRequestedScrobbleStartForCurrentItem = false
scrobbleStartRequestGeneration += 1L
}
fun emitStopScrobbleForCurrentProgress() {

View file

@ -28,6 +28,16 @@ private data class StoredWatchedPayload(
val lastSuccessfulPushEpochMs: Long = 0L,
)
internal enum class WatchedTraktHistorySync {
Mirror,
Skip,
}
internal fun shouldMirrorWatchedMarkToTraktHistory(
sync: WatchedTraktHistorySync,
isTraktAuthenticated: Boolean,
): Boolean = sync == WatchedTraktHistorySync.Mirror && isTraktAuthenticated
object WatchedRepository {
private const val watchedItemsPageSize = 900
@ -134,6 +144,17 @@ object WatchedRepository {
}
fun markWatched(items: Collection<WatchedItem>) {
markWatched(items = items, traktHistorySync = WatchedTraktHistorySync.Mirror)
}
internal fun markWatchedFromPlaybackCompletion(item: WatchedItem) {
markWatched(items = listOf(item), traktHistorySync = WatchedTraktHistorySync.Skip)
}
private fun markWatched(
items: Collection<WatchedItem>,
traktHistorySync: WatchedTraktHistorySync,
) {
ensureLoaded()
if (items.isEmpty()) return
val markedAt = WatchedClock.nowEpochMs()
@ -146,7 +167,7 @@ object WatchedRepository {
}
publish()
persist()
pushMarksToServer(timestampedItems)
pushMarksToServer(timestampedItems, traktHistorySync)
}
fun unmarkWatched(item: WatchedItem) {
@ -223,13 +244,22 @@ object WatchedRepository {
}
}
private fun pushMarksToServer(items: Collection<WatchedItem>) {
private fun pushMarksToServer(
items: Collection<WatchedItem>,
traktHistorySync: WatchedTraktHistorySync,
) {
syncScope.launch {
runCatching {
if (items.isEmpty()) return@runCatching
val profileId = ProfileRepository.activeProfileId
pushToActiveTargets(profileId = profileId, items = items)
recordSuccessfulPush(profileId = profileId, items = items)
val pushed = pushToActiveTargets(
profileId = profileId,
items = items,
traktHistorySync = traktHistorySync,
)
if (pushed) {
recordSuccessfulPush(profileId = profileId, items = items)
}
}.onFailure { e ->
log.e(e) { "Failed to push watched items" }
}
@ -296,16 +326,24 @@ object WatchedRepository {
private suspend fun pushToActiveTargets(
profileId: Int,
items: Collection<WatchedItem>,
) {
traktHistorySync: WatchedTraktHistorySync,
): Boolean {
val shouldMirrorToTrakt = shouldMirrorWatchedMarkToTraktHistory(
sync = traktHistorySync,
isTraktAuthenticated = TraktAuthRepository.isAuthenticated.value,
)
if (shouldUseTraktWatchedSync()) {
if (!shouldMirrorToTrakt) return false
TraktWatchedSyncAdapter.push(profileId = profileId, items = items)
return
return true
}
syncAdapter.push(profileId = profileId, items = items)
if (TraktAuthRepository.isAuthenticated.value) {
if (shouldMirrorToTrakt) {
TraktWatchedSyncAdapter.push(profileId = profileId, items = items)
}
return true
}
private suspend fun deleteFromActiveTargets(

View file

@ -129,7 +129,7 @@ object WatchingActions {
episode = entry.episodeNumber,
markedAtEpochMs = entry.lastUpdatedEpochMs,
)
WatchedRepository.markWatched(watchedItem)
WatchedRepository.markWatchedFromPlaybackCompletion(watchedItem)
if (!entry.isEpisode) return
actionScope.launch {

View file

@ -4,6 +4,7 @@ import com.nuvio.app.features.details.MetaDetails
import com.nuvio.app.features.details.MetaVideo
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class WatchedRepositoryTest {
@ -97,4 +98,26 @@ class WatchedRepositoryTest {
assertTrue(merged.isEmpty())
}
@Test
fun playbackCompletionWatchedMarks_doNotMirrorToTraktHistory() {
assertFalse(
shouldMirrorWatchedMarkToTraktHistory(
sync = WatchedTraktHistorySync.Skip,
isTraktAuthenticated = true,
),
)
assertTrue(
shouldMirrorWatchedMarkToTraktHistory(
sync = WatchedTraktHistorySync.Mirror,
isTraktAuthenticated = true,
),
)
assertFalse(
shouldMirrorWatchedMarkToTraktHistory(
sync = WatchedTraktHistorySync.Mirror,
isTraktAuthenticated = false,
),
)
}
}