From d510737079dfe6f4d448d707cfd1ed1f378b2be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Mon, 30 Sep 2024 14:01:15 +0200 Subject: [PATCH] Improve `PillarboxPreloadManager` --- .../ui/showcases/layouts/StoryViewModel.kt | 47 +++---------- .../player/PillarboxPreloadManager.kt | 68 ++++++++++++------- .../player/PillarboxPreloadManagerTest.kt | 16 ++--- 3 files changed, 62 insertions(+), 69 deletions(-) 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 ffb595edf..27744d4a7 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 @@ -5,62 +5,36 @@ package ch.srgssr.pillarbox.demo.ui.showcases.layouts import android.app.Application -import android.os.HandlerThread -import android.os.Process import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.ViewModel import androidx.media3.common.C import androidx.media3.common.MediaItem import androidx.media3.common.Player -import androidx.media3.exoplayer.upstream.DefaultAllocator import ch.srgssr.pillarbox.core.business.source.SRGAssetLoader import ch.srgssr.pillarbox.demo.shared.data.Playlist -import ch.srgssr.pillarbox.demo.shared.source.BlockedTimeRangeAssetLoader import ch.srgssr.pillarbox.player.PillarboxExoPlayer -import ch.srgssr.pillarbox.player.PillarboxLoadControl import ch.srgssr.pillarbox.player.PillarboxPreloadManager -import ch.srgssr.pillarbox.player.PlayerPool import ch.srgssr.pillarbox.player.source.PillarboxMediaSourceFactory -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds /** * [ViewModel] that manages multiple [Player]s that can be used in a story-like layout. */ class StoryViewModel(application: Application) : AndroidViewModel(application) { - private val playbackThread = HandlerThread("MediaSourceEdge:Playback", Process.THREAD_PRIORITY_AUDIO).apply { start() } - private val preloadLooper = playbackThread.looper - private val loadControl = PillarboxLoadControl( - bufferDurations = PillarboxLoadControl.BufferDurations( - minBufferDuration = 5.seconds, - maxBufferDuration = 20.seconds, - bufferForPlayback = 500.milliseconds, - ), - allocator = DefaultAllocator(false, C.DEFAULT_BUFFER_SEGMENT_SIZE), - ) private val preloadManager = PillarboxPreloadManager( context = application, mediaSourceFactory = PillarboxMediaSourceFactory(application).apply { addAssetLoader(SRGAssetLoader(application)) }, - playerPool = PlayerPool( - playersCount = 3, - playerFactory = { - PillarboxExoPlayer( - context = application, - mediaSourceFactory = PillarboxMediaSourceFactory(application).apply { - addAssetLoader(SRGAssetLoader(application)) - addAssetLoader(BlockedTimeRangeAssetLoader(application)) - }, - loadControl = loadControl, - playbackLooper = preloadLooper, - ).apply { - repeatMode = Player.REPEAT_MODE_ONE - videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING - prepare() - } - }, - ), + playerFactory = { playbackLooper -> + PillarboxExoPlayer( + context = application, + playbackLooper = playbackLooper, + ).apply { + repeatMode = Player.REPEAT_MODE_ONE + videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING + prepare() + } + }, ) /** @@ -104,6 +78,5 @@ class StoryViewModel(application: Application) : AndroidViewModel(application) { override fun onCleared() { super.onCleared() preloadManager.release() - playbackThread.quit() } } diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPreloadManager.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPreloadManager.kt index 6a946ceb9..9b8a7fd48 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPreloadManager.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPreloadManager.kt @@ -5,7 +5,9 @@ package ch.srgssr.pillarbox.player import android.content.Context +import android.os.HandlerThread import android.os.Looper +import android.os.Process import androidx.media3.common.C import androidx.media3.common.MediaItem import androidx.media3.exoplayer.DefaultRendererCapabilitiesList @@ -18,6 +20,7 @@ import androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STA import androidx.media3.exoplayer.source.preload.TargetPreloadStatusControl import androidx.media3.exoplayer.trackselection.TrackSelector import androidx.media3.exoplayer.upstream.BandwidthMeter +import androidx.media3.exoplayer.upstream.DefaultAllocator import ch.srgssr.pillarbox.player.source.PillarboxMediaSourceFactory import kotlin.math.abs import kotlin.time.Duration.Companion.milliseconds @@ -37,15 +40,16 @@ import kotlin.time.Duration.Companion.seconds * @param bandwidthMeter * @param rendererCapabilitiesListFactory * @param loadControl - * @param preloadLooper - * @param playerPool + * @param playbackThread + * @param playersCount + * @param playerFactory * * @see DefaultPreloadManager */ class PillarboxPreloadManager( context: Context, targetPreloadStatusControl: TargetPreloadStatusControl? = null, - private val mediaSourceFactory: MediaSource.Factory = PillarboxMediaSourceFactory(context), + mediaSourceFactory: MediaSource.Factory = PillarboxMediaSourceFactory(context), trackSelector: TrackSelector = PillarboxTrackSelector(context).apply { init({}, PillarboxBandwidthMeter(context)) }, @@ -53,29 +57,42 @@ class PillarboxPreloadManager( rendererCapabilitiesListFactory: RendererCapabilitiesList.Factory = DefaultRendererCapabilitiesList.Factory( PillarboxRenderersFactory(context) ), - private val loadControl: LoadControl = PillarboxLoadControl(), - preloadLooper: Looper = context.mainLooper, - private val playerPool: PlayerPool = PlayerPool( - playersCount = 3, - playerFactory = { - PillarboxExoPlayer( - context = context, - mediaSourceFactory = mediaSourceFactory, - loadControl = loadControl, - ) - }, + private val loadControl: LoadControl = PillarboxLoadControl( + bufferDurations = PillarboxLoadControl.BufferDurations( + minBufferDuration = 5.seconds, + maxBufferDuration = 20.seconds, + bufferForPlayback = 500.milliseconds, + ), + allocator = DefaultAllocator(false, C.DEFAULT_BUFFER_SEGMENT_SIZE), ), + private val playbackThread: HandlerThread = HandlerThread("PillarboxPreloadManager:Playback", Process.THREAD_PRIORITY_AUDIO), + playersCount: Int = 3, + playerFactory: (playbackLooper: Looper) -> PillarboxExoPlayer = { playbackLooper -> + PillarboxExoPlayer( + context = context, + loadControl = loadControl, + playbackLooper = playbackLooper, + ) + }, ) { - private val preloadManager = DefaultPreloadManager( - targetPreloadStatusControl ?: DefaultTargetPreloadStatusControl(), - mediaSourceFactory, - trackSelector, - bandwidthMeter, - rendererCapabilitiesListFactory, - loadControl.allocator, - preloadLooper, + private val playerPool = PlayerPool( + playersCount = playersCount, + playerFactory = { playerFactory(playbackThread.looper) }, ) + // We use a lazy creation so the playbackThread can be started first + private val preloadManager by lazy { + DefaultPreloadManager( + targetPreloadStatusControl ?: DefaultTargetPreloadStatusControl(), + mediaSourceFactory, + trackSelector, + bandwidthMeter, + rendererCapabilitiesListFactory, + loadControl.allocator, + playbackThread.looper, + ) + } + /** * The index of the currently playing media item. * @@ -95,6 +112,10 @@ class PillarboxPreloadManager( val sourceCount: Int get() = preloadManager.sourceCount + init { + playbackThread.start() + } + /** * Add a [MediaItem] with its [rankingData] to the preload manager. * @@ -146,6 +167,7 @@ class PillarboxPreloadManager( fun release() { playerPool.release() preloadManager.release() + playbackThread.quit() } /** @@ -200,7 +222,7 @@ class PillarboxPreloadManager( /** * Default implementation of [TargetPreloadStatusControl] that will preload the first second of the `n ± 1` item, and the first half-second of - * the `n ± 2` item, where `n` is the index of the current item. + * the `n ± 2,3` item, where `n` is the index of the current item. */ @Suppress("MagicNumber") inner class DefaultTargetPreloadStatusControl : TargetPreloadStatusControl { diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/PillarboxPreloadManagerTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/PillarboxPreloadManagerTest.kt index 776316582..6d6acd412 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/PillarboxPreloadManagerTest.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/PillarboxPreloadManagerTest.kt @@ -30,14 +30,14 @@ class PillarboxPreloadManagerTest { createdPlayersCount = 0 preloadManager = PillarboxPreloadManager( context = context, - playerPool = PlayerPool( - playersCount = PLAYERS_COUNT, - playerFactory = { - createdPlayersCount++ + playerFactory = { playbackLooper -> + createdPlayersCount++ - PillarboxExoPlayer(context) - } - ) + PillarboxExoPlayer( + context = context, + playbackLooper = playbackLooper, + ) + }, ) } @@ -104,8 +104,6 @@ class PillarboxPreloadManagerTest { } private companion object { - private const val PLAYERS_COUNT = 3 - private val VOD1 = MediaItem.fromUri("urn:rts:video:13444390") private val VOD2 = MediaItem.fromUri("urn:rts:video:13444333") private val VOD3 = MediaItem.fromUri("urn:rts:video:13444466")