diff --git a/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeScreen.kt b/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeScreen.kt index 019e2ff..5f8726b 100644 --- a/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeScreen.kt +++ b/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeScreen.kt @@ -91,6 +91,8 @@ fun HomeScreen(viewModel: AppViewModel = androidx.lifecycle.viewmodel.compose.vi var playingVideo by rememberSaveable { mutableStateOf(null) } // shortUUID // Non-null while the playing video lives on ANOTHER instance (vidiverse results). var playingVideoHost by rememberSaveable { mutableStateOf(null) } + // Thumbnail of the opened card — shown as the player's poster while it loads. + var playingVideoThumb by rememberSaveable { mutableStateOf(null) } var restoreToken by rememberSaveable { mutableStateOf(0) } // increments when playback ends var drawerOpen by rememberSaveable { mutableStateOf(false) } val context = androidx.compose.ui.platform.LocalContext.current @@ -175,6 +177,8 @@ fun HomeScreen(viewModel: AppViewModel = androidx.lifecycle.viewmodel.compose.vi val localHost = AppGraph.tokenStore.baseUrl?.substringAfter("://")?.substringBefore('/') val host = video.channel.host playingVideoHost = if (host != null && host != localHost) host else null + playingVideoThumb = video.thumbnailUrl + ?: video.thumbnailPath?.takeIf { it.isNotBlank() }?.let { AppGraph.repository.absoluteUrl(it) } playingVideo = video.shortUUID } Box(modifier = Modifier.fillMaxSize()) { @@ -273,13 +277,15 @@ fun HomeScreen(viewModel: AppViewModel = androidx.lifecycle.viewmodel.compose.vi PlayerScreen( shortUuid = playingVideo!!, sourceHost = playingVideoHost, + posterUrl = playingVideoThumb, onBack = { playingVideo = null }, onOpenChannel = { handle, displayName -> playingVideo = null channelDetails = handle channelName = displayName }, - onOpenVideo = { playingVideo = it; playingVideoHost = null }, + // A link-opened video has no card thumbnail to pose with. + onOpenVideo = { playingVideo = it; playingVideoHost = null; playingVideoThumb = null }, ) } } 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 3ba847b..95bcf0a 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 @@ -207,6 +207,9 @@ fun PlayerScreen( onBack: () -> Unit, /** Set when the video lives on another instance (vidiverse search results). */ sourceHost: String? = null, + /** Thumbnail of the opened card, shown full-screen while the metadata loads so + * the player doesn't flash black before buffering starts (user report). */ + posterUrl: String? = null, // From the info sidebar's local PeerTube video links: switches the player over. onOpenVideo: (String) -> Unit = {}, // From the paused/controls overlay's channel icon: (handle, displayName), @@ -227,15 +230,27 @@ fun PlayerScreen( } when (val s = state) { - is PlayerUiState.Loading -> PlayerLoading() + is PlayerUiState.Loading -> PlayerLoading(posterUrl) is PlayerUiState.Error -> PlayerError(s.message, viewModel, onBack) is PlayerUiState.Ready -> PlayerSurface(s, viewModel, shortUuid, onBack, onOpenChannel, onOpenVideo) } } @Composable -private fun PlayerLoading() { +private fun PlayerLoading(posterUrl: String? = null) { Box(Modifier.fillMaxSize().background(Color.Black), contentAlignment = Alignment.Center) { + // Show the card's thumbnail (dimmed) while the metadata resolves, so the + // player opens onto the video's own poster instead of plain black; it + // hands off seamlessly to the buffering thumbnail once the stream is ready. + if (posterUrl != null) { + coil.compose.AsyncImage( + model = posterUrl, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize(), + ) + Box(Modifier.fillMaxSize().background(Color(0xB3000000))) + } CircularProgressIndicator() } }