diff --git a/buildSrc/src/main/kotlin/ProjectConfig.kt b/buildSrc/src/main/kotlin/ProjectConfig.kt index e9a69fa95..0cd1236e6 100644 --- a/buildSrc/src/main/kotlin/ProjectConfig.kt +++ b/buildSrc/src/main/kotlin/ProjectConfig.kt @@ -9,6 +9,21 @@ const val STRAW_SDK_TARGET = 35 // Sulkta fork — Straw // +// vc=83 / 0.1.0-CQ — fix: swapping videos from the minibar kept playing +// the old one: +// * With a video minimized to the bottom minibar, picking a different +// video updated the title, details and related list but the OLD video +// kept playing. The inline player's resolve→play effect read the +// shared (activity-scoped) view-model's `resolved` stream without +// checking it actually belonged to the newly-opened video. For one +// frame after the swap the view-model still holds the previous video's +// resolved URLs, so playback was claimed under the NEW url while +// streaming the OLD media — and the correct re-fire was then swallowed +// by the "already playing this url" short-circuit, so the new video +// never started. Restored the loadedUrl fence (the same guard +// VideoDetailBody and every ViewModel already use) that the vc=75 +// expandable-player rearchitect had dropped from the inline wiring. +// // vc=82 / 0.1.0-CP — subscription-feed enrichment goes lightweight: // * Filling in a feed row's view count + duration used to run the FULL // stream extraction per item (the same path opening a video does): @@ -149,6 +164,6 @@ const val STRAW_SDK_TARGET = 35 // vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via // strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no // NewPipeExtractor in the runtime path. -const val STRAW_VERSION_CODE = 82 -const val STRAW_VERSION_NAME = "0.1.0-CP" +const val STRAW_VERSION_CODE = 83 +const val STRAW_VERSION_NAME = "0.1.0-CQ" const val STRAW_APPLICATION_ID = "com.sulkta.straw" diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/ExpandablePlayer.kt b/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/ExpandablePlayer.kt index a7552815e..339399dd4 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/ExpandablePlayer.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/ExpandablePlayer.kt @@ -447,6 +447,23 @@ private fun InlinePlayerSurface( LaunchedEffect(controller, resolved, streamUrl, retryVersion, started) { if (!started) return@LaunchedEffect val c = controller ?: return@LaunchedEffect + // Only start playback once the VM's resolved stream actually belongs + // to THIS video. The VM is activity-scoped, so the instant the user + // swaps to a new video (e.g. picks another from the browse screen + // while the current one sits in the minibar) `state` still holds the + // PREVIOUS video's resolved for one composition frame — until + // vm.load(streamUrl) nulls it. Without this fence we'd call + // setPlayingFrom(streamUrl=NEW, resolved=OLD): NowPlaying.claim wins + // under the NEW url, so the title/details/minibar all flip to NEW, + // but the controller keeps streaming OLD's media — and the later + // re-fire with NEW's resolved short-circuits on the "already playing + // this url" guard below, so NEW never actually starts. That's the + // "swap from the minibar keeps playing the old video" bug. + // VideoDetailUiState.loadedUrl exists for exactly this fence (it's + // used by VideoDetailBody + every ViewModel); the vc=75 expandable- + // player rearchitect dropped it when the inline-player resolve→play + // wiring moved out of the old VideoDetailScreen into here. + if (state.loadedUrl != streamUrl) return@LaunchedEffect val r = resolved ?: return@LaunchedEffect if (NowPlaying.current.value?.streamUrl == streamUrl) return@LaunchedEffect c.setPlayingFrom(