mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-07-26 22:42:17 +00:00
fix(progress): isolate continue watching sources
This commit is contained in:
parent
e930699ae9
commit
e408a2f48c
6 changed files with 79 additions and 66 deletions
|
|
@ -139,7 +139,6 @@ interface TrackingProgressProvider {
|
|||
fun applyOptimisticRemoval(entries: Collection<WatchProgressEntry>) = Unit
|
||||
fun applyOptimisticProgress(entry: WatchProgressEntry) = Unit
|
||||
fun normalizeParentContentId(parentContentId: String, videoId: String?): String = parentContentId
|
||||
fun shouldRetainLocalEntry(entry: WatchProgressEntry): Boolean = true
|
||||
suspend fun refreshEpisodeProgress(contentId: String, forceRefresh: Boolean) = Unit
|
||||
fun isHiddenFromProgress(contentId: String): Boolean = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,9 +131,6 @@ object TraktTrackingProgressProvider : TrackingProgressProvider {
|
|||
override fun normalizeParentContentId(parentContentId: String, videoId: String?): String =
|
||||
resolveEffectiveContentId(parentContentId, videoId)
|
||||
|
||||
override fun shouldRetainLocalEntry(entry: WatchProgressEntry): Boolean =
|
||||
!isTraktCompatibleId(entry.parentMetaId)
|
||||
|
||||
override suspend fun refreshEpisodeProgress(contentId: String, forceRefresh: Boolean) =
|
||||
TraktProgressRepository.refreshEpisodeProgress(contentId, forceRefresh)
|
||||
|
||||
|
|
|
|||
|
|
@ -68,34 +68,6 @@ private data class MetadataProviderReadiness(
|
|||
get() = providers.isNotEmpty()
|
||||
}
|
||||
|
||||
internal fun mergeTrackerProgressEntries(
|
||||
remoteEntries: Collection<WatchProgressEntry>,
|
||||
localEntries: Collection<WatchProgressEntry>,
|
||||
): List<WatchProgressEntry> {
|
||||
val newestByMedia = linkedMapOf<String, WatchProgressEntry>()
|
||||
remoteEntries.forEach { entry ->
|
||||
newestByMedia[entry.trackerMediaIdentity()] = entry
|
||||
}
|
||||
localEntries.forEach { entry ->
|
||||
val key = entry.trackerMediaIdentity()
|
||||
val existing = newestByMedia[key]
|
||||
if (existing == null || entry.lastUpdatedEpochMs > existing.lastUpdatedEpochMs) {
|
||||
newestByMedia[key] = entry
|
||||
}
|
||||
}
|
||||
return newestByMedia.values.toList()
|
||||
}
|
||||
|
||||
private fun WatchProgressEntry.trackerMediaIdentity(): String = buildString {
|
||||
append(parentMetaType.trim().lowercase())
|
||||
append(':')
|
||||
append(parentMetaId.trim().lowercase())
|
||||
append(':')
|
||||
append(seasonNumber ?: -1)
|
||||
append(':')
|
||||
append(episodeNumber ?: -1)
|
||||
}
|
||||
|
||||
internal class MetadataResolutionRetryCoordinator {
|
||||
private val lock = SynchronizedObject()
|
||||
private var generation = 0L
|
||||
|
|
@ -1489,10 +1461,14 @@ object WatchProgressRepository {
|
|||
}
|
||||
|
||||
private fun currentEntries(): List<WatchProgressEntry> {
|
||||
val provider = activeProgressProvider() ?: return localEntriesSnapshot()
|
||||
return mergeTrackerProgressEntries(
|
||||
remoteEntries = provider.snapshot().entries,
|
||||
localEntries = localEntriesSnapshot().filter(provider::shouldRetainLocalEntry),
|
||||
val providerEntries = activeProgressProvider()
|
||||
?.snapshot()
|
||||
?.entries
|
||||
.orEmpty()
|
||||
return projectWatchProgressSourceEntries(
|
||||
source = activeSource,
|
||||
nuvioEntries = localEntriesSnapshot(),
|
||||
providerEntries = providerEntries,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.nuvio.app.features.watchprogress
|
||||
|
||||
import com.nuvio.app.features.tracking.WatchProgressSource
|
||||
|
||||
internal fun projectWatchProgressSourceEntries(
|
||||
source: WatchProgressSource,
|
||||
nuvioEntries: Collection<WatchProgressEntry>,
|
||||
providerEntries: Collection<WatchProgressEntry>,
|
||||
): List<WatchProgressEntry> = if (source.providerId == null) {
|
||||
nuvioEntries.toList()
|
||||
} else {
|
||||
providerEntries.toList()
|
||||
}
|
||||
|
|
@ -9,36 +9,6 @@ import kotlin.test.assertTrue
|
|||
|
||||
class WatchProgressIdentityTest {
|
||||
|
||||
@Test
|
||||
fun `tracker progress merge keeps one entry per media and favors fresher local playback`() {
|
||||
val remote = entry(
|
||||
progressKey = "simkl-playback:42",
|
||||
lastUpdatedEpochMs = 100L,
|
||||
lastPositionMs = 400L,
|
||||
)
|
||||
val staleLocal = entry(lastUpdatedEpochMs = 90L, lastPositionMs = 300L)
|
||||
val otherLocal = entry(
|
||||
parentMetaId = "other",
|
||||
videoId = "other:1:2",
|
||||
lastUpdatedEpochMs = 80L,
|
||||
)
|
||||
|
||||
val firstMerge = mergeTrackerProgressEntries(
|
||||
remoteEntries = listOf(remote),
|
||||
localEntries = listOf(staleLocal, otherLocal),
|
||||
)
|
||||
|
||||
assertEquals(2, firstMerge.size)
|
||||
assertEquals("simkl-playback:42", firstMerge.single { it.parentMetaId == "show" }.progressKey)
|
||||
|
||||
val freshLocal = staleLocal.copy(lastUpdatedEpochMs = 110L, lastPositionMs = 500L)
|
||||
val secondMerge = mergeTrackerProgressEntries(
|
||||
remoteEntries = listOf(remote),
|
||||
localEntries = listOf(freshLocal),
|
||||
)
|
||||
assertEquals(500L, secondMerge.single().lastPositionMs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `provider change during metadata batch schedules one follow up`() {
|
||||
val coordinator = MetadataResolutionRetryCoordinator()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
package com.nuvio.app.features.watchprogress
|
||||
|
||||
import com.nuvio.app.features.tracking.WatchProgressSource
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class WatchProgressSourceProjectionTest {
|
||||
@Test
|
||||
fun `remote source excludes every Nuvio progress entry`() {
|
||||
val nuvioEntries = listOf(
|
||||
entry(parentMetaId = "shared", updatedAt = 200L),
|
||||
entry(parentMetaId = "nuvio-only", updatedAt = 300L),
|
||||
)
|
||||
val providerEntries = listOf(
|
||||
entry(parentMetaId = "shared", updatedAt = 100L),
|
||||
)
|
||||
|
||||
listOf(WatchProgressSource.TRAKT, WatchProgressSource.SIMKL).forEach { source ->
|
||||
val projected = projectWatchProgressSourceEntries(
|
||||
source = source,
|
||||
nuvioEntries = nuvioEntries,
|
||||
providerEntries = providerEntries,
|
||||
)
|
||||
|
||||
assertEquals(providerEntries, projected)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Nuvio source excludes every provider progress entry`() {
|
||||
val nuvioEntries = listOf(entry(parentMetaId = "nuvio"))
|
||||
val providerEntries = listOf(entry(parentMetaId = "provider"))
|
||||
|
||||
val projected = projectWatchProgressSourceEntries(
|
||||
source = WatchProgressSource.NUVIO_SYNC,
|
||||
nuvioEntries = nuvioEntries,
|
||||
providerEntries = providerEntries,
|
||||
)
|
||||
|
||||
assertEquals(nuvioEntries, projected)
|
||||
}
|
||||
|
||||
private fun entry(
|
||||
parentMetaId: String,
|
||||
updatedAt: Long = 1L,
|
||||
): WatchProgressEntry = WatchProgressEntry(
|
||||
contentType = "series",
|
||||
parentMetaId = parentMetaId,
|
||||
parentMetaType = "series",
|
||||
videoId = "$parentMetaId:1:1",
|
||||
title = parentMetaId,
|
||||
seasonNumber = 1,
|
||||
episodeNumber = 1,
|
||||
lastPositionMs = 10L,
|
||||
durationMs = 100L,
|
||||
lastUpdatedEpochMs = updatedAt,
|
||||
)
|
||||
}
|
||||
Loading…
Reference in a new issue