player: show the card thumbnail as a poster while the video loads
Opening a video flashed plain black during the metadata-loading phase (before buffering, where the thumbnail already shows) — most visible on the TV where that phase is longer. Pass the opened card's thumbnail into the player and paint it full-screen (dimmed, under the spinner) during loading, handing off to the buffering thumbnail once the stream is ready, so the player opens onto the video's own poster instead of black. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
46a325eed3
commit
fcb3be8e96
2 changed files with 24 additions and 3 deletions
|
|
@ -91,6 +91,8 @@ fun HomeScreen(viewModel: AppViewModel = androidx.lifecycle.viewmodel.compose.vi
|
|||
var playingVideo by rememberSaveable { mutableStateOf<String?>(null) } // shortUUID
|
||||
// Non-null while the playing video lives on ANOTHER instance (vidiverse results).
|
||||
var playingVideoHost by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
// Thumbnail of the opened card — shown as the player's poster while it loads.
|
||||
var playingVideoThumb by rememberSaveable { mutableStateOf<String?>(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 },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue