diff --git a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/MediaMetadataView.kt b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/MediaMetadataView.kt new file mode 100644 index 000000000..a290d9d75 --- /dev/null +++ b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/MediaMetadataView.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.demo.tv.ui.player.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.media3.common.MediaMetadata +import androidx.tv.material3.ExperimentalTvMaterial3Api +import androidx.tv.material3.MaterialTheme +import androidx.tv.material3.Text +import ch.srgssr.pillarbox.demo.tv.ui.theme.paddings +import coil.compose.AsyncImage + +/** + * Media metadata view + * + * @param mediaMetadata The [MediaMetadata] to display. + * @param modifier The Modifier. + */ +@OptIn(ExperimentalTvMaterial3Api::class) +@Composable +fun MediaMetadataView( + mediaMetadata: MediaMetadata, + modifier: Modifier = Modifier, +) { + Row(modifier.background(color = Color.Black)) { + AsyncImage( + modifier = Modifier + .width(200.dp) + .aspectRatio(16 / 9f), + contentScale = ContentScale.Fit, + model = mediaMetadata.artworkUri, + contentDescription = null, + ) + Column( + verticalArrangement = Arrangement.Bottom, + modifier = Modifier.padding(MaterialTheme.paddings.mini) + ) { + Text( + text = mediaMetadata.title?.toString() ?: "No title", + color = Color.White, + style = MaterialTheme.typography.bodyLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + mediaMetadata.description?.let { + Text( + text = mediaMetadata.description.toString(), + color = Color.White, + style = MaterialTheme.typography.bodyMedium, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } + } + } +} + +@Preview(device = Devices.TV_1080p) +@Composable +private fun MediaMetadataPreview() { + val mediaMetadata = MediaMetadata.Builder().setTitle("Title").setDescription("Description").build() + MediaMetadataView(mediaMetadata = mediaMetadata, modifier = Modifier.fillMaxSize()) +} diff --git a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/PlayerView.kt b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/PlayerView.kt index 7c2e18ca8..a3c6d0d2d 100644 --- a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/PlayerView.kt +++ b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/PlayerView.kt @@ -11,12 +11,18 @@ import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Settings import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -33,10 +39,16 @@ import ch.srgssr.pillarbox.demo.tv.ui.player.compose.controls.PlayerError import ch.srgssr.pillarbox.demo.tv.ui.player.compose.controls.PlayerPlaybackRow import ch.srgssr.pillarbox.demo.tv.ui.player.compose.settings.PlaybackSettingsDrawer import ch.srgssr.pillarbox.demo.tv.ui.theme.paddings +import ch.srgssr.pillarbox.player.extension.getChapterAtPosition +import ch.srgssr.pillarbox.player.getCurrentChapterAsFlow +import ch.srgssr.pillarbox.ui.extension.currentMediaMetadataAsState import ch.srgssr.pillarbox.ui.extension.playerErrorAsState import ch.srgssr.pillarbox.ui.widget.maintainVisibleOnFocus import ch.srgssr.pillarbox.ui.widget.player.PlayerSurface import ch.srgssr.pillarbox.ui.widget.rememberDelayedVisibilityState +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.map +import kotlin.time.Duration.Companion.seconds /** * Tv player view @@ -69,6 +81,10 @@ fun PlayerView( if (error != null) { PlayerError(modifier = Modifier.fillMaxSize(), playerError = error!!, onRetry = player::prepare) } else { + val chapterFlow = remember(player) { + player.getCurrentChapterAsFlow().map { it?.mediaMetadata } + } + val chapterMetaData by chapterFlow.collectAsState(initial = player.getChapterAtPosition()?.mediaMetadata) PlayerSurface( player = player, modifier = Modifier @@ -81,6 +97,33 @@ fun PlayerView( ) .focusable(true) ) + var chapterInfoVisibility by remember { + mutableStateOf(chapterMetaData != null) + } + LaunchedEffect(chapterMetaData) { + chapterInfoVisibility = chapterMetaData != null + if (chapterInfoVisibility) { + delay(5.seconds) + chapterInfoVisibility = false + } + } + AnimatedVisibility( + visible = !visibilityState.isVisible && chapterInfoVisibility, + enter = expandVertically { it }, + exit = shrinkVertically { it } + ) { + Box(modifier = Modifier.fillMaxSize()) { + chapterMetaData?.let { + MediaMetadataView( + modifier = Modifier + .fillMaxWidth(0.5f) + .wrapContentHeight() + .align(Alignment.BottomStart), + mediaMetadata = it + ) + } + } + } AnimatedVisibility( visible = visibilityState.isVisible, enter = expandVertically { it }, @@ -97,6 +140,15 @@ fun PlayerView( state = visibilityState ) + val currentMediaMetadata by player.currentMediaMetadataAsState() + MediaMetadataView( + modifier = Modifier + .fillMaxWidth(0.5f) + .wrapContentHeight() + .align(Alignment.BottomStart), + mediaMetadata = chapterMetaData ?: currentMediaMetadata + ) + IconButton( onClick = { drawerState.setValue(DrawerValue.Open) }, modifier = Modifier