From c86f4282fc891a852d4862fdbb2697eabb3a9f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Thu, 25 Apr 2024 15:38:27 +0200 Subject: [PATCH] Add a showcase to customise playback settings (#510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joaquim Stähli --- .../pillarbox/demo/shared/data/Playlist.kt | 5 + .../demo/shared/ui/NavigationRoutes.kt | 1 + .../demo/ui/components/DemoListItemView.kt | 6 +- .../demo/ui/showcases/ShowcasesHome.kt | 27 +-- .../demo/ui/showcases/ShowcasesNavigation.kt | 5 + .../CustomPlaybackSettingsShowcase.kt | 170 ++++++++++++++++++ .../src/main/res/values/strings.xml | 6 + 7 files changed, 206 insertions(+), 14 deletions(-) create mode 100644 pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/playlists/CustomPlaybackSettingsShowcase.kt diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/data/Playlist.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/data/Playlist.kt index 7d3f6c705..ccc3a891d 100644 --- a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/data/Playlist.kt +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/data/Playlist.kt @@ -598,6 +598,11 @@ data class Playlist(val title: String, val items: List, val descriptio ) ) + val EmptyPlaylist = Playlist( + title = "Empty", + items = emptyList(), + ) + val StreamUrls = Playlist( title = "Media with urls", items = listOf( diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/NavigationRoutes.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/NavigationRoutes.kt index db1306f21..b8398b9a3 100644 --- a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/NavigationRoutes.kt +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/NavigationRoutes.kt @@ -13,6 +13,7 @@ object NavigationRoutes { const val homeSample = "home_sample" const val homeShowcases = "home_showcases" const val showcaseList = "showcase_list" + const val showcasePlaybackSettings = "showcase_playback_settings" const val story = "story" const val simplePlayer = "simple_player" const val adaptive = "adaptive" diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/components/DemoListItemView.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/components/DemoListItemView.kt index 70968d6d4..1a71d1cd7 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/components/DemoListItemView.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/components/DemoListItemView.kt @@ -31,7 +31,7 @@ fun DemoListItemView( title: String, modifier: Modifier = Modifier, subtitle: String? = null, - onClick: () -> Unit = {} + onClick: () -> Unit, ) { Column( modifier = modifier @@ -72,12 +72,14 @@ private fun DemoItemPreview() { DemoListItemView( modifier = itemModifier, title = "Title 1", - subtitle = "Description 1" + subtitle = "Description 1", + onClick = {}, ) DemoListItemView( modifier = itemModifier, title = "Title 2", + onClick = {}, ) } } diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/ShowcasesHome.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/ShowcasesHome.kt index 5fb60177a..5dafc8b09 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/ShowcasesHome.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/ShowcasesHome.kt @@ -43,7 +43,7 @@ fun ShowcasesHome(navController: NavController) { Playlist.MixedContent, Playlist.MixedContentLiveDvrVod, Playlist.MixedContentLiveOnlyVod, - Playlist("Empty", emptyList()) + Playlist.EmptyPlaylist, ) } val titleModifier = Modifier.padding( @@ -85,17 +85,22 @@ fun ShowcasesHome(navController: NavController) { ) DemoListSectionView { - playlists.forEachIndexed { index, item -> + playlists.forEach { item -> DemoListItemView( title = item.title, modifier = itemModifier, + subtitle = item.description, onClick = { SimplePlayerActivity.startActivity(context, item) } ) - if (index < playlists.lastIndex) { - HorizontalDivider() - } + HorizontalDivider() } + + DemoListItemView( + title = stringResource(R.string.showcase_playback_settings), + modifier = itemModifier, + onClick = { navController.navigate(NavigationRoutes.showcasePlaybackSettings) }, + ) } DemoListHeaderView( @@ -104,13 +109,11 @@ fun ShowcasesHome(navController: NavController) { ) DemoListSectionView { - DemoListSectionView { - DemoListItemView( - title = stringResource(R.string.exoplayer_view), - modifier = itemModifier, - onClick = { navController.navigate(NavigationRoutes.exoPlayerSample) } - ) - } + DemoListItemView( + title = stringResource(R.string.exoplayer_view), + modifier = itemModifier, + onClick = { navController.navigate(NavigationRoutes.exoPlayerSample) } + ) HorizontalDivider() diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/ShowcasesNavigation.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/ShowcasesNavigation.kt index 497d326a0..ae5b4bfc7 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/ShowcasesNavigation.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/ShowcasesNavigation.kt @@ -8,6 +8,7 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import ch.srgssr.pillarbox.demo.DemoPageView import ch.srgssr.pillarbox.demo.composable +import ch.srgssr.pillarbox.demo.shared.data.Playlist import ch.srgssr.pillarbox.demo.shared.ui.NavigationRoutes import ch.srgssr.pillarbox.demo.ui.showcases.integrations.ExoPlayerShowcase import ch.srgssr.pillarbox.demo.ui.showcases.layouts.SimpleLayoutShowcase @@ -19,6 +20,7 @@ import ch.srgssr.pillarbox.demo.ui.showcases.misc.SphericalSurfaceShowcase import ch.srgssr.pillarbox.demo.ui.showcases.misc.StartAtGivenTimeShowcase import ch.srgssr.pillarbox.demo.ui.showcases.misc.TrackingToggleShowcase import ch.srgssr.pillarbox.demo.ui.showcases.misc.UpdatableMediaItemShowcase +import ch.srgssr.pillarbox.demo.ui.showcases.playlists.CustomPlaybackSettingsShowcase /** * Inject Showcases Navigation @@ -27,6 +29,9 @@ fun NavGraphBuilder.showcasesNavGraph(navController: NavController) { composable(NavigationRoutes.showcaseList, DemoPageView("home", Levels)) { ShowcasesHome(navController = navController) } + composable(NavigationRoutes.showcasePlaybackSettings, DemoPageView("playback settings", Levels)) { + CustomPlaybackSettingsShowcase(playlist = Playlist.VideoUrls) + } composable(NavigationRoutes.story, DemoPageView("story", Levels)) { StoryLayoutShowcase() } diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/playlists/CustomPlaybackSettingsShowcase.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/playlists/CustomPlaybackSettingsShowcase.kt new file mode 100644 index 000000000..63e84c84e --- /dev/null +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/playlists/CustomPlaybackSettingsShowcase.kt @@ -0,0 +1,170 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.demo.ui.showcases.playlists + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +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.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.LifecycleResumeEffect +import androidx.media3.common.Player +import ch.srgssr.pillarbox.demo.R +import ch.srgssr.pillarbox.demo.shared.data.Playlist +import ch.srgssr.pillarbox.demo.ui.player.DemoPlayerView +import ch.srgssr.pillarbox.demo.ui.theme.paddings +import ch.srgssr.pillarbox.player.PillarboxExoPlayer + +/** + * Showcase allowing the user to change the repeat mode and decide if the current media item should pause when it ends. + * + * @param playlist The [Playlist] to play. + * @param modifier The [Modifier] to apply to the layout. + */ +@Composable +fun CustomPlaybackSettingsShowcase( + playlist: Playlist, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + val player = remember(playlist) { + PillarboxExoPlayer(context).apply { + setMediaItems(playlist.items.map { it.toMediaItem() }) + prepare() + play() + } + } + + val repeatModes = listOf( + Player.REPEAT_MODE_OFF to stringResource(R.string.repeat_mode_off), + Player.REPEAT_MODE_ONE to stringResource(R.string.repeat_mode_one), + Player.REPEAT_MODE_ALL to stringResource(R.string.repeat_mode_all), + ) + + var pauseAtEndOfItem by remember { mutableStateOf(player.pauseAtEndOfMediaItems) } + + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(MaterialTheme.paddings.small), + ) { + Box { + var showRepeatModeMenu by remember { mutableStateOf(false) } + var selectedRepeatModeIndex by remember { + mutableIntStateOf( + repeatModes.indexOfFirst { (repeatMode, _) -> + repeatMode == player.repeatMode + } + ) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { showRepeatModeMenu = true } + .padding( + horizontal = MaterialTheme.paddings.baseline, + vertical = MaterialTheme.paddings.small, + ), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text(text = stringResource(R.string.repeat_mode)) + + Text(text = repeatModes[selectedRepeatModeIndex].second) + } + + DropdownMenu( + expanded = showRepeatModeMenu, + onDismissRequest = { showRepeatModeMenu = false }, + offset = DpOffset( + x = -MaterialTheme.paddings.small, + y = 0.dp, + ), + ) { + repeatModes.forEachIndexed { index, (repeatMode, repeatModeLabel) -> + DropdownMenuItem( + text = { Text(text = repeatModeLabel) }, + onClick = { + selectedRepeatModeIndex = index + player.repeatMode = repeatMode + showRepeatModeMenu = false + }, + leadingIcon = { + AnimatedVisibility(index == selectedRepeatModeIndex) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = null, + ) + } + } + ) + } + } + } + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + pauseAtEndOfItem = !pauseAtEndOfItem + player.pauseAtEndOfMediaItems = pauseAtEndOfItem + } + .padding( + horizontal = MaterialTheme.paddings.baseline, + vertical = MaterialTheme.paddings.small, + ), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text(text = stringResource(R.string.pause_end_media_items)) + + Switch( + checked = pauseAtEndOfItem, + onCheckedChange = null, + ) + } + + DemoPlayerView( + player = player, + displayPlaylist = true, + ) + + LifecycleResumeEffect(player) { + player.play() + onPauseOrDispose { + player.pause() + } + } + DisposableEffect(player) { + onDispose { + player.release() + } + } + } +} diff --git a/pillarbox-demo/src/main/res/values/strings.xml b/pillarbox-demo/src/main/res/values/strings.xml index 6ae278c87..df96f1e71 100644 --- a/pillarbox-demo/src/main/res/values/strings.xml +++ b/pillarbox-demo/src/main/res/values/strings.xml @@ -28,4 +28,10 @@ Navigate up Start at given time (10min) 360° + Custom playback settings + Repeat mode + off + one + all + Pause at end of media items