From cb7eada91599208f8ce7a3d62a4cc50db96a6c0c Mon Sep 17 00:00:00 2001 From: Sulkta Date: Tue, 26 May 2026 11:16:00 -0700 Subject: [PATCH] vc=57: hide stale inline player frame during video switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tester-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. --- buildSrc/src/main/kotlin/ProjectConfig.kt | 4 ++-- .../straw/feature/detail/VideoDetailScreen.kt | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/ProjectConfig.kt b/buildSrc/src/main/kotlin/ProjectConfig.kt index 11aec6076..3756da281 100644 --- a/buildSrc/src/main/kotlin/ProjectConfig.kt +++ b/buildSrc/src/main/kotlin/ProjectConfig.kt @@ -55,6 +55,6 @@ const val NEWPIPE_APPLICATION_ID_NEW = "net.newpipe.app" // vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via // strawcore-core (Sulkta-OSS/strawcore) via the UniFFI wrapper; no // NewPipeExtractor in the runtime path. -const val STRAW_VERSION_CODE = 56 -const val STRAW_VERSION_NAME = "0.1.0-BP" +const val STRAW_VERSION_CODE = 57 +const val STRAW_VERSION_NAME = "0.1.0-BQ" const val STRAW_APPLICATION_ID = "com.sulkta.straw" diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/feature/detail/VideoDetailScreen.kt b/strawApp/src/main/kotlin/com/sulkta/straw/feature/detail/VideoDetailScreen.kt index ae6f442a0..d989d2676 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/feature/detail/VideoDetailScreen.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/feature/detail/VideoDetailScreen.kt @@ -776,6 +776,14 @@ private fun InlinePlayer( 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) { when { controller == null || state.loading -> CircularProgressIndicator(color = Color.White) @@ -794,6 +802,22 @@ private fun InlinePlayer( color = Color.White, 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 -> { AndroidView( factory = { ctx ->