From b1d9e2dc93d3508d08ed678f1b0faef5bbdac888 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Mon, 6 Apr 2026 14:09:13 +0530 Subject: [PATCH] fix: improve resume position handling in player and trakt progress logic --- .../nuvio/app/features/player/PlayerScreen.kt | 19 +++++++------------ .../features/trakt/TraktProgressRepository.kt | 13 ++++--------- .../watchprogress/WatchProgressModels.kt | 12 ++++++++++++ 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt index 9f2074a2b..ce65f56b4 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreen.kt @@ -507,15 +507,8 @@ fun PlayerScreen( fun switchToSource(stream: StreamItem) { val url = stream.directPlaybackUrl ?: return if (url == activeSourceUrl) return + val currentPositionMs = playbackSnapshot.positionMs.coerceAtLeast(0L) flushWatchProgress() - val resumeVideoId = buildPlaybackVideoId( - parentMetaId = parentMetaId, - seasonNumber = activeSeasonNumber, - episodeNumber = activeEpisodeNumber, - fallbackVideoId = activeVideoId, - ) - val entry = WatchProgressRepository.progressForVideo(resumeVideoId) - val resumePositionMs = entry?.lastPositionMs?.takeIf { it > 0L } ?: 0L if (playerSettingsUiState.streamReuseLastLinkEnabled && activeVideoId != null) { val cacheKey = StreamLinkCacheRepository.contentKey( contentType ?: parentMetaType, @@ -538,7 +531,7 @@ fun PlayerScreen( activeStreamSubtitle = stream.streamSubtitle activeProviderName = stream.addonName activeProviderAddonId = stream.addonId - activeInitialPositionMs = resumePositionMs + activeInitialPositionMs = currentPositionMs activeInitialProgressFraction = null showSourcesPanel = false controlsVisible = true @@ -564,9 +557,11 @@ fun PlayerScreen( fallbackVideoId = epVideoId, ) val epEntry = WatchProgressRepository.progressForVideo(epResumeVideoId) - val epResumePositionMs = epEntry ?.takeIf { !it.isCompleted } - ?.lastPositionMs?.takeIf { it > 0L } ?: 0L + val epResumeFraction = epEntry?.progressPercent + ?.takeIf { it > 0f } + ?.let { (it / 100f).coerceIn(0f, 1f) } + val epResumePositionMs = epEntry?.lastPositionMs?.takeIf { it > 0L } ?: 0L if (playerSettingsUiState.streamReuseLastLinkEnabled) { val cacheKey = StreamLinkCacheRepository.contentKey( contentType ?: parentMetaType, @@ -595,7 +590,7 @@ fun PlayerScreen( activeEpisodeThumbnail = episode.thumbnail activeVideoId = episode.id activeInitialPositionMs = epResumePositionMs - activeInitialProgressFraction = null + activeInitialProgressFraction = epResumeFraction controlsVisible = true } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktProgressRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktProgressRepository.kt index 7f9ca0ec4..8c51e9065 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktProgressRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktProgressRepository.kt @@ -26,7 +26,6 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json private const val BASE_URL = "https://api.trakt.tv" -private const val PLAYBACK_SYNTHETIC_DURATION_MS = 100_000L private const val HISTORY_LIMIT = 250 private const val METADATA_FETCH_TIMEOUT_MS = 3_500L private const val METADATA_FETCH_CONCURRENCY = 5 @@ -356,8 +355,6 @@ object TraktProgressRepository { val progressPercent = normalizeTraktProgressPercent(item.progress) ?: return null if (progressPercent <= 0f) return null - val progressFraction = progressPercent / 100f - val positionMs = (PLAYBACK_SYNTHETIC_DURATION_MS * progressFraction).toLong() return WatchProgressEntry( contentType = "movie", @@ -365,8 +362,8 @@ object TraktProgressRepository { parentMetaType = "movie", videoId = parentMetaId, title = movie.title ?: parentMetaId, - lastPositionMs = positionMs, - durationMs = PLAYBACK_SYNTHETIC_DURATION_MS, + lastPositionMs = 0L, + durationMs = 0L, lastUpdatedEpochMs = rankedTimestamp(item.pausedAt, fallbackIndex), isCompleted = false, progressPercent = progressPercent, @@ -384,8 +381,6 @@ object TraktProgressRepository { val progressPercent = normalizeTraktProgressPercent(item.progress) ?: return null if (progressPercent <= 0f) return null - val progressFraction = progressPercent / 100f - val positionMs = (PLAYBACK_SYNTHETIC_DURATION_MS * progressFraction).toLong() return WatchProgressEntry( contentType = "series", @@ -401,8 +396,8 @@ object TraktProgressRepository { seasonNumber = season, episodeNumber = number, episodeTitle = episode.title, - lastPositionMs = positionMs, - durationMs = PLAYBACK_SYNTHETIC_DURATION_MS, + lastPositionMs = 0L, + durationMs = 0L, lastUpdatedEpochMs = rankedTimestamp(item.pausedAt, fallbackIndex), isCompleted = false, progressPercent = progressPercent, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressModels.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressModels.kt index 6371bdddf..34150cf5d 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressModels.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressModels.kt @@ -53,6 +53,18 @@ data class WatchProgressEntry( val isResumable: Boolean get() = !isCompleted + + fun resolveResumePosition(actualDurationMs: Long): Long { + if (actualDurationMs <= 0L) return lastPositionMs.coerceAtLeast(0L) + if (durationMs > 0L && lastPositionMs > 0L) { + return lastPositionMs.coerceIn(0L, actualDurationMs) + } + progressPercent?.let { percent -> + val fraction = (percent / 100f).coerceIn(0f, 1f) + return (actualDurationMs * fraction).toLong() + } + return lastPositionMs.coerceAtLeast(0L) + } } data class WatchProgressUiState(