From c3b5908f31be98e75795532f9a02b7edcb50cb0c Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 15 Oct 2026 13:46:26 +0000 Subject: [PATCH] player: keep the thumbnail until the first frame, not just STATE_READY ExoPlayer reports STATE_READY a beat before it actually draws the first frame, so the buffering thumbnail was removed into a black gap right before playback ("thumbnail, then black, then playing"). Hold the thumbnail overlay until onRenderedFirstFrame fires, so it hands straight over to the picture. Co-Authored-By: Claude Opus 4.8 --- .../java/net/jeena/jtubetv/ui/screens/PlayerScreen.kt | 10 +++++++++- 1 file changed, 9 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 95bcf0a..d11778a 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 @@ -323,6 +323,10 @@ private fun PlayerSurface( // 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) } + // STATE_READY can fire before the first frame is actually drawn, leaving a black + // gap between the thumbnail and the picture. Keep the thumbnail until the first + // frame really renders (user report: "thumbnail, then black, then playing"). + var firstFrame by remember { mutableStateOf(false) } LaunchedEffect(bufferingPending) { kotlinx.coroutines.delay(200) buffering = bufferingPending @@ -436,6 +440,10 @@ private fun PlayerSurface( override fun onPlaybackStateChanged(playbackState: Int) { bufferingPending = playbackState != Player.STATE_READY } + + override fun onRenderedFirstFrame() { + firstFrame = true + } }) onDispose { viewModel.persistExit(shortUuid, (player.currentPosition / 1000).toInt()) @@ -588,7 +596,7 @@ private fun PlayerSurface( // ready to render — metadata resolution alone (the Loading state) is not // enough (issue #8). Fullscreen loading shows the video's own thumbnail // dimmed under the circle loader (issue #19) instead of plain black. - if (buffering) { + if (buffering || !firstFrame) { Box( Modifier .fillMaxSize()