Skip to content

Commit

Permalink
Remove Player management from PIllarboxPreloadManager
Browse files Browse the repository at this point in the history
  • Loading branch information
StaehliJ committed Oct 10, 2024
1 parent 399be6e commit 7b4e09b
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 158 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,22 @@
package ch.srgssr.pillarbox.demo.ui.showcases.layouts

import android.app.Application
import android.os.HandlerThread
import android.os.Process
import android.util.SparseArray
import androidx.core.util.forEach
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.DefaultRendererCapabilitiesList
import androidx.media3.exoplayer.source.MediaSource
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_LOADED_TO_POSITION_MS
import androidx.media3.exoplayer.source.preload.TargetPreloadStatusControl
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.player.PillarboxBandwidthMeter
import ch.srgssr.pillarbox.player.PillarboxExoPlayer
import ch.srgssr.pillarbox.player.PillarboxLoadControl
import ch.srgssr.pillarbox.player.PillarboxRenderersFactory
import ch.srgssr.pillarbox.player.PillarboxPreloadManager
import ch.srgssr.pillarbox.player.PillarboxTrackSelector
import ch.srgssr.pillarbox.player.source.PillarboxMediaSourceFactory
import kotlin.math.abs
Expand All @@ -38,37 +32,31 @@ import kotlin.time.Duration.Companion.seconds
*/
class StoryViewModel(application: Application) : AndroidViewModel(application) {

private val playbackThread: HandlerThread = HandlerThread("StoryMode-playback", Process.THREAD_PRIORITY_AUDIO).apply {
start()
}
private val playbackLooper = playbackThread.looper
private val allocator = DefaultAllocator(false, C.DEFAULT_BUFFER_SEGMENT_SIZE)
private val mediaSourceFactory = PillarboxMediaSourceFactory(application).apply {
addAssetLoader(SRGAssetLoader(application))
}
private val preloadManager =
PillarboxPreloadManager(
context = application,
targetPreloadStatusControl = StoryPreloadStatusControl(),
mediaSourceFactory = mediaSourceFactory,
trackSelector = PillarboxTrackSelector(application).apply {
parameters = parameters.buildUpon()
.setForceLowestBitrate(true)
.build()
}
)

private val loadControl = PillarboxLoadControl(
bufferDurations = PillarboxLoadControl.BufferDurations(
minBufferDuration = 5.seconds,
maxBufferDuration = 20.seconds,
bufferForPlayback = 500.milliseconds,
bufferForPlaybackAfterRebuffer = 1_000.milliseconds,
),
allocator
preloadManager.allocator
)

private val preloadManager =
DefaultPreloadManager(
StoryPreloadStatusControl(),
mediaSourceFactory,
PillarboxTrackSelector(application).apply {
init({}, PillarboxBandwidthMeter(application))
},
PillarboxBandwidthMeter(application),
DefaultRendererCapabilitiesList.Factory(PillarboxRenderersFactory(application)),
allocator,
playbackLooper,
)

private var currentPage = C.INDEX_UNSET

private val players = SparseArray<PillarboxExoPlayer>(3).apply {
Expand All @@ -77,7 +65,7 @@ class StoryViewModel(application: Application) : AndroidViewModel(application) {
i,
PillarboxExoPlayer(
context = application,
playbackLooper = playbackLooper,
playbackLooper = preloadManager.playbackLooper,
loadControl = loadControl
).apply {
repeatMode = Player.REPEAT_MODE_ONE
Expand All @@ -102,7 +90,7 @@ class StoryViewModel(application: Application) : AndroidViewModel(application) {
mediaItems.forEachIndexed { index, mediaItem ->
preloadManager.add(mediaItem, index)
}
preloadManager.setCurrentPlayingIndex(0)
preloadManager.currentPlayingIndex = 0
preloadManager.invalidate()

players.forEach { key, _ -> setupPlayerForPage(key) }
Expand Down Expand Up @@ -141,7 +129,7 @@ class StoryViewModel(application: Application) : AndroidViewModel(application) {
fun setCurrentPage(page: Int) {
if (currentPage == page) return
currentPage = page
preloadManager.setCurrentPlayingIndex(currentPage)
preloadManager.currentPlayingIndex = currentPage
preloadManager.invalidate()
}

Expand All @@ -162,16 +150,15 @@ class StoryViewModel(application: Application) : AndroidViewModel(application) {
}

override fun onCleared() {
preloadManager.release()
players.forEach { _, value ->
value.release()
}
playbackThread.quitSafely()
preloadManager.release()
}

/**
* Default implementation of [TargetPreloadStatusControl] that will preload the first second of the `n ± 1` item, and the first half-second of
* the `n ± 2,3` item, where `n` is the index of the current item.
* the `n ± 2,3,4` item, where `n` is the index of the current item.
*/
@Suppress("MagicNumber")
inner class StoryPreloadStatusControl : TargetPreloadStatusControl<Int> {
Expand All @@ -180,7 +167,7 @@ class StoryViewModel(application: Application) : AndroidViewModel(application) {

return when (offset) {
1 -> Status(STAGE_LOADED_TO_POSITION_MS, 1.seconds.inWholeMicroseconds)
2, 3, 4 -> Status(STAGE_LOADED_TO_POSITION_MS, 500.milliseconds.inWholeMicroseconds)
2, 3, 4 -> Status(STAGE_LOADED_TO_POSITION_MS, 1.milliseconds.inWholeMicroseconds)
else -> null
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import android.os.Process
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.DefaultRendererCapabilitiesList
import androidx.media3.exoplayer.LoadControl
import androidx.media3.exoplayer.RendererCapabilitiesList
import androidx.media3.exoplayer.source.MediaSource
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_LOADED_TO_POSITION_MS
import androidx.media3.exoplayer.source.preload.TargetPreloadStatusControl
import androidx.media3.exoplayer.trackselection.TrackSelector
import androidx.media3.exoplayer.upstream.Allocator
import androidx.media3.exoplayer.upstream.BandwidthMeter
import androidx.media3.exoplayer.upstream.DefaultAllocator
import ch.srgssr.pillarbox.player.source.PillarboxMediaSourceFactory
Expand All @@ -27,72 +27,32 @@ import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

/**
* Helper class for the Media3's [DefaultPreloadManager]. The main difference between this class and [DefaultPreloadManager] is the addition of the
* [PlayerPool] argument. It allows the dynamic creation of a fixed number of [PillarboxExoPlayer] instances.
*
* This class provides the same methods as [DefaultPreloadManager] plus [getPlayer] and [getCurrentlyPlayingPlayer] to get an instance of a
* [PillarboxExoPlayer].
* Helper class for the Media3's [DefaultPreloadManager].
*
* @param context The current [Context].
* @param targetPreloadStatusControl The [TargetPreloadStatusControl] to decide when to preload an item and for how long.
* @param mediaSourceFactory The [MediaSource.Factory] to create each [MediaSource].
* @param mediaSourceFactory The [PillarboxMediaSourceFactory] to create each [MediaSource].
* @param trackSelector The [TrackSelector] for this preload manager.
* @param bandwidthMeter The [BandwidthMeter] for this preload manager.
* @param rendererCapabilitiesListFactory The [RendererCapabilitiesList.Factory] for this preload manager.
* @param loadControl The [LoadControl] for this preload manager.
* @property allocator The [Allocator] for this preload manager. Have to be the same as the one used by the Player.
* @param playbackThread The [Thread] on which the players run.
* @param playersCount The maximum number of [PillarboxExoPlayer] to create.
* @param playerFactory Called when a new [PillarboxExoPlayer] instance is necessary (up to `playersCount` times). The provided `Looper` **must**
* be passed to [PillarboxExoPlayer]'s constructor.
*
* @see DefaultPreloadManager
*/
class PillarboxPreloadManager(
context: Context,
targetPreloadStatusControl: TargetPreloadStatusControl<Int>? = null,
mediaSourceFactory: MediaSource.Factory = PillarboxMediaSourceFactory(context),
trackSelector: TrackSelector = PillarboxTrackSelector(context).apply {
init({}, PillarboxBandwidthMeter(context))
},
mediaSourceFactory: PillarboxMediaSourceFactory = PillarboxMediaSourceFactory(context),
trackSelector: TrackSelector = PillarboxTrackSelector(context),
bandwidthMeter: BandwidthMeter = PillarboxBandwidthMeter(context),
rendererCapabilitiesListFactory: RendererCapabilitiesList.Factory = DefaultRendererCapabilitiesList.Factory(
PillarboxRenderersFactory(context)
),
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),
),
val allocator: DefaultAllocator = 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 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,
)
}
private val preloadManager: DefaultPreloadManager

/**
* The index of the currently playing media item.
Expand All @@ -113,8 +73,24 @@ class PillarboxPreloadManager(
val sourceCount: Int
get() = preloadManager.sourceCount

/**
* Playback looper to use with PillarboxExoPlayer.
*/
val playbackLooper: Looper

init {
playbackThread.start()
playbackLooper = playbackThread.looper
trackSelector.init({}, bandwidthMeter)
preloadManager = DefaultPreloadManager(
targetPreloadStatusControl ?: DefaultTargetPreloadStatusControl(),
mediaSourceFactory,
trackSelector,
bandwidthMeter,
rendererCapabilitiesListFactory,
allocator,
playbackLooper,
)
}

/**
Expand Down Expand Up @@ -166,9 +142,8 @@ class PillarboxPreloadManager(
* @see DefaultPreloadManager.release
*/
fun release() {
playerPool.release()
preloadManager.release()
playbackThread.quit()
playbackThread.quitSafely()
}

/**
Expand Down Expand Up @@ -202,25 +177,6 @@ class PillarboxPreloadManager(
preloadManager.reset()
}

/**
* Get a [PillarboxExoPlayer] for the given [index]. If the desired player has not been created yet, [PlayerPool.playerFactory] will be called.
*
* @param index The index of the [PillarboxExoPlayer] to retrieve.
* @return The desired [PillarboxExoPlayer], or `null` if [index] is negative.
*/
fun getPlayer(index: Int): PillarboxExoPlayer? {
return playerPool.getPlayerAtPosition(index)
}

/**
* Get the currently playing [PillarboxExoPlayer].
*
* @return The currently playing [PillarboxExoPlayer], or `null` if there is no active player.
*/
fun getCurrentlyPlayingPlayer(): PillarboxExoPlayer? {
return getPlayer(currentPlayingIndex)
}

/**
* Default implementation of [TargetPreloadStatusControl] that will preload the first second of the `n ± 1` item, and the first half-second of
* the `n ± 2,3` item, where `n` is the index of the current item.
Expand Down

This file was deleted.

0 comments on commit 7b4e09b

Please sign in to comment.