From b2601bb0c0fabe95dcd4e2cf42921e8162a9eec9 Mon Sep 17 00:00:00 2001 From: Jeena Date: Wed, 14 Oct 2026 00:20:50 +0000 Subject: [PATCH] player: debounce the buffering indicator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ExoPlayer flaps READY↔BUFFERING at every buffer edge and seek; each flap tore the red spinner overlay out of composition and rebuilt it, visibly restarting the animation from zero and flickering the covering box — the "struggles and restarts" report. The displayed state now follows the raw player state debounced, last value wins after 200 ms, so quick flaps cancel out and the overlay never churns mid-playback. The long initial buffering still shows it immediately. Closes #17 --- .../net/jeena/jtubetv/ui/screens/PlayerScreen.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/net/jeena/jtubetv/ui/screens/PlayerScreen.kt b/app/src/main/java/net/jeena/jtubetv/ui/screens/PlayerScreen.kt index b77e291..a080bed 100644 --- a/app/src/main/java/net/jeena/jtubetv/ui/screens/PlayerScreen.kt +++ b/app/src/main/java/net/jeena/jtubetv/ui/screens/PlayerScreen.kt @@ -286,6 +286,17 @@ private fun PlayerSurface( // a re-buffer after seeking) — shows an indicator until media really plays // (issue #8: the Loading screen ended too early and the rest was black). var buffering by remember { mutableStateOf(true) } + // The DISPLAYED overlay follows the raw player state debounced (issue #17): + // ExoPlayer flaps READY↔BUFFERING at every buffer edge and seek — tearing + // the overlay out of composition on each flap restarted the red spinner + // from zero ("it struggles and restarts"). Last pending value wins after + // 200 ms: quick flaps cancel the pending change and the overlay stays + // composed, so the animation never restarts mid-playback. + var bufferingPending by remember { mutableStateOf(true) } + LaunchedEffect(bufferingPending) { + kotlinx.coroutines.delay(200) + buffering = bufferingPending + } // Key press that was consumed at ACTION_DOWN — its trailing ACTION_UP must not // re-reveal the native controller right after a play/pause toggle. var consumedDownKey by remember { mutableStateOf(0) } @@ -393,7 +404,7 @@ private fun PlayerSurface( } override fun onPlaybackStateChanged(playbackState: Int) { - buffering = playbackState != Player.STATE_READY + bufferingPending = playbackState != Player.STATE_READY } }) onDispose {