From 5cabf0cb672200061c0dfc9a2613ffbb4cb38692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 23 Jan 2024 10:46:46 +0100 Subject: [PATCH 1/9] Simplify Story integration --- .../demo/ui/showcases/layouts/SimpleStory.kt | 1 - .../ui/showcases/layouts/StoryLoadControl.kt | 41 ------------------- .../ui/showcases/layouts/StoryViewModel.kt | 3 -- 3 files changed, 45 deletions(-) delete mode 100644 pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/StoryLoadControl.kt diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/SimpleStory.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/SimpleStory.kt index 23ae136c8..8d5c7a2e0 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/SimpleStory.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/SimpleStory.kt @@ -56,7 +56,6 @@ private fun SimpleStoryPlayer(demoItem: DemoItem, isPlaying: Boolean = false) { val player = PillarboxPlayer( context = context, mediaItemSource = PlayerModule.provideMixedItemSource(context), - loadControl = StoryLoadControl.build() ).apply { setMediaItem(demoItem.toMediaItem()) prepare() diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/StoryLoadControl.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/StoryLoadControl.kt deleted file mode 100644 index b76c80697..000000000 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/StoryLoadControl.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) SRG SSR. All rights reserved. - * License information is available from the LICENSE file. - */ -package ch.srgssr.pillarbox.demo.ui.showcases.layouts - -import androidx.media3.exoplayer.DefaultLoadControl -import androidx.media3.exoplayer.LoadControl - -/** - * Story load control - * - * Build a Custom [LoadControl] to optimize playbabck startup. - * - * Warning bad parameters can lead to OOM pretty quickly. - */ -object StoryLoadControl { - // Minimum Video you want to buffer while Playing - private const val MIN_BUFFER_DURATION = 1000 - - // Max Video you want to buffer during PlayBack - private const val MAX_BUFFER_DURATION = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS - - // Min Video you want to buffer before start Playing it - private const val MIN_PLAYBACK_START_BUFFER = 1000 - - // Min video You want to buffer when user resumes video - private const val MIN_PLAYBACK_RESUME_BUFFER = 1000 - - /** - * Build a new [LoadControl] optimized for Story view - */ - fun build(): LoadControl = DefaultLoadControl.Builder() - .setBufferDurationsMs( - MIN_BUFFER_DURATION, - MAX_BUFFER_DURATION, - MIN_PLAYBACK_START_BUFFER, - MIN_PLAYBACK_RESUME_BUFFER - ) - .build() -} diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/StoryViewModel.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/StoryViewModel.kt index 891e19d1e..7fe1047a8 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/StoryViewModel.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/StoryViewModel.kt @@ -30,19 +30,16 @@ class StoryViewModel(application: Application) : AndroidViewModel(application) { PillarboxPlayer( context = application, mediaItemSource = mediaItemSource, - loadControl = StoryLoadControl.build(), mediaItemTrackerProvider = itemTrackerProvider ), PillarboxPlayer( context = application, mediaItemSource = mediaItemSource, - loadControl = StoryLoadControl.build(), mediaItemTrackerProvider = itemTrackerProvider ), PillarboxPlayer( context = application, mediaItemSource = mediaItemSource, - loadControl = StoryLoadControl.build(), mediaItemTrackerProvider = itemTrackerProvider ) ) From acbcfc5d7b696b4e7fb256fc5d8a26595d29e607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 23 Jan 2024 11:32:52 +0100 Subject: [PATCH 2/9] Update simple story showcase --- .../demo/ui/showcases/layouts/SimpleStory.kt | 67 ++++++++++++------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/SimpleStory.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/SimpleStory.kt index 8d5c7a2e0..2ce51b54a 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/SimpleStory.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/layouts/SimpleStory.kt @@ -4,22 +4,29 @@ */ package ch.srgssr.pillarbox.demo.ui.showcases.layouts +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerDefaults +import androidx.compose.foundation.pager.PagerSnapDistance import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.viewinterop.AndroidView +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.compose.LifecycleStartEffect import androidx.media3.common.C import androidx.media3.common.Player -import androidx.media3.ui.AspectRatioFrameLayout -import androidx.media3.ui.PlayerView import ch.srgssr.pillarbox.demo.shared.data.DemoItem import ch.srgssr.pillarbox.demo.shared.data.Playlist import ch.srgssr.pillarbox.demo.shared.di.PlayerModule import ch.srgssr.pillarbox.player.PillarboxPlayer +import ch.srgssr.pillarbox.ui.ScaleMode +import ch.srgssr.pillarbox.ui.widget.player.PlayerSurface /** * A sample trying to reproduce story like TikTok. @@ -29,13 +36,19 @@ import ch.srgssr.pillarbox.player.PillarboxPlayer @Composable fun SimpleStory() { val playlist = remember { - Playlist.VideoUrls + Playlist.VideoUrns } val pagerState = rememberPagerState { playlist.items.size } HorizontalPager( modifier = Modifier.fillMaxHeight(), key = { page -> playlist.items[page].uri }, + beyondBoundsPageCount = 1, + flingBehavior = PagerDefaults.flingBehavior( + state = pagerState, + pagerSnapDistance = PagerSnapDistance.atMost(0), + snapAnimationSpec = spring(stiffness = Spring.StiffnessHigh) + ), state = pagerState ) { page -> SimpleStoryPlayer(demoItem = playlist.items[page], isPlaying = pagerState.currentPage == page) @@ -51,28 +64,30 @@ fun SimpleStory() { */ @Composable private fun SimpleStoryPlayer(demoItem: DemoItem, isPlaying: Boolean = false) { - AndroidView( - factory = { context -> - val player = PillarboxPlayer( - context = context, - mediaItemSource = PlayerModule.provideMixedItemSource(context), - ).apply { - setMediaItem(demoItem.toMediaItem()) - prepare() - videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING - repeatMode = Player.REPEAT_MODE_ONE - playWhenReady = isPlaying - } - - PlayerView(context).apply { - hideController() - useController = false - resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM - this.player = player - } - }, - onRelease = { - it.player?.release() + val context = LocalContext.current + val player = remember(demoItem) { + PlayerModule.provideDefaultPlayer(context).apply { + setMediaItem(demoItem.toMediaItem()) + videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING + repeatMode = Player.REPEAT_MODE_ONE + } + } + DisposableEffect(player) { + player.prepare() + onDispose { + player.release() } + } + PlayerSurface( + modifier = Modifier.fillMaxSize(), + player = player, + scaleMode = ScaleMode.Crop ) + + LifecycleStartEffect(isPlaying) { + player.playWhenReady = isPlaying + onStopOrDispose { + player.pause() + } + } } From c26fa6c41dd466d582f1772e9249cf30c2b975c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 23 Jan 2024 11:57:40 +0100 Subject: [PATCH 3/9] Pause player when app goes in background --- .../demo/ui/showcases/misc/AdaptivePlayerShowcase.kt | 8 +++++++- .../demo/ui/showcases/misc/MultiPlayerShowcase.kt | 10 ++++++++++ .../demo/ui/showcases/misc/StartAtGivenTimeShowcase.kt | 8 +++++++- .../ui/showcases/misc/UpdatableMediaItemShowcase.kt | 7 +++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/AdaptivePlayerShowcase.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/AdaptivePlayerShowcase.kt index 4a47d90ae..76ddf84d8 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/AdaptivePlayerShowcase.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/AdaptivePlayerShowcase.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.compose.LifecycleStartEffect import androidx.media3.common.Player import ch.srgssr.pillarbox.demo.shared.data.Playlist import ch.srgssr.pillarbox.demo.shared.di.PlayerModule @@ -53,11 +54,16 @@ fun AdaptivePlayerShowcase() { AdaptivePlayer(player = player, modifier = Modifier.fillMaxSize()) DisposableEffect(player) { player.prepare() - player.play() onDispose { player.release() } } + LifecycleStartEffect(player) { + player.play() + onStopOrDispose { + player.pause() + } + } } @Composable diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt index eee6284d2..a68daeb19 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration +import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.viewmodel.compose.viewModel import ch.srgssr.pillarbox.demo.ui.player.PlayerView import ch.srgssr.pillarbox.demo.ui.theme.paddings @@ -37,6 +38,15 @@ fun MultiPlayerShowcase() { val playerLeft = multiPlayerViewModel.getPlayerLeft(swapLeftRight) val playerRight = multiPlayerViewModel.getPlayerRight(swapLeftRight) + LifecycleResumeEffect(Unit) { + playerLeft.play() + playerRight.play() + onPauseOrDispose { + playerLeft.pause() + playerRight.pause() + } + } + Column(horizontalAlignment = Alignment.CenterHorizontally) { Button(onClick = { swapLeftRight = !swapLeftRight }) { Text(text = "Swap players") diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/StartAtGivenTimeShowcase.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/StartAtGivenTimeShowcase.kt index f08a9080a..3e41c7a03 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/StartAtGivenTimeShowcase.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/StartAtGivenTimeShowcase.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.compose.LifecycleResumeEffect import ch.srgssr.pillarbox.demo.shared.data.DemoItem import ch.srgssr.pillarbox.demo.shared.di.PlayerModule import ch.srgssr.pillarbox.demo.ui.player.PlayerView @@ -23,10 +24,15 @@ fun StartAtGivenTimeShowcase() { PlayerModule.provideDefaultPlayer(context).apply { setMediaItem(DemoItem.AppleBasic_16_9_TS_HLS.toMediaItem()) prepare() - play() seekTo(10.minutes.inWholeMilliseconds) } } + LifecycleResumeEffect(player) { + player.play() + onPauseOrDispose { + player.pause() + } + } DisposableEffect(player) { onDispose { player.release() diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/UpdatableMediaItemShowcase.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/UpdatableMediaItemShowcase.kt index 6c5c3566c..52f54e3f0 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/UpdatableMediaItemShowcase.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/UpdatableMediaItemShowcase.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.viewmodel.compose.viewModel import ch.srgssr.pillarbox.ui.extension.currentMediaMetadataAsState import ch.srgssr.pillarbox.ui.widget.player.PlayerSurface @@ -32,4 +33,10 @@ fun UpdatableMediaItemShowcase() { ) } } + LifecycleResumeEffect(player) { + player.play() + onPauseOrDispose { + player.pause() + } + } } From bd57c6beddf15a4a1f6f9b2268b775a8d1113418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 23 Jan 2024 11:58:19 +0100 Subject: [PATCH 4/9] Improve start at a given time --- .../demo/ui/showcases/misc/StartAtGivenTimeShowcase.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/StartAtGivenTimeShowcase.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/StartAtGivenTimeShowcase.kt index 3e41c7a03..3834a7dbb 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/StartAtGivenTimeShowcase.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/StartAtGivenTimeShowcase.kt @@ -22,9 +22,8 @@ fun StartAtGivenTimeShowcase() { val context = LocalContext.current val player = remember { PlayerModule.provideDefaultPlayer(context).apply { - setMediaItem(DemoItem.AppleBasic_16_9_TS_HLS.toMediaItem()) + setMediaItem(DemoItem.AppleBasic_16_9_TS_HLS.toMediaItem(), 10.minutes.inWholeMilliseconds) prepare() - seekTo(10.minutes.inWholeMilliseconds) } } LifecycleResumeEffect(player) { From 74936f300dc871ba5a4d688555395818f44b418d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 23 Jan 2024 12:03:28 +0100 Subject: [PATCH 5/9] Rename to ResizablePlayer --- .../srgssr/pillarbox/demo/ui/showcases/ShowcasesNavigation.kt | 4 ++-- .../{AdaptivePlayerShowcase.kt => ResizablePlayerShowcase.kt} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/{AdaptivePlayerShowcase.kt => ResizablePlayerShowcase.kt} (98%) 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 b8ae0a4d6..d5e255a47 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 @@ -12,8 +12,8 @@ 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 import ch.srgssr.pillarbox.demo.ui.showcases.layouts.StoryLayoutShowcase -import ch.srgssr.pillarbox.demo.ui.showcases.misc.AdaptivePlayerShowcase import ch.srgssr.pillarbox.demo.ui.showcases.misc.MultiPlayerShowcase +import ch.srgssr.pillarbox.demo.ui.showcases.misc.ResizablePlayerShowcase import ch.srgssr.pillarbox.demo.ui.showcases.misc.SmoothSeekingShowcase import ch.srgssr.pillarbox.demo.ui.showcases.misc.StartAtGivenTimeShowcase import ch.srgssr.pillarbox.demo.ui.showcases.misc.TrackingToggleShowcase @@ -33,7 +33,7 @@ fun NavGraphBuilder.showcasesNavGraph(navController: NavController) { SimpleLayoutShowcase() } composable(NavigationRoutes.adaptive, DemoPageView("adaptive player", Levels)) { - AdaptivePlayerShowcase() + ResizablePlayerShowcase() } composable(NavigationRoutes.playerSwap, DemoPageView("multiplayer", Levels)) { MultiPlayerShowcase() diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/AdaptivePlayerShowcase.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/ResizablePlayerShowcase.kt similarity index 98% rename from pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/AdaptivePlayerShowcase.kt rename to pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/ResizablePlayerShowcase.kt index 76ddf84d8..d620d27df 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/AdaptivePlayerShowcase.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/ResizablePlayerShowcase.kt @@ -38,11 +38,11 @@ import ch.srgssr.pillarbox.ui.ScaleMode import ch.srgssr.pillarbox.ui.widget.player.PlayerSurface /** - * Adaptive player demo + * Resizable player demo * The view allow to resize the player view and changing the scale mode */ @Composable -fun AdaptivePlayerShowcase() { +fun ResizablePlayerShowcase() { val context = LocalContext.current val player = remember { PlayerModule.provideDefaultPlayer(context).apply { From 54af5b1df3b886799f1c6445ef144e8b0da10011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 23 Jan 2024 13:50:21 +0100 Subject: [PATCH 6/9] Simplify Mutli player showcase --- .../ui/showcases/misc/MultiPlayerShowcase.kt | 52 +++++++++----- .../ui/showcases/misc/MultiPlayerViewModel.kt | 69 ------------------- 2 files changed, 36 insertions(+), 85 deletions(-) delete mode 100644 pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerViewModel.kt diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt index a68daeb19..c125c8b94 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt @@ -14,6 +14,7 @@ import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -21,8 +22,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.LifecycleResumeEffect -import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.media3.common.Player +import ch.srgssr.pillarbox.demo.shared.data.DemoItem +import ch.srgssr.pillarbox.demo.shared.di.PlayerModule import ch.srgssr.pillarbox.demo.ui.player.PlayerView import ch.srgssr.pillarbox.demo.ui.theme.paddings @@ -31,22 +35,40 @@ import ch.srgssr.pillarbox.demo.ui.theme.paddings */ @Composable fun MultiPlayerShowcase() { - val multiPlayerViewModel: MultiPlayerViewModel = viewModel() var swapLeftRight by remember { mutableStateOf(false) } - val playerLeft = multiPlayerViewModel.getPlayerLeft(swapLeftRight) - val playerRight = multiPlayerViewModel.getPlayerRight(swapLeftRight) - + val context = LocalContext.current + val playerOne = remember { + PlayerModule.provideDefaultPlayer(context).apply { + repeatMode = Player.REPEAT_MODE_ONE + setMediaItem(DemoItem.LiveVideo.toMediaItem()) + prepare() + } + } + val playerTwo = remember { + PlayerModule.provideDefaultPlayer(context).apply { + repeatMode = Player.REPEAT_MODE_ONE + setMediaItem(DemoItem.DvrVideo.toMediaItem()) + prepare() + } + } + DisposableEffect(Unit) { + onDispose { + playerOne.release() + playerTwo.release() + } + } LifecycleResumeEffect(Unit) { - playerLeft.play() - playerRight.play() + playerOne.play() + playerTwo.play() onPauseOrDispose { - playerLeft.pause() - playerRight.pause() + playerOne.pause() + playerTwo.pause() } } - + val leftPlayer = if (swapLeftRight) playerOne else playerTwo + val rightPlayer = if (swapLeftRight) playerTwo else playerOne Column(horizontalAlignment = Alignment.CenterHorizontally) { Button(onClick = { swapLeftRight = !swapLeftRight }) { Text(text = "Swap players") @@ -56,16 +78,14 @@ fun MultiPlayerShowcase() { PlayerView( modifier = Modifier .weight(1.0f) - .aspectRatio(AspectRatio) .padding(MaterialTheme.paddings.mini), - player = playerLeft + player = leftPlayer, ) PlayerView( modifier = Modifier .weight(1.0f) - .aspectRatio(AspectRatio) .padding(MaterialTheme.paddings.mini), - player = playerRight + player = rightPlayer ) } } else { @@ -75,14 +95,14 @@ fun MultiPlayerShowcase() { .weight(1.0f) .aspectRatio(AspectRatio) .padding(MaterialTheme.paddings.mini), - player = playerLeft + player = leftPlayer ) PlayerView( modifier = Modifier .weight(1.0f) .aspectRatio(AspectRatio) .padding(MaterialTheme.paddings.mini), - player = playerRight + player = rightPlayer ) } } diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerViewModel.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerViewModel.kt deleted file mode 100644 index c902cee3e..000000000 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerViewModel.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) SRG SSR. All rights reserved. - * License information is available from the LICENSE file. - */ -package ch.srgssr.pillarbox.demo.ui.showcases.misc - -import android.app.Application -import androidx.lifecycle.AndroidViewModel -import androidx.media3.common.Player -import ch.srgssr.pillarbox.demo.shared.data.DemoItem -import ch.srgssr.pillarbox.demo.shared.di.PlayerModule - -/** - * Multi player view model - * - * Two players playing content endlessly. - * There is no audio focus and audio volume handle for this demo. - */ -class MultiPlayerViewModel(application: Application) : AndroidViewModel(application) { - private val player1 = PlayerModule.provideDefaultPlayer(application) - private val player2 = PlayerModule.provideDefaultPlayer(application) - - init { - /* - * On some devices playing DRM content on multiple players may not work. - * One of the players will receive a PlaybackException with ERROR_CODE_DECODER_INIT_FAILED. - * It may happen on low-end devices like Samsung Galaxy A13, for example. - */ - player1.setMediaItem(DemoItem.LiveVideo.toMediaItem()) - player2.setMediaItem(DemoItem.DvrVideo.toMediaItem()) - preparePlayer(player1) - preparePlayer(player2) - } - - private fun preparePlayer(player: Player) { - player.repeatMode = Player.REPEAT_MODE_ONE - player.prepare() - player.play() - } - - /** - * Get player left - * - * @param playerSwap - */ - fun getPlayerLeft(playerSwap: Boolean): Player { - return if (playerSwap) { - player1 - } else { - player2 - } - } - - /** - * Get player right - * - * @param playerSwap - * @return - */ - fun getPlayerRight(playerSwap: Boolean): Player { - return getPlayerLeft(!playerSwap) - } - - override fun onCleared() { - super.onCleared() - player1.release() - player2.release() - } -} From ae6c7c7f4319f57a9f78d2119b3d9e920617ee06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 23 Jan 2024 16:29:06 +0100 Subject: [PATCH 7/9] Remove depreciated call of lifecycleScope.launchWhenCreated --- .../demo/ui/player/SimplePlayerActivity.kt | 19 ++++++++++--------- .../integrations/MediaControllerActivity.kt | 18 +++++++++--------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/SimplePlayerActivity.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/SimplePlayerActivity.kt index 2088c23f0..c4c7e8b30 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/SimplePlayerActivity.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/SimplePlayerActivity.kt @@ -25,8 +25,8 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.media3.common.Player import ch.srgssr.pillarbox.analytics.SRGAnalytics import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost @@ -72,14 +72,9 @@ class SimplePlayerActivity : ComponentActivity(), ServiceConnection { val ilHost = (intent.extras?.getSerializable(ARG_IL_HOST) as URL?) ?: IlHost.DEFAULT playerViewModel = ViewModelProvider(this, factory = SimplePlayerViewModel.Factory(application, ilHost))[SimplePlayerViewModel::class.java] readIntent(intent) - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - SRGAnalytics.trackPagView(DemoPageView("simple player", levels = listOf("app", "pillarbox"))) - } - } - lifecycleScope.launchWhenCreated { - playerViewModel.pictureInPictureRatio.collectLatest { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && playerViewModel.pictureInPictureEnabled.value) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + lifecycleScope.launch { + playerViewModel.pictureInPictureRatio.flowWithLifecycle(lifecycle, Lifecycle.State.CREATED).collectLatest { val params = PictureInPictureParams.Builder() .setAspectRatio(playerViewModel.pictureInPictureRatio.value) .build() @@ -87,6 +82,7 @@ class SimplePlayerActivity : ComponentActivity(), ServiceConnection { } } } + // Bind PlaybackService to allow background playback and MediaNotification. bindPlaybackService() setContent { @@ -166,6 +162,11 @@ class SimplePlayerActivity : ComponentActivity(), ServiceConnection { } } + override fun onResume() { + super.onResume() + SRGAnalytics.trackPagView(DemoPageView("simple player", levels = listOf("app", "pillarbox"))) + } + override fun onStart() { super.onStart() playerViewModel.player.play() diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/integrations/MediaControllerActivity.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/integrations/MediaControllerActivity.kt index 6a6af2410..88811c242 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/integrations/MediaControllerActivity.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/integrations/MediaControllerActivity.kt @@ -20,8 +20,8 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.Lifecycle +import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.media3.common.Player import ch.srgssr.pillarbox.analytics.SRGAnalytics import ch.srgssr.pillarbox.demo.DemoPageView @@ -43,14 +43,9 @@ class MediaControllerActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - SRGAnalytics.trackPagView(DemoPageView("media controller player", levels = listOf("app", "pillarbox"))) - } - } - lifecycleScope.launchWhenCreated { - controllerViewModel.pictureInPictureRatio.collectLatest { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && controllerViewModel.pictureInPictureEnabled.value) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + lifecycleScope.launch { + controllerViewModel.pictureInPictureRatio.flowWithLifecycle(lifecycle, Lifecycle.State.CREATED).collectLatest { val params = PictureInPictureParams.Builder() .setAspectRatio(controllerViewModel.pictureInPictureRatio.value) .build() @@ -111,4 +106,9 @@ class MediaControllerActivity : ComponentActivity() { controllerViewModel.pictureInPictureEnabled.value = isInPictureInPictureMode } } + + override fun onResume() { + super.onResume() + SRGAnalytics.trackPagView(DemoPageView("media controller player", levels = listOf("app", "pillarbox"))) + } } From 2163f21649aa6ac7b51cb673e8dfdedea9f16685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Thu, 25 Jan 2024 14:43:13 +0100 Subject: [PATCH 8/9] Avoid re-accessing the flow. --- .../ch/srgssr/pillarbox/demo/ui/player/SimplePlayerActivity.kt | 2 +- .../demo/ui/showcases/integrations/MediaControllerActivity.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/SimplePlayerActivity.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/SimplePlayerActivity.kt index c4c7e8b30..b284367ea 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/SimplePlayerActivity.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/SimplePlayerActivity.kt @@ -76,7 +76,7 @@ class SimplePlayerActivity : ComponentActivity(), ServiceConnection { lifecycleScope.launch { playerViewModel.pictureInPictureRatio.flowWithLifecycle(lifecycle, Lifecycle.State.CREATED).collectLatest { val params = PictureInPictureParams.Builder() - .setAspectRatio(playerViewModel.pictureInPictureRatio.value) + .setAspectRatio(it) .build() setPictureInPictureParams(params) } diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/integrations/MediaControllerActivity.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/integrations/MediaControllerActivity.kt index 88811c242..bacd227a8 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/integrations/MediaControllerActivity.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/integrations/MediaControllerActivity.kt @@ -47,7 +47,7 @@ class MediaControllerActivity : ComponentActivity() { lifecycleScope.launch { controllerViewModel.pictureInPictureRatio.flowWithLifecycle(lifecycle, Lifecycle.State.CREATED).collectLatest { val params = PictureInPictureParams.Builder() - .setAspectRatio(controllerViewModel.pictureInPictureRatio.value) + .setAspectRatio(it) .build() setPictureInPictureParams(params) } From 20f132132777a68675cf946d2d9c911bb54c8a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Thu, 25 Jan 2024 14:44:54 +0100 Subject: [PATCH 9/9] Update pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gaƫtan Muller --- .../pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt index c125c8b94..3ff7a431b 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/MultiPlayerShowcase.kt @@ -67,8 +67,8 @@ fun MultiPlayerShowcase() { playerTwo.pause() } } - val leftPlayer = if (swapLeftRight) playerOne else playerTwo - val rightPlayer = if (swapLeftRight) playerTwo else playerOne + val leftPlayer = if (swapLeftRight) playerTwo else playerOne + val rightPlayer = if (swapLeftRight) playerOne else playerTwo Column(horizontalAlignment = Alignment.CenterHorizontally) { Button(onClick = { swapLeftRight = !swapLeftRight }) { Text(text = "Swap players")