From 0b9a9dab50181dd197127b2fae07e0ac99a5bd08 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Mon, 12 Oct 2026 18:36:33 +0200 Subject: [PATCH] fix(ios,android): offer the right episode as up next while the next one opens After "next episode" during the credits, the previous file keeps playing until the new stream is open. Its chapters and position were read against the already-advanced index, so the up-next card offered the episode after the next one for those seconds. The card, the skip offer and auto-skip now only act on readings of the open episode. --- .../java/io/narl/protonstream/playback/PlayerScreen.kt | 9 +++++++-- ios/ProtonStream/Player/PlayerHost.swift | 9 +++++++-- ios/ProtonStream/Player/PlayerView.swift | 3 ++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/java/io/narl/protonstream/playback/PlayerScreen.kt b/android/app/src/main/java/io/narl/protonstream/playback/PlayerScreen.kt index 960f159..2510a2b 100644 --- a/android/app/src/main/java/io/narl/protonstream/playback/PlayerScreen.kt +++ b/android/app/src/main/java/io/narl/protonstream/playback/PlayerScreen.kt @@ -333,7 +333,12 @@ fun PlayerScreen( val problem by nativeHost.problem.collectAsState() LaunchedEffect(problem) { problem?.let { playbackError = it } } - val offer = remember(chapters, key, (state.position * 4).roundToInt()) { + // Whether mpv's readings are this episode's. Not while the next one opens: + // the last one's credits play on until it is loaded, and read against the + // new index they offered the episode after the next as up next. + val playingThis = state.media == key + val offer = remember(chapters, key, playingThis, (state.position * 4).roundToInt()) { + if (!playingThis) return@remember null runCatching { skipOffer(chapters, state.position) }.getOrNull() } // Auto-skip is offered once per chapter: seeking back into an opening on @@ -475,7 +480,7 @@ fun PlayerScreen( UpNextCard( next = next, - visible = autoplay && !state.paused && + visible = autoplay && !state.paused && playingThis && chapters.creditsStart?.let { state.position >= it } == true, onPlayNow = { advance() }, modifier = Modifier diff --git a/ios/ProtonStream/Player/PlayerHost.swift b/ios/ProtonStream/Player/PlayerHost.swift index 2990b9e..9934d77 100644 --- a/ios/ProtonStream/Player/PlayerHost.swift +++ b/ios/ProtonStream/Player/PlayerHost.swift @@ -111,6 +111,11 @@ final class PlayerHost { var episode: EpisodeRecord? { episodes.indices.contains(index) ? episodes[index] : nil } var key: String? { episode.map(mediaKey) } var next: EpisodeRecord? { episodes.indices.contains(index + 1) ? episodes[index + 1] : nil } + /// Whether mpv's readings — position, chapters — are the open episode's. + /// Not while the next one opens: the last one's credits go on playing + /// until it is loaded, and read against the new index they offered the + /// episode *after* the next as up next. + var playingOpenEpisode: Bool { player?.media != nil && player?.media == key } var isOpen: Bool { title != nil } // MARK: - Opening @@ -329,7 +334,7 @@ final class PlayerHost { } private func tick() { - guard let player else { return } + guard let player, playingOpenEpisode else { return } replanChapters(player) // Auto-skip, once per chapter. if autoSkip, !player.paused, let offer = skipOffer(plan: chapters, position: player.position), @@ -352,7 +357,7 @@ final class PlayerHost { /// The skip button's offer at the current position, if any. var offer: SkipOffer? { - guard let player else { return nil } + guard let player, playingOpenEpisode else { return nil } return skipOffer(plan: chapters, position: player.position) } diff --git a/ios/ProtonStream/Player/PlayerView.swift b/ios/ProtonStream/Player/PlayerView.swift index 101083e..b24727d 100644 --- a/ios/ProtonStream/Player/PlayerView.swift +++ b/ios/ProtonStream/Player/PlayerView.swift @@ -63,7 +63,8 @@ struct PlayerView: View { } UpNextCard( next: host.next, - visible: host.autoplay && !player.paused && host.chapters.creditsStart.map { player.position >= $0 } == true, + visible: host.autoplay && !player.paused && host.playingOpenEpisode + && host.chapters.creditsStart.map { player.position >= $0 } == true, onPlayNow: host.advance ) }