vc=57: hide stale inline player frame during video switch

Cobb-reported 2026-05-26: tapping a related/search/subs video while
another is playing rendered the NEW detail page (title, description)
with the OLD video's last frame visible in the inline player slot.

Root cause — there's a window between streamInfo resolving for the
new URL and setPlayingFrom landing on the controller. PlayerView
bound to the controller renders the previous video's surface during
that window because the controller's MediaItem hasn't swapped yet.

Fix — observe NowPlaying.current and add a branch in InlinePlayer's
state-when that renders thumbnail + spinner when the controller is
still on a different streamUrl. Branch sits above the PlayerView
else-arm so the stale surface never gets attached. Flips to
PlayerView the moment NowPlaying.claim() lands the new URL.
This commit is contained in:
Kayos 2026-05-26 11:16:00 -07:00
parent 50f4ce0a6c
commit 8dec2f2621
2 changed files with 26 additions and 2 deletions

View file

@ -55,6 +55,6 @@ const val NEWPIPE_APPLICATION_ID_NEW = "net.newpipe.app"
// vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via // vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via
// strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no // strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no
// NewPipeExtractor in the runtime path. // NewPipeExtractor in the runtime path.
const val STRAW_VERSION_CODE = 56 const val STRAW_VERSION_CODE = 57
const val STRAW_VERSION_NAME = "0.1.0-BP" const val STRAW_VERSION_NAME = "0.1.0-BQ"
const val STRAW_APPLICATION_ID = "com.sulkta.straw" const val STRAW_APPLICATION_ID = "com.sulkta.straw"

View file

@ -776,6 +776,14 @@ private fun InlinePlayer(
onDispose { c?.removeListener(listener) } onDispose { c?.removeListener(listener) }
} }
// Track whether the shared controller has actually swapped over to
// THIS video's stream. Until it does (the brief window between
// streamInfo resolving and setPlayingFrom + setMediaItem landing),
// binding PlayerView to the controller would render the PREVIOUS
// video's frame under the new detail page — exactly the "new page,
// old video" bug.
val nowPlaying by NowPlaying.current.collectAsStateWithLifecycle()
val controllerOnThisVideo = nowPlaying?.streamUrl == streamUrl
Box(modifier = modifier, contentAlignment = Alignment.Center) { Box(modifier = modifier, contentAlignment = Alignment.Center) {
when { when {
controller == null || state.loading -> CircularProgressIndicator(color = Color.White) controller == null || state.loading -> CircularProgressIndicator(color = Color.White)
@ -794,6 +802,22 @@ private fun InlinePlayer(
color = Color.White, color = Color.White,
modifier = Modifier.padding(16.dp), modifier = Modifier.padding(16.dp),
) )
// Stream resolved for THIS URL but the controller hasn't
// actually swapped media items yet — show the thumbnail
// with a spinner. Without this, the PlayerView below would
// bind to the controller and render the OUTGOING video's
// last frame while the new detail page chrome shows the
// new title/description. Bug reported 2026-05-26.
!controllerOnThisVideo -> {
if (!thumbnail.isNullOrBlank()) {
AsyncImage(
model = thumbnail,
contentDescription = null,
modifier = Modifier.fillMaxSize(),
)
}
CircularProgressIndicator(color = Color.White)
}
else -> { else -> {
AndroidView( AndroidView(
factory = { ctx -> factory = { ctx ->