@@ -1125,16 +1125,28 @@ fun RootScreen(
11251125 }
11261126 }
11271127
1128+ fun syncPlaybackStateToQueueIndex(index: Int): Boolean {
1129+ val queue = activePlaybackQueue ?: return false
1130+ if (index !in queue.items.indices) return false
1131+ activePlaybackItems.getOrNull(index)?.let { activePlaybackItem = it }
1132+ activePlaybackTitle = queue.items[index].title
1133+ activePlaybackSubtitleState = null
1134+ resumePositionMs = null
1135+ return true
1136+ }
1137+
11281138 fun startPlayback(
11291139 item: ContentItem,
11301140 items: List<ContentItem>,
11311141 config: AuthConfig,
11321142 parentItem: ContentItem? = null,
11331143 playbackResumePositionMs: Long? = null
11341144 ) {
1135- val playableItems = items.filter(::isPlayableContent)
1145+ val playableItems = playableContentItemsForQueue(items, item)
1146+ val queue = buildPlaybackQueue(playableItems, item, config)
1147+ val startItem = playableItems.getOrNull(queue.startIndex) ?: item
11361148 activePlaybackItems = playableItems
1137- activePlaybackItem = item
1149+ activePlaybackItem = startItem
11381150 activePlaybackSeriesParent =
11391151 if (item.contentType == ContentType.SERIES) {
11401152 parentItem
@@ -1149,9 +1161,8 @@ fun RootScreen(
11491161 BufferProfile.VOD
11501162 }
11511163 playbackEngine.setBufferProfile(profile)
1152- val queue = buildPlaybackQueue(items, item, config)
11531164 activePlaybackQueue = queue
1154- activePlaybackTitle = queue.items.getOrNull(queue.startIndex)?.title ?: item .title
1165+ activePlaybackTitle = queue.items.getOrNull(queue.startIndex)?.title ?: startItem .title
11551166 }
11561167
11571168 val handlePlayItem: (ContentItem, List<ContentItem>) -> Unit = { item, items ->
@@ -1160,12 +1171,7 @@ fun RootScreen(
11601171 resumeFocusId = resolveResumeFocusTarget(item)
11611172 val shouldResolveSeriesQueue =
11621173 item.contentType == ContentType.SERIES &&
1163- !item.containerExtension.isNullOrBlank() &&
1164- (items.size <= 1 ||
1165- items.any {
1166- it.contentType != ContentType.SERIES ||
1167- it.containerExtension.isNullOrBlank()
1168- })
1174+ !item.containerExtension.isNullOrBlank()
11691175 if (shouldResolveSeriesQueue) {
11701176 coroutineScope.launch {
11711177 val resolved =
@@ -1274,12 +1280,7 @@ fun RootScreen(
12741280 resumeFocusId = resolveResumeFocusTarget(item)
12751281 val shouldResolveSeriesQueue =
12761282 item.contentType == ContentType.SERIES &&
1277- !item.containerExtension.isNullOrBlank() &&
1278- (items.size <= 1 ||
1279- items.any {
1280- it.contentType != ContentType.SERIES ||
1281- it.containerExtension.isNullOrBlank()
1282- })
1283+ !item.containerExtension.isNullOrBlank()
12831284 if (shouldResolveSeriesQueue) {
12841285 coroutineScope.launch {
12851286 val resolved =
@@ -1387,9 +1388,16 @@ fun RootScreen(
13871388 completionThresholdPercent = CONTINUE_WATCHING_MAX_PROGRESS_PERCENT
13881389 )
13891390 val subtitleState = activePlaybackSubtitleState
1391+ val hasFollowingSeriesEpisode =
1392+ item.contentType == ContentType.SERIES &&
1393+ activePlaybackQueue?.items
1394+ ?.drop(safePlayer.currentMediaItemIndex + 1)
1395+ ?.any { it.type == ContentType.SERIES } == true
13901396 coroutineScope.launch {
13911397 if (!shouldStore) {
1392- continueWatchingRepository.removeEntry(config, item)
1398+ if (!hasFollowingSeriesEpisode) {
1399+ continueWatchingRepository.removeEntry(config, item)
1400+ }
13931401 vodPlaybackStateRepository.removeEntry(config, queueMediaIdFor(item))
13941402 } else {
13951403 val parentItem =
@@ -1619,6 +1627,14 @@ fun RootScreen(
16191627 syncPlaybackStateFromPlayer(mediaItem)
16201628 }
16211629
1630+ override fun onPositionDiscontinuity(
1631+ oldPosition: Player.PositionInfo,
1632+ newPosition: Player.PositionInfo,
1633+ reason: Int
1634+ ) {
1635+ syncPlaybackStateFromPlayer(newPosition.mediaItem)
1636+ }
1637+
16221638 override fun onPlaybackStateChanged(playbackState: Int) {
16231639 syncPlaybackStateFromPlayer()
16241640 if (playbackState == Player.STATE_READY) {
@@ -2294,8 +2310,15 @@ fun RootScreen(
22942310 resumePositionMs = null
22952311 },
22962312 onPlayNextEpisode = {
2297- playbackEngine.player.seekToNextMediaItem()
2298- playbackEngine.player.playWhenReady = true
2313+ val player = playbackEngine.player
2314+ savePlaybackProgress()
2315+ val nextIndex = player.currentMediaItemIndex + 1
2316+ if (syncPlaybackStateToQueueIndex(nextIndex)) {
2317+ player.seekTo(nextIndex, 0L)
2318+ } else {
2319+ player.seekToNextMediaItem()
2320+ }
2321+ player.playWhenReady = true
22992322 },
23002323 onMatchFrameRateChange = { enabled ->
23012324 playbackEngine.applySettings(settings.copy(matchFrameRateEnabled = enabled))
@@ -2718,6 +2741,17 @@ private fun isPlayableContent(item: ContentItem): Boolean {
27182741 return item.contentType != ContentType.SERIES || !item.containerExtension.isNullOrBlank()
27192742}
27202743
2744+ private fun playableContentItemsForQueue(
2745+ items: List<ContentItem>,
2746+ current: ContentItem
2747+ ): List<ContentItem> {
2748+ val playableItems = items.filter(::isPlayableContent).toMutableList()
2749+ if (playableItems.none { isSameContentIdentity(it, current) }) {
2750+ playableItems.add(current)
2751+ }
2752+ return playableItems
2753+ }
2754+
27212755private fun isSameContentIdentity(first: ContentItem, second: ContentItem): Boolean {
27222756 if (first.contentType != second.contentType) return false
27232757 val firstStreamId = first.streamId?.takeUnless { it.isBlank() }
@@ -2739,10 +2773,7 @@ private fun buildPlaybackQueue(
27392773 current: ContentItem,
27402774 authConfig: AuthConfig
27412775): PlaybackQueue {
2742- val playableItems = items.filter(::isPlayableContent).toMutableList()
2743- if (playableItems.none { isSameContentIdentity(it, current) }) {
2744- playableItems.add(current)
2745- }
2776+ val playableItems = playableContentItemsForQueue(items, current)
27462777 val startIndex =
27472778 playableItems.indexOfFirst { isSameContentIdentity(it, current) }.let { index ->
27482779 if (index >= 0) index else 0
@@ -8746,10 +8777,13 @@ fun ContinueWatchingScreen(
87468777 }
87478778 val columns = rememberReflowColumns(baseColumns, navLayoutExpanded)
87488779 val resolvedParents = remember { androidx.compose.runtime.mutableStateMapOf<String, ContentItem>() }
8780+ val parentResolutionAttempted =
8781+ remember { androidx.compose.runtime.mutableStateMapOf<String, Boolean>() }
87498782 val posterFontScale = remember(columns) { 4f / columns.toFloat() }
87508783 val resolvedParentsSnapshot = resolvedParents.toMap()
8784+ val parentResolutionAttemptedSnapshot = parentResolutionAttempted.toMap()
87518785 val displayEntries =
8752- remember(continueWatchingItems, resolvedParentsSnapshot) {
8786+ remember(continueWatchingItems, resolvedParentsSnapshot, parentResolutionAttemptedSnapshot ) {
87538787 fun displayGroupingKey(entry: ContinueWatchingEntry): String {
87548788 val canonicalSeriesId =
87558789 entry.parentItem?.streamId?.takeUnless { it.isBlank() }
@@ -8786,6 +8820,16 @@ fun ContinueWatchingScreen(
87868820 }
87878821 .maxByOrNull { it.first }
87888822 ?.second
8823+ val waitingForParentResolution =
8824+ group.any { entry ->
8825+ entry.item.contentType == ContentType.SERIES &&
8826+ entry.parentItem == null &&
8827+ !resolvedParentsSnapshot.containsKey(entry.key) &&
8828+ parentResolutionAttemptedSnapshot[entry.key] != true
8829+ }
8830+ if (latestSeriesParent == null && waitingForParentResolution) {
8831+ return@mapNotNull null
8832+ }
87898833 val displayItem = latestSeriesParent ?: latest.item
87908834 val resumeLabel =
87918835 if (latest.item.contentType == ContentType.SERIES) {
@@ -8856,16 +8900,22 @@ fun ContinueWatchingScreen(
88568900 .filterNot { it in currentKeys }
88578901 .toList()
88588902 .forEach { resolvedParents.remove(it) }
8903+ parentResolutionAttempted.keys
8904+ .filterNot { it in currentKeys }
8905+ .toList()
8906+ .forEach { parentResolutionAttempted.remove(it) }
88598907 continueWatchingItems
88608908 .filter { entry ->
88618909 entry.item.contentType == ContentType.SERIES && entry.parentItem == null &&
8862- !resolvedParents.containsKey(entry.key)
8910+ !resolvedParents.containsKey(entry.key) &&
8911+ parentResolutionAttempted[entry.key] != true
88638912 }
88648913 .forEach { entry ->
88658914 val resolved = resolveSeriesParent(entry.item)
88668915 if (resolved != null) {
88678916 resolvedParents[entry.key] = resolved
88688917 }
8918+ parentResolutionAttempted[entry.key] = true
88698919 }
88708920 }
88718921
0 commit comments