From a1b259ec2e022bbce7a4fc17e9e22b3639ec73ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Fri, 5 Jan 2024 16:39:25 +0100 Subject: [PATCH 01/21] Move smooth seeking to PillarboxPlayer --- .../ui/player/controls/PlayerTimeSlider.kt | 3 +- .../ch/srgssr/pillarbox/player/Pillarbox.kt | 33 ++++++ .../pillarbox/player/PillarboxPlayer.kt | 107 +++++++++++++++++- .../ui/SmoothProgressTrackerState.kt | 54 +-------- 4 files changed, 144 insertions(+), 53 deletions(-) create mode 100644 pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/Pillarbox.kt diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/controls/PlayerTimeSlider.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/controls/PlayerTimeSlider.kt index 697c53930..c98508382 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/controls/PlayerTimeSlider.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/controls/PlayerTimeSlider.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.media3.common.Player +import ch.srgssr.pillarbox.player.Pillarbox import ch.srgssr.pillarbox.player.extension.canSeek import ch.srgssr.pillarbox.ui.ProgressTrackerState import ch.srgssr.pillarbox.ui.SimpleProgressTrackerState @@ -41,7 +42,7 @@ fun rememberProgressTrackerState( coroutineScope: CoroutineScope = rememberCoroutineScope() ): ProgressTrackerState { return remember(player, smoothTracker) { - if (smoothTracker) { + if (smoothTracker && player is Pillarbox) { SmoothProgressTrackerState(player, coroutineScope) } else { SimpleProgressTrackerState(player, coroutineScope) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/Pillarbox.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/Pillarbox.kt new file mode 100644 index 000000000..06b00af34 --- /dev/null +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/Pillarbox.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.player + +import androidx.media3.common.Player +import androidx.media3.exoplayer.ExoPlayer + +/** + * Pillarbox [ExoPlayer] interface extension. + */ +interface Pillarbox : ExoPlayer { + + /** + * Listener + */ + interface Listener : Player.Listener { + /** + * On smooth seeking enabled changed + * + * @param smoothSeekingEnabled The new value of [smoothSeekingEnabled] + */ + fun onSmoothSeekingEnabledChanged(smoothSeekingEnabled: Boolean) + } + + /** + * Smooth seeking enabled + * + * When [smoothSeekingEnabled] is enabled, next seek event is send only after the current is done. + */ + var smoothSeekingEnabled: Boolean +} diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt index f008a174b..94f821778 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt @@ -5,6 +5,7 @@ package ch.srgssr.pillarbox.player import android.content.Context +import androidx.media3.common.C import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackParameters import androidx.media3.common.Player @@ -41,9 +42,29 @@ class PillarboxPlayer internal constructor( private val exoPlayer: ExoPlayer, mediaItemTrackerProvider: MediaItemTrackerProvider? ) : - ExoPlayer by exoPlayer { + ExoPlayer by exoPlayer, Pillarbox { + private val listeners = HashSet() private val itemTracker: CurrentMediaItemTracker? private val window = Window() + override var smoothSeekingEnabled: Boolean = false + set(value) { + if (value != field) { + field = value + if (!value) { + seekToPending() + } + clearSeeking() + val listeners = HashSet(listeners) + for (listener in listeners) { + listener.onSmoothSeekingEnabledChanged(value) + } + } + } + private var playerIsSeeking: Boolean = false + get() { + return field && smoothSeekingEnabled + } + private var pendingSeek: Long? = null /** * Enable or disable MediaItem tracking @@ -53,13 +74,14 @@ class PillarboxPlayer internal constructor( get() = itemTracker?.enabled ?: false init { - addListener(ComponentListener()) + exoPlayer.addListener(ComponentListener()) itemTracker = mediaItemTrackerProvider?.let { CurrentMediaItemTracker(this, it) } if (BuildConfig.DEBUG) { addAnalyticsListener(EventLogger()) } + exoPlayer.videoChangeFrameRateStrategy = C.VIDEO_CHANGE_FRAME_RATE_STRATEGY_OFF } constructor( @@ -99,6 +121,40 @@ class PillarboxPlayer internal constructor( mediaItemTrackerProvider = mediaItemTrackerProvider ) + override fun addListener(listener: Player.Listener) { + exoPlayer.addListener(listener) + if (listener is Pillarbox.Listener) { + listeners.add(listener) + } + } + + override fun removeListener(listener: Player.Listener) { + exoPlayer.removeListener(listener) + if (listener is Pillarbox.Listener) { + listeners.remove(listener) + } + } + + override fun seekTo(positionMs: Long) { + if (playerIsSeeking) { + pendingSeek = positionMs + return + } + exoPlayer.seekTo(positionMs) + } + + override fun seekTo(mediaItemIndex: Int, positionMs: Long) { + if (mediaItemIndex != currentMediaItemIndex) { + exoPlayer.seekTo(mediaItemIndex, positionMs) + return + } + if (playerIsSeeking) { + pendingSeek = positionMs + return + } + exoPlayer.seekTo(mediaItemIndex, positionMs) + } + /** * Releases the player. * This method must be called when the player is no longer required. The player must not be used after calling this method. @@ -106,6 +162,7 @@ class PillarboxPlayer internal constructor( * Release call automatically [stop] if the player is not in [Player.STATE_IDLE]. */ override fun release() { + clearSeeking() if (playbackState != Player.STATE_IDLE) { stop() } @@ -132,10 +189,56 @@ class PillarboxPlayer internal constructor( playbackParameters = playbackParameters.withSpeed(speed) } + private fun seekToPending() { + pendingSeek?.let { pendingPosition -> + exoPlayer.seekTo(pendingPosition) + pendingSeek = null + } + } + + private fun clearSeeking() { + pendingSeek = null + playerIsSeeking = false + } + private inner class ComponentListener : Player.Listener { private val window = Window() + override fun onPositionDiscontinuity( + oldPosition: Player.PositionInfo, + newPosition: Player.PositionInfo, + reason: Int + ) { + when (reason) { + Player.DISCONTINUITY_REASON_SEEK, Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT -> { + playerIsSeeking = true + } + + else -> { + clearSeeking() + } + } + } + + override fun onPlaybackStateChanged(playbackState: Int) { + when (playbackState) { + Player.STATE_READY -> { + seekToPending() + playerIsSeeking = false + } + + Player.STATE_IDLE, Player.STATE_ENDED -> { + clearSeeking() + } + + Player.STATE_BUFFERING -> { + // Do nothing + } + } + } + override fun onPlayerError(error: PlaybackException) { + clearSeeking() if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) { setPlaybackSpeed(NormalSpeed) seekToDefaultPosition() diff --git a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt index 3ef099ba7..eb9df6a38 100644 --- a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt +++ b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt @@ -6,12 +6,10 @@ package ch.srgssr.pillarbox.ui import androidx.media3.common.C import androidx.media3.common.Player +import ch.srgssr.pillarbox.player.Pillarbox import ch.srgssr.pillarbox.player.extension.getPlaybackSpeed import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.launchIn import kotlin.time.Duration /** @@ -21,38 +19,11 @@ import kotlin.time.Duration * @param coroutineScope */ class SmoothProgressTrackerState( - private val player: Player, + private val player: Pillarbox, coroutineScope: CoroutineScope ) : ProgressTrackerState { - private val playerSeekState = callbackFlow { - val listener = object : Player.Listener { - override fun onPositionDiscontinuity( - oldPosition: Player.PositionInfo, - newPosition: Player.PositionInfo, - reason: Int - ) { - if (reason == Player.DISCONTINUITY_REASON_SEEK || reason == Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT) { - isSeeking = true - } - } - - override fun onPlaybackStateChanged(playbackState: Int) { - if (playbackState == Player.STATE_READY && isSeeking) { - seekToPending() - isSeeking = false - } - } - } - player.addListener(listener) - awaitClose { - player.removeListener(listener) - } - } private val simpleProgressTrackerState = SimpleProgressTrackerState(player, coroutineScope) - - private var isSeeking = false - private var pendingSeek: Duration? = null private var startChanging = false private var storedPlaybackSpeed = player.getPlaybackSpeed() @@ -61,18 +32,10 @@ class SmoothProgressTrackerState( override val progress: StateFlow = simpleProgressTrackerState.progress - init { - playerSeekState.launchIn(coroutineScope) - } - override fun onChanged(progress: Duration) { simpleProgressTrackerState.onChanged(progress) - if (isSeeking) { - pendingSeek = progress - return - } - if (!startChanging) { + player.smoothSeekingEnabled = true startChanging = true storedPlayWhenReady = player.playWhenReady storedTrackSelectionParameters = player.trackSelectionParameters @@ -95,17 +58,8 @@ class SmoothProgressTrackerState( player.playWhenReady = storedPlayWhenReady player.trackSelectionParameters = storedTrackSelectionParameters player.setPlaybackSpeed(storedPlaybackSpeed) - - isSeeking = false - pendingSeek = null startChanging = false - } - - private fun seekToPending() { - pendingSeek?.let { - player.seekTo(it.inWholeMilliseconds) - pendingSeek = null - } + player.smoothSeekingEnabled = false } private companion object { From 080532212a628f017934fbc387a69dc934cc23c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 9 Jan 2024 10:48:38 +0100 Subject: [PATCH 02/21] Setup playback speed for trick play to 16 --- .../java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt index eb9df6a38..1f913ba9b 100644 --- a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt +++ b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt @@ -39,7 +39,6 @@ class SmoothProgressTrackerState( startChanging = true storedPlayWhenReady = player.playWhenReady storedTrackSelectionParameters = player.trackSelectionParameters - player.playWhenReady = false player.trackSelectionParameters = player.trackSelectionParameters.buildUpon() .setPreferredVideoRoleFlags(C.ROLE_FLAG_TRICK_PLAY) @@ -54,7 +53,6 @@ class SmoothProgressTrackerState( override fun onFinished() { simpleProgressTrackerState.onFinished() - player.playWhenReady = storedPlayWhenReady player.trackSelectionParameters = storedTrackSelectionParameters player.setPlaybackSpeed(storedPlaybackSpeed) @@ -63,6 +61,6 @@ class SmoothProgressTrackerState( } private companion object { - private const val SEEKING_PLAYBACK_SPEED = Float.MAX_VALUE + private const val SEEKING_PLAYBACK_SPEED = 16f } } From b1ca90b74aca9901b7f9751cb327c31df362352f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 9 Jan 2024 14:30:38 +0100 Subject: [PATCH 03/21] Improve smooth seeking --- .../pillarbox/demo/shared/di/PlayerModule.kt | 7 +- .../pillarbox/player/PillarboxPlayer.kt | 82 +++++++++++++------ .../ui/SmoothProgressTrackerState.kt | 16 ---- 3 files changed, 57 insertions(+), 48 deletions(-) diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/di/PlayerModule.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/di/PlayerModule.kt index 02811c8dd..cc23f3f03 100644 --- a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/di/PlayerModule.kt +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/di/PlayerModule.kt @@ -5,7 +5,6 @@ package ch.srgssr.pillarbox.demo.shared.di import android.content.Context -import androidx.media3.exoplayer.SeekParameters import ch.srg.dataProvider.integrationlayer.dependencies.modules.IlServiceModule import ch.srg.dataProvider.integrationlayer.dependencies.modules.OkHttpModule import ch.srgssr.dataprovider.paging.DataProviderPaging @@ -16,7 +15,6 @@ import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost import ch.srgssr.pillarbox.core.business.integrationlayer.service.Vector.getVector import ch.srgssr.pillarbox.demo.shared.data.MixedMediaItemSource import ch.srgssr.pillarbox.demo.shared.ui.integrationLayer.data.ILRepository -import ch.srgssr.pillarbox.player.PillarboxLoadControl import ch.srgssr.pillarbox.player.PillarboxPlayer import java.net.URL @@ -47,10 +45,7 @@ object PlayerModule { return DefaultPillarbox( context = context, mediaItemSource = provideMixedItemSource(context, ilHost), - loadControl = PillarboxLoadControl(smoothSeeking = true) - ).apply { - setSeekParameters(SeekParameters.CLOSEST_SYNC) - } + ) } /** diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt index 94f821778..634434297 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt @@ -6,6 +6,7 @@ package ch.srgssr.pillarbox.player import android.content.Context import androidx.media3.common.C +import androidx.media3.common.MediaItem import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackParameters import androidx.media3.common.Player @@ -17,6 +18,7 @@ import androidx.media3.exoplayer.DefaultLoadControl import androidx.media3.exoplayer.DefaultRenderersFactory import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.LoadControl +import androidx.media3.exoplayer.SeekParameters import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter @@ -50,8 +52,11 @@ class PillarboxPlayer internal constructor( set(value) { if (value != field) { field = value - if (!value) { - seekToPending() + if (value) { + exoPlayer.setSeekParameters(SeekParameters.CLOSEST_SYNC) + } else { + seekEnd() + exoPlayer.setSeekParameters(pendingSeekParameters) } clearSeeking() val listeners = HashSet(listeners) @@ -60,11 +65,9 @@ class PillarboxPlayer internal constructor( } } } - private var playerIsSeeking: Boolean = false - get() { - return field && smoothSeekingEnabled - } private var pendingSeek: Long? = null + private var isSeeking: Boolean = false + private var pendingSeekParameters: SeekParameters = exoPlayer.seekParameters /** * Enable or disable MediaItem tracking @@ -135,20 +138,53 @@ class PillarboxPlayer internal constructor( } } + override fun setSeekParameters(seekParameters: SeekParameters?) { + if (smoothSeekingEnabled) { + pendingSeekParameters = seekParameters ?: SeekParameters.DEFAULT + } else { + exoPlayer.setSeekParameters(seekParameters) + } + } + + override fun getSeekParameters(): SeekParameters { + if (smoothSeekingEnabled) { + return pendingSeekParameters + } + return exoPlayer.seekParameters + } + override fun seekTo(positionMs: Long) { - if (playerIsSeeking) { + if (!smoothSeekingEnabled) { + exoPlayer.seekTo(positionMs) + return + } + smoothSeekTo(positionMs) + } + + private fun smoothSeekTo(positionMs: Long) { + if (isSeeking) { pendingSeek = positionMs return } + isSeeking = true exoPlayer.seekTo(positionMs) } override fun seekTo(mediaItemIndex: Int, positionMs: Long) { + if (!smoothSeekingEnabled) { + exoPlayer.seekTo(mediaItemIndex, positionMs) + return + } + smoothSeekTo(mediaItemIndex, positionMs) + } + + private fun smoothSeekTo(mediaItemIndex: Int, positionMs: Long) { if (mediaItemIndex != currentMediaItemIndex) { + clearSeeking() exoPlayer.seekTo(mediaItemIndex, positionMs) return } - if (playerIsSeeking) { + if (isSeeking) { pendingSeek = positionMs return } @@ -189,42 +225,36 @@ class PillarboxPlayer internal constructor( playbackParameters = playbackParameters.withSpeed(speed) } - private fun seekToPending() { + private fun seekEnd() { + isSeeking = false pendingSeek?.let { pendingPosition -> - exoPlayer.seekTo(pendingPosition) pendingSeek = null + seekTo(pendingPosition) } } private fun clearSeeking() { + isSeeking = false pendingSeek = null - playerIsSeeking = false } private inner class ComponentListener : Player.Listener { private val window = Window() - override fun onPositionDiscontinuity( - oldPosition: Player.PositionInfo, - newPosition: Player.PositionInfo, - reason: Int - ) { - when (reason) { - Player.DISCONTINUITY_REASON_SEEK, Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT -> { - playerIsSeeking = true - } + override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { + clearSeeking() + } - else -> { - clearSeeking() - } - } + override fun onRenderedFirstFrame() { + seekEnd() } override fun onPlaybackStateChanged(playbackState: Int) { when (playbackState) { Player.STATE_READY -> { - seekToPending() - playerIsSeeking = false + if (isSeeking) { + seekEnd() + } } Player.STATE_IDLE, Player.STATE_ENDED -> { diff --git a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt index 1f913ba9b..6fca60b13 100644 --- a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt +++ b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt @@ -4,10 +4,8 @@ */ package ch.srgssr.pillarbox.ui -import androidx.media3.common.C import androidx.media3.common.Player import ch.srgssr.pillarbox.player.Pillarbox -import ch.srgssr.pillarbox.player.extension.getPlaybackSpeed import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import kotlin.time.Duration @@ -25,11 +23,7 @@ class SmoothProgressTrackerState( private val simpleProgressTrackerState = SimpleProgressTrackerState(player, coroutineScope) private var startChanging = false - - private var storedPlaybackSpeed = player.getPlaybackSpeed() private var storedPlayWhenReady = player.playWhenReady - private var storedTrackSelectionParameters = player.trackSelectionParameters - override val progress: StateFlow = simpleProgressTrackerState.progress override fun onChanged(progress: Duration) { @@ -38,24 +32,14 @@ class SmoothProgressTrackerState( player.smoothSeekingEnabled = true startChanging = true storedPlayWhenReady = player.playWhenReady - storedTrackSelectionParameters = player.trackSelectionParameters player.playWhenReady = false - player.trackSelectionParameters = player.trackSelectionParameters.buildUpon() - .setPreferredVideoRoleFlags(C.ROLE_FLAG_TRICK_PLAY) - .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true) - .setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, true) - .build() - player.setPlaybackSpeed(SEEKING_PLAYBACK_SPEED) } - player.seekTo(progress.inWholeMilliseconds) } override fun onFinished() { simpleProgressTrackerState.onFinished() player.playWhenReady = storedPlayWhenReady - player.trackSelectionParameters = storedTrackSelectionParameters - player.setPlaybackSpeed(storedPlaybackSpeed) startChanging = false player.smoothSeekingEnabled = false } From b064993b4bd598f1d693cc2621f3a637869f09b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 9 Jan 2024 14:31:03 +0100 Subject: [PATCH 04/21] Make experimental trick play experimentations --- .../pillarbox/player/PillarboxLoadControl.kt | 2 + .../pillarbox/ui/TrickPlayExperimental.kt | 66 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/TrickPlayExperimental.kt diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt index 9fbc55f98..1c5ede11f 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt @@ -19,6 +19,8 @@ import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds /** + * Experimental + * * Pillarbox [LoadControl] implementation that optimize content loading for smooth seeking. * * @param bufferDurations Buffer duration when [smoothSeeking] is not enabled. diff --git a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/TrickPlayExperimental.kt b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/TrickPlayExperimental.kt new file mode 100644 index 000000000..209371220 --- /dev/null +++ b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/TrickPlayExperimental.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.ui + +import androidx.media3.common.C +import androidx.media3.common.Player +import ch.srgssr.pillarbox.player.Pillarbox +import ch.srgssr.pillarbox.player.extension.getPlaybackSpeed +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import kotlin.time.Duration + +/** + * [Player] progress tracker that updates the player's actual progress everytime that [onChanged] is called. + * + * @param player The [Player] whose current position must be tracked. + * @param coroutineScope + */ +class TrickPlayExperimental( + private val player: Pillarbox, + coroutineScope: CoroutineScope +) : ProgressTrackerState { + + private val simpleProgressTrackerState = SimpleProgressTrackerState(player, coroutineScope) + private var startChanging = false + + private var storedPlaybackSpeed = player.getPlaybackSpeed() + private var storedPlayWhenReady = player.playWhenReady + private var storedTrackSelectionParameters = player.trackSelectionParameters + + override val progress: StateFlow = simpleProgressTrackerState.progress + + override fun onChanged(progress: Duration) { + simpleProgressTrackerState.onChanged(progress) + if (!startChanging) { + player.smoothSeekingEnabled = true + startChanging = true + storedPlayWhenReady = player.playWhenReady + storedTrackSelectionParameters = player.trackSelectionParameters + player.playWhenReady = false + + player.trackSelectionParameters = player.trackSelectionParameters.buildUpon() + .setPreferredVideoRoleFlags(C.ROLE_FLAG_TRICK_PLAY) + .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true) + .setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, true) + .build() + player.setPlaybackSpeed(SEEKING_PLAYBACK_SPEED) + } + player.seekTo(progress.inWholeMilliseconds) + } + + override fun onFinished() { + simpleProgressTrackerState.onFinished() + player.playWhenReady = storedPlayWhenReady + player.trackSelectionParameters = storedTrackSelectionParameters + player.setPlaybackSpeed(storedPlaybackSpeed) + startChanging = false + player.smoothSeekingEnabled = false + } + + private companion object { + private const val SEEKING_PLAYBACK_SPEED = 16f + } +} From 42cb9d23ce7607dacd1daf2933631e7c427bbeb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 9 Jan 2024 14:38:09 +0100 Subject: [PATCH 05/21] Hide loading indicator while dragging the slider --- .../main/java/ch/srgssr/pillarbox/demo/ui/player/PlayerView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/PlayerView.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/PlayerView.kt index c6c5666b2..3c599820b 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/PlayerView.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/PlayerView.kt @@ -96,7 +96,7 @@ fun PlayerView( player = player, scaleMode = scaleMode ) { - if (isBuffering) { + if (isBuffering && !isSliderDragged) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator(modifier = Modifier.align(Alignment.Center), color = Color.White) } From 8bc7c5d73196eda23782cb94f90b7c7440ea7cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 9 Jan 2024 15:05:18 +0100 Subject: [PATCH 06/21] Don't change de SeekParameters when enable smoothseeking --- .../ch/srgssr/pillarbox/player/Pillarbox.kt | 5 +++++ .../pillarbox/player/PillarboxPlayer.kt | 22 +------------------ .../ui/SmoothProgressTrackerState.kt | 5 ++++- 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/Pillarbox.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/Pillarbox.kt index 06b00af34..78c15718d 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/Pillarbox.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/Pillarbox.kt @@ -6,6 +6,7 @@ package ch.srgssr.pillarbox.player import androidx.media3.common.Player import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.SeekParameters /** * Pillarbox [ExoPlayer] interface extension. @@ -28,6 +29,10 @@ interface Pillarbox : ExoPlayer { * Smooth seeking enabled * * When [smoothSeekingEnabled] is enabled, next seek event is send only after the current is done. + * + * To have the best result it is important to + * 1) Pause the player while seeking + * 2) Set the [ExoPlayer.setSeekParameters] to [SeekParameters.CLOSEST_SYNC]. */ var smoothSeekingEnabled: Boolean } diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt index 634434297..119495413 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt @@ -18,7 +18,6 @@ import androidx.media3.exoplayer.DefaultLoadControl import androidx.media3.exoplayer.DefaultRenderersFactory import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.LoadControl -import androidx.media3.exoplayer.SeekParameters import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter @@ -52,11 +51,8 @@ class PillarboxPlayer internal constructor( set(value) { if (value != field) { field = value - if (value) { - exoPlayer.setSeekParameters(SeekParameters.CLOSEST_SYNC) - } else { + if (!value) { seekEnd() - exoPlayer.setSeekParameters(pendingSeekParameters) } clearSeeking() val listeners = HashSet(listeners) @@ -67,7 +63,6 @@ class PillarboxPlayer internal constructor( } private var pendingSeek: Long? = null private var isSeeking: Boolean = false - private var pendingSeekParameters: SeekParameters = exoPlayer.seekParameters /** * Enable or disable MediaItem tracking @@ -138,21 +133,6 @@ class PillarboxPlayer internal constructor( } } - override fun setSeekParameters(seekParameters: SeekParameters?) { - if (smoothSeekingEnabled) { - pendingSeekParameters = seekParameters ?: SeekParameters.DEFAULT - } else { - exoPlayer.setSeekParameters(seekParameters) - } - } - - override fun getSeekParameters(): SeekParameters { - if (smoothSeekingEnabled) { - return pendingSeekParameters - } - return exoPlayer.seekParameters - } - override fun seekTo(positionMs: Long) { if (!smoothSeekingEnabled) { exoPlayer.seekTo(positionMs) diff --git a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt index 6fca60b13..19452f29d 100644 --- a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt +++ b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt @@ -5,6 +5,7 @@ package ch.srgssr.pillarbox.ui import androidx.media3.common.Player +import androidx.media3.exoplayer.SeekParameters import ch.srgssr.pillarbox.player.Pillarbox import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow @@ -20,7 +21,7 @@ class SmoothProgressTrackerState( private val player: Pillarbox, coroutineScope: CoroutineScope ) : ProgressTrackerState { - + private var initialSeekParameters = player.seekParameters private val simpleProgressTrackerState = SimpleProgressTrackerState(player, coroutineScope) private var startChanging = false private var storedPlayWhenReady = player.playWhenReady @@ -29,6 +30,7 @@ class SmoothProgressTrackerState( override fun onChanged(progress: Duration) { simpleProgressTrackerState.onChanged(progress) if (!startChanging) { + player.setSeekParameters(SeekParameters.CLOSEST_SYNC) player.smoothSeekingEnabled = true startChanging = true storedPlayWhenReady = player.playWhenReady @@ -42,6 +44,7 @@ class SmoothProgressTrackerState( player.playWhenReady = storedPlayWhenReady startChanging = false player.smoothSeekingEnabled = false + player.setSeekParameters(initialSeekParameters) } private companion object { From 93d3bf9542dd8ab449245a8868a29a526fd9bb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 9 Jan 2024 16:58:45 +0100 Subject: [PATCH 07/21] Create a default Pillarbox LoadControl --- .../core/business/DefaultPillarbox.kt | 3 +- .../pillarbox/player/PillarboxLoadControl.kt | 65 ++++++------------- .../pillarbox/player/PillarboxPlayer.kt | 3 +- 3 files changed, 24 insertions(+), 47 deletions(-) diff --git a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/DefaultPillarbox.kt b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/DefaultPillarbox.kt index 989afdb19..8ccdf93b2 100644 --- a/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/DefaultPillarbox.kt +++ b/pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/DefaultPillarbox.kt @@ -11,6 +11,7 @@ import androidx.media3.exoplayer.LoadControl import ch.srgssr.pillarbox.core.business.akamai.AkamaiTokenDataSource import ch.srgssr.pillarbox.core.business.integrationlayer.service.DefaultMediaCompositionDataSource import ch.srgssr.pillarbox.core.business.tracker.DefaultMediaItemTrackerRepository +import ch.srgssr.pillarbox.player.PillarboxLoadControl import ch.srgssr.pillarbox.player.PillarboxPlayer import ch.srgssr.pillarbox.player.SeekIncrement import ch.srgssr.pillarbox.player.data.MediaItemSource @@ -42,7 +43,7 @@ object DefaultPillarbox { mediaCompositionDataSource = DefaultMediaCompositionDataSource(), ), dataSourceFactory: DataSource.Factory = AkamaiTokenDataSource.Factory(), - loadControl: LoadControl = DefaultLoadControl(), + loadControl: LoadControl = PillarboxLoadControl(), ): PillarboxPlayer { return PillarboxPlayer( context = context, diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt index 1c5ede11f..e56eb20bf 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt @@ -16,50 +16,39 @@ import androidx.media3.exoplayer.upstream.Allocator import androidx.media3.exoplayer.upstream.DefaultAllocator import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds /** - * Experimental + * Pillarbox [LoadControl] implementation that optimize content loading. * - * Pillarbox [LoadControl] implementation that optimize content loading for smooth seeking. - * - * @param bufferDurations Buffer duration when [smoothSeeking] is not enabled. - * @property smoothSeeking If enabled, use an optimized [LoadControl]. + * @param bufferDurations Buffer durations to set [DefaultLoadControl.Builder.setBufferDurationsMs]. * @param allocator The [DefaultAllocator] to use in the internal [DefaultLoadControl]. */ class PillarboxLoadControl( - bufferDurations: BufferDurations = BufferDurations(), - var smoothSeeking: Boolean = false, + bufferDurations: BufferDurations = DEFAULT_BUFFER_DURATIONS, private val allocator: DefaultAllocator = DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE), ) : LoadControl { - private val fastSeekLoadControl: DefaultLoadControl = DefaultLoadControl.Builder() - .setAllocator(allocator) - .setDurations(FAST_SEEK_DURATIONS) - .setPrioritizeTimeOverSizeThresholds(true) - .build() private val defaultLoadControl: DefaultLoadControl = DefaultLoadControl.Builder() .setAllocator(allocator) - .setDurations(bufferDurations) + .setBufferDurationsMs( + bufferDurations.minBufferDuration.inWholeMilliseconds.toInt(), + bufferDurations.maxBufferDuration.inWholeMilliseconds.toInt(), + bufferDurations.bufferForPlayback.inWholeMilliseconds.toInt(), + bufferDurations.bufferForPlaybackAfterRebuffer.inWholeMilliseconds.toInt(), + ) .setPrioritizeTimeOverSizeThresholds(true) + .setBackBuffer(BACK_BUFFER_DURATION_MS, true) .build() - private val activeLoadControl: LoadControl - get() { - return if (smoothSeeking) fastSeekLoadControl else defaultLoadControl - } override fun onPrepared() { - fastSeekLoadControl.onPrepared() defaultLoadControl.onPrepared() } override fun onStopped() { - fastSeekLoadControl.onStopped() defaultLoadControl.onStopped() } override fun onReleased() { - fastSeekLoadControl.onReleased() defaultLoadControl.onReleased() } @@ -68,11 +57,11 @@ class PillarboxLoadControl( } override fun getBackBufferDurationUs(): Long { - return BACK_BUFFER_DURATION_MS + return defaultLoadControl.backBufferDurationUs } override fun retainBackBufferFromKeyframe(): Boolean { - return true + return defaultLoadControl.retainBackBufferFromKeyframe() } override fun shouldContinueLoading( @@ -80,7 +69,7 @@ class PillarboxLoadControl( bufferedDurationUs: Long, playbackSpeed: Float ): Boolean { - return activeLoadControl.shouldContinueLoading(playbackPositionUs, bufferedDurationUs, playbackSpeed) + return defaultLoadControl.shouldContinueLoading(playbackPositionUs, bufferedDurationUs, playbackSpeed) } override fun onTracksSelected( @@ -90,7 +79,6 @@ class PillarboxLoadControl( trackGroups: TrackGroupArray, trackSelections: Array ) { - fastSeekLoadControl.onTracksSelected(timeline, mediaPeriodId, renderers, trackGroups, trackSelections) defaultLoadControl.onTracksSelected(timeline, mediaPeriodId, renderers, trackGroups, trackSelections) } @@ -100,7 +88,6 @@ class PillarboxLoadControl( trackGroups: TrackGroupArray, trackSelections: Array ) { - fastSeekLoadControl.onTracksSelected(renderers, trackGroups, trackSelections) defaultLoadControl.onTracksSelected(renderers, trackGroups, trackSelections) } @@ -112,7 +99,7 @@ class PillarboxLoadControl( rebuffering: Boolean, targetLiveOffsetUs: Long ): Boolean { - return activeLoadControl.shouldStartPlayback(timeline, mediaPeriodId, bufferedDurationUs, playbackSpeed, rebuffering, targetLiveOffsetUs) + return defaultLoadControl.shouldStartPlayback(timeline, mediaPeriodId, bufferedDurationUs, playbackSpeed, rebuffering, targetLiveOffsetUs) } @Deprecated("Deprecated in Java") @@ -122,7 +109,7 @@ class PillarboxLoadControl( rebuffering: Boolean, targetLiveOffsetUs: Long ): Boolean { - return activeLoadControl.shouldStartPlayback(bufferedDurationUs, playbackSpeed, rebuffering, targetLiveOffsetUs) + return defaultLoadControl.shouldStartPlayback(bufferedDurationUs, playbackSpeed, rebuffering, targetLiveOffsetUs) } /** @@ -142,22 +129,12 @@ class PillarboxLoadControl( val bufferForPlaybackAfterRebuffer: Duration = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS.milliseconds, ) - private companion object SmoothLoadControl { - private const val BACK_BUFFER_DURATION_MS = 6_000L - private val FAST_SEEK_DURATIONS = BufferDurations( - minBufferDuration = 2.seconds, - maxBufferDuration = 2.seconds, - bufferForPlayback = 2.seconds, - bufferForPlaybackAfterRebuffer = 2.seconds, + private companion object { + private const val BACK_BUFFER_DURATION_MS = 4_000 + private val DEFAULT_BUFFER_DURATIONS = BufferDurations( + bufferForPlayback = 500L.milliseconds, + bufferForPlaybackAfterRebuffer = 1000L.milliseconds, + minBufferDuration = 1000L.milliseconds ) - - private fun DefaultLoadControl.Builder.setDurations(durations: BufferDurations): DefaultLoadControl.Builder { - return setBufferDurationsMs( - durations.minBufferDuration.inWholeMilliseconds.toInt(), - durations.maxBufferDuration.inWholeMilliseconds.toInt(), - durations.bufferForPlayback.inWholeMilliseconds.toInt(), - durations.bufferForPlaybackAfterRebuffer.inWholeMilliseconds.toInt(), - ) - } } } diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt index 119495413..737db3782 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt @@ -14,7 +14,6 @@ import androidx.media3.common.Timeline.Window import androidx.media3.common.TrackSelectionParameters import androidx.media3.datasource.DataSource import androidx.media3.datasource.DefaultHttpDataSource -import androidx.media3.exoplayer.DefaultLoadControl import androidx.media3.exoplayer.DefaultRenderersFactory import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.LoadControl @@ -86,7 +85,7 @@ class PillarboxPlayer internal constructor( context: Context, mediaItemSource: MediaItemSource, dataSourceFactory: DataSource.Factory = DefaultHttpDataSource.Factory(), - loadControl: LoadControl = DefaultLoadControl(), + loadControl: LoadControl = PillarboxLoadControl(), mediaItemTrackerProvider: MediaItemTrackerProvider = MediaItemTrackerRepository(), seekIncrement: SeekIncrement = SeekIncrement() ) : this( From a7aead30cbea988a43f5d602143766119a5b9394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 9 Jan 2024 16:59:18 +0100 Subject: [PATCH 08/21] Delete experimental code --- .../pillarbox/ui/TrickPlayExperimental.kt | 66 ------------------- 1 file changed, 66 deletions(-) delete mode 100644 pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/TrickPlayExperimental.kt diff --git a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/TrickPlayExperimental.kt b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/TrickPlayExperimental.kt deleted file mode 100644 index 209371220..000000000 --- a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/TrickPlayExperimental.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) SRG SSR. All rights reserved. - * License information is available from the LICENSE file. - */ -package ch.srgssr.pillarbox.ui - -import androidx.media3.common.C -import androidx.media3.common.Player -import ch.srgssr.pillarbox.player.Pillarbox -import ch.srgssr.pillarbox.player.extension.getPlaybackSpeed -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.StateFlow -import kotlin.time.Duration - -/** - * [Player] progress tracker that updates the player's actual progress everytime that [onChanged] is called. - * - * @param player The [Player] whose current position must be tracked. - * @param coroutineScope - */ -class TrickPlayExperimental( - private val player: Pillarbox, - coroutineScope: CoroutineScope -) : ProgressTrackerState { - - private val simpleProgressTrackerState = SimpleProgressTrackerState(player, coroutineScope) - private var startChanging = false - - private var storedPlaybackSpeed = player.getPlaybackSpeed() - private var storedPlayWhenReady = player.playWhenReady - private var storedTrackSelectionParameters = player.trackSelectionParameters - - override val progress: StateFlow = simpleProgressTrackerState.progress - - override fun onChanged(progress: Duration) { - simpleProgressTrackerState.onChanged(progress) - if (!startChanging) { - player.smoothSeekingEnabled = true - startChanging = true - storedPlayWhenReady = player.playWhenReady - storedTrackSelectionParameters = player.trackSelectionParameters - player.playWhenReady = false - - player.trackSelectionParameters = player.trackSelectionParameters.buildUpon() - .setPreferredVideoRoleFlags(C.ROLE_FLAG_TRICK_PLAY) - .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true) - .setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, true) - .build() - player.setPlaybackSpeed(SEEKING_PLAYBACK_SPEED) - } - player.seekTo(progress.inWholeMilliseconds) - } - - override fun onFinished() { - simpleProgressTrackerState.onFinished() - player.playWhenReady = storedPlayWhenReady - player.trackSelectionParameters = storedTrackSelectionParameters - player.setPlaybackSpeed(storedPlaybackSpeed) - startChanging = false - player.smoothSeekingEnabled = false - } - - private companion object { - private const val SEEKING_PLAYBACK_SPEED = 16f - } -} From 7281397c2e4bc947ef7f1d2037f8b7677ec8e6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 9 Jan 2024 16:59:36 +0100 Subject: [PATCH 09/21] Update smooth seeking show case --- .../showcases/misc/SmoothSeekingShowcase.kt | 34 ++++++++++--- .../showcases/misc/SmoothSeekingViewModel.kt | 51 ------------------- 2 files changed, 27 insertions(+), 58 deletions(-) delete mode 100644 pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/SmoothSeekingViewModel.kt diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/SmoothSeekingShowcase.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/SmoothSeekingShowcase.kt index 764878215..37d11ff49 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/SmoothSeekingShowcase.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/SmoothSeekingShowcase.kt @@ -8,7 +8,6 @@ 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.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -17,6 +16,7 @@ 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.mutableStateOf import androidx.compose.runtime.remember @@ -24,11 +24,14 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.LifecycleStartEffect -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.media3.common.Player +import ch.srgssr.pillarbox.core.business.DefaultPillarbox import ch.srgssr.pillarbox.demo.R +import ch.srgssr.pillarbox.demo.shared.data.DemoItem +import ch.srgssr.pillarbox.demo.shared.di.PlayerModule import ch.srgssr.pillarbox.demo.ui.player.controls.PlayerPlaybackRow import ch.srgssr.pillarbox.demo.ui.player.controls.PlayerTimeSlider import ch.srgssr.pillarbox.demo.ui.player.controls.rememberProgressTrackerState @@ -42,17 +45,35 @@ import ch.srgssr.pillarbox.ui.widget.player.PlayerSurface */ @Composable fun SmoothSeekingShowcase() { - val smoothSeekingViewModel: SmoothSeekingViewModel = viewModel() - val player = smoothSeekingViewModel.player + val context = LocalContext.current + val player = remember { + DefaultPillarbox( + context = context, + mediaItemSource = PlayerModule.provideMixedItemSource(context) + ).apply { + addMediaItem(DemoItem.UnifiedStreamingOnDemand_Dash_TrickPlay.toMediaItem()) + addMediaItem(DemoItem.UnifiedStreamingOnDemandTrickplay.toMediaItem()) + addMediaItem(DemoItem.UnifiedStreamingOnDemand_Dash_FragmentedMP4.toMediaItem()) + addMediaItem(DemoItem.OnDemandHLS.toMediaItem()) + addMediaItem(DemoItem.GoogleDashH265.toMediaItem()) + } + } + DisposableEffect(Unit) { + player.prepare() + player.play() + onDispose { + player.release() + } + } var smoothSeekingEnabled by remember { mutableStateOf(false) } Column { - Box(modifier = Modifier.aspectRatio(16 / 9f)) { + Box(modifier = Modifier) { val playbackState by player.playbackStateAsState() val isBuffering = playbackState == Player.STATE_BUFFERING - PlayerSurface(player = player) { + PlayerSurface(player = player, defaultAspectRatio = 16 / 9f) { if (isBuffering) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator(modifier = Modifier.align(Alignment.Center), color = Color.White) @@ -86,7 +107,6 @@ fun SmoothSeekingShowcase() { checked = smoothSeekingEnabled, onCheckedChange = { enabled -> smoothSeekingEnabled = enabled - smoothSeekingViewModel.setSmoothSeekingEnabled(enabled) } ) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/SmoothSeekingViewModel.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/SmoothSeekingViewModel.kt deleted file mode 100644 index 7da3ecfd0..000000000 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/SmoothSeekingViewModel.kt +++ /dev/null @@ -1,51 +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.exoplayer.SeekParameters -import ch.srgssr.pillarbox.core.business.DefaultPillarbox -import ch.srgssr.pillarbox.demo.shared.data.DemoItem -import ch.srgssr.pillarbox.demo.shared.di.PlayerModule -import ch.srgssr.pillarbox.player.PillarboxLoadControl - -/** - * Smooth seeking view model - * - * @param application - */ -class SmoothSeekingViewModel(application: Application) : AndroidViewModel(application) { - private val loadControl = PillarboxLoadControl() - - /** - * Player - */ - val player = DefaultPillarbox( - context = application, - loadControl = loadControl, - mediaItemSource = PlayerModule.provideMixedItemSource(application) - ) - - init { - player.prepare() - player.play() - player.setMediaItem(DemoItem.UnifiedStreamingOnDemand_Dash_TrickPlay.toMediaItem()) - } - - /** - * Set smooth seeking enabled - * - * @param smoothSeeking true to enable smoothSeeking. - */ - fun setSmoothSeekingEnabled(smoothSeeking: Boolean) { - loadControl.smoothSeeking = smoothSeeking - player.setSeekParameters(if (smoothSeeking) SeekParameters.CLOSEST_SYNC else SeekParameters.DEFAULT) - } - - override fun onCleared() { - player.release() - } -} From 416d47db07739df41443fd2a1d069fda46b07950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Wed, 10 Jan 2024 11:27:56 +0100 Subject: [PATCH 10/21] Clear pending seek when other seek event are requested --- .../pillarbox/player/PillarboxPlayer.kt | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt index 737db3782..20cb3993f 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt @@ -170,6 +170,46 @@ class PillarboxPlayer internal constructor( exoPlayer.seekTo(mediaItemIndex, positionMs) } + override fun seekToDefaultPosition() { + clearSeeking() + exoPlayer.seekToDefaultPosition() + } + + override fun seekToDefaultPosition(mediaItemIndex: Int) { + clearSeeking() + exoPlayer.seekToDefaultPosition(mediaItemIndex) + } + + override fun seekBack() { + clearSeeking() + exoPlayer.seekBack() + } + + override fun seekForward() { + clearSeeking() + exoPlayer.seekParameters + } + + override fun seekToNext() { + clearSeeking() + exoPlayer.seekToNext() + } + + override fun seekToPrevious() { + clearSeeking() + exoPlayer.seekToPrevious() + } + + override fun seekToNextMediaItem() { + clearSeeking() + exoPlayer.seekToNextMediaItem() + } + + override fun seekToPreviousMediaItem() { + clearSeeking() + exoPlayer.seekToPreviousMediaItem() + } + /** * Releases the player. * This method must be called when the player is no longer required. The player must not be used after calling this method. From 97f5545a25e022ff2731c511c6cb066b70c30b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Wed, 10 Jan 2024 11:30:01 +0100 Subject: [PATCH 11/21] Remove none necessary code --- .../src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt index 20cb3993f..689622ba6 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt @@ -5,7 +5,6 @@ package ch.srgssr.pillarbox.player import android.content.Context -import androidx.media3.common.C import androidx.media3.common.MediaItem import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackParameters @@ -78,7 +77,6 @@ class PillarboxPlayer internal constructor( if (BuildConfig.DEBUG) { addAnalyticsListener(EventLogger()) } - exoPlayer.videoChangeFrameRateStrategy = C.VIDEO_CHANGE_FRAME_RATE_STRATEGY_OFF } constructor( From cabf338a905b1d642172e42dda5dfefacb082b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Wed, 10 Jan 2024 12:18:52 +0100 Subject: [PATCH 12/21] Fix missing forward call --- .../src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt index 689622ba6..fd0fc7de0 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt @@ -185,7 +185,7 @@ class PillarboxPlayer internal constructor( override fun seekForward() { clearSeeking() - exoPlayer.seekParameters + exoPlayer.seekForward() } override fun seekToNext() { From d650af69c2f614b4e20bfd99983b9b44efbbd875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Wed, 10 Jan 2024 12:21:38 +0100 Subject: [PATCH 13/21] Doesn't disable smooth seeking if smooth seeking is already enabled --- .../pillarbox/ui/SmoothProgressTrackerState.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt index 19452f29d..bb95388c9 100644 --- a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt +++ b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt @@ -21,30 +21,33 @@ class SmoothProgressTrackerState( private val player: Pillarbox, coroutineScope: CoroutineScope ) : ProgressTrackerState { - private var initialSeekParameters = player.seekParameters + private var storedSeekParameters = player.seekParameters + private var storedPlayWhenReady = player.playWhenReady + private var storedSmoothSeeking = player.smoothSeekingEnabled private val simpleProgressTrackerState = SimpleProgressTrackerState(player, coroutineScope) private var startChanging = false - private var storedPlayWhenReady = player.playWhenReady override val progress: StateFlow = simpleProgressTrackerState.progress override fun onChanged(progress: Duration) { simpleProgressTrackerState.onChanged(progress) if (!startChanging) { - player.setSeekParameters(SeekParameters.CLOSEST_SYNC) - player.smoothSeekingEnabled = true startChanging = true storedPlayWhenReady = player.playWhenReady + storedSmoothSeeking = player.smoothSeekingEnabled + storedSeekParameters = player.seekParameters + player.setSeekParameters(SeekParameters.CLOSEST_SYNC) + player.smoothSeekingEnabled = true player.playWhenReady = false } player.seekTo(progress.inWholeMilliseconds) } override fun onFinished() { + startChanging = false simpleProgressTrackerState.onFinished() player.playWhenReady = storedPlayWhenReady - startChanging = false - player.smoothSeekingEnabled = false - player.setSeekParameters(initialSeekParameters) + player.smoothSeekingEnabled = storedSmoothSeeking + player.setSeekParameters(storedSeekParameters) } private companion object { From c305d72ad9f7ad68e452be924546070fe6acaea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Wed, 10 Jan 2024 16:05:40 +0100 Subject: [PATCH 14/21] Fix buildHealth --- pillarbox-ui/build.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pillarbox-ui/build.gradle.kts b/pillarbox-ui/build.gradle.kts index c59803905..b39b8fd19 100644 --- a/pillarbox-ui/build.gradle.kts +++ b/pillarbox-ui/build.gradle.kts @@ -49,7 +49,7 @@ android { } dependencies { - implementation(project(":pillarbox-player")) + api(project(":pillarbox-player")) implementation(libs.androidx.annotation) api(libs.androidx.compose.animation) @@ -68,6 +68,7 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.guava) api(libs.androidx.media3.common) + implementation(libs.androidx.media3.exoplayer) api(libs.androidx.media3.ui) implementation(libs.kotlinx.coroutines.core) From 245415f97dec4ef691d49ff7e2a06a6e18fe0527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Thu, 11 Jan 2024 15:41:36 +0100 Subject: [PATCH 15/21] Rename to PillarboxExoplayer --- .../demo/ui/player/controls/PlayerTimeSlider.kt | 4 ++-- .../player/{Pillarbox.kt => PillarboxExoPlayer.kt} | 2 +- .../java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt | 9 +++++---- .../ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) rename pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/{Pillarbox.kt => PillarboxExoPlayer.kt} (95%) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/controls/PlayerTimeSlider.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/controls/PlayerTimeSlider.kt index c98508382..2d86bfc2a 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/controls/PlayerTimeSlider.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/controls/PlayerTimeSlider.kt @@ -17,7 +17,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.media3.common.Player -import ch.srgssr.pillarbox.player.Pillarbox +import ch.srgssr.pillarbox.player.PillarboxExoPlayer import ch.srgssr.pillarbox.player.extension.canSeek import ch.srgssr.pillarbox.ui.ProgressTrackerState import ch.srgssr.pillarbox.ui.SimpleProgressTrackerState @@ -42,7 +42,7 @@ fun rememberProgressTrackerState( coroutineScope: CoroutineScope = rememberCoroutineScope() ): ProgressTrackerState { return remember(player, smoothTracker) { - if (smoothTracker && player is Pillarbox) { + if (smoothTracker && player is PillarboxExoPlayer) { SmoothProgressTrackerState(player, coroutineScope) } else { SimpleProgressTrackerState(player, coroutineScope) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/Pillarbox.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxExoPlayer.kt similarity index 95% rename from pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/Pillarbox.kt rename to pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxExoPlayer.kt index 78c15718d..11df638bd 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/Pillarbox.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxExoPlayer.kt @@ -11,7 +11,7 @@ import androidx.media3.exoplayer.SeekParameters /** * Pillarbox [ExoPlayer] interface extension. */ -interface Pillarbox : ExoPlayer { +interface PillarboxExoPlayer : ExoPlayer { /** * Listener diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt index fd0fc7de0..7dcf697ed 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt @@ -41,8 +41,8 @@ class PillarboxPlayer internal constructor( private val exoPlayer: ExoPlayer, mediaItemTrackerProvider: MediaItemTrackerProvider? ) : - ExoPlayer by exoPlayer, Pillarbox { - private val listeners = HashSet() + ExoPlayer by exoPlayer, PillarboxExoPlayer { + private val listeners = HashSet() private val itemTracker: CurrentMediaItemTracker? private val window = Window() override var smoothSeekingEnabled: Boolean = false @@ -118,16 +118,17 @@ class PillarboxPlayer internal constructor( override fun addListener(listener: Player.Listener) { exoPlayer.addListener(listener) - if (listener is Pillarbox.Listener) { + if (listener is PillarboxExoPlayer.Listener) { listeners.add(listener) } } override fun removeListener(listener: Player.Listener) { exoPlayer.removeListener(listener) - if (listener is Pillarbox.Listener) { + if (listener is PillarboxExoPlayer.Listener) { listeners.remove(listener) } + exoPlayer.isPlaying } override fun seekTo(positionMs: Long) { diff --git a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt index bb95388c9..1de278bd7 100644 --- a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt +++ b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt @@ -6,7 +6,7 @@ package ch.srgssr.pillarbox.ui import androidx.media3.common.Player import androidx.media3.exoplayer.SeekParameters -import ch.srgssr.pillarbox.player.Pillarbox +import ch.srgssr.pillarbox.player.PillarboxExoPlayer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import kotlin.time.Duration @@ -18,7 +18,7 @@ import kotlin.time.Duration * @param coroutineScope */ class SmoothProgressTrackerState( - private val player: Pillarbox, + private val player: PillarboxExoPlayer, coroutineScope: CoroutineScope ) : ProgressTrackerState { private var storedSeekParameters = player.seekParameters From 9a9bff9fb95cb7bf03131b42dea9af03a6c47ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Fri, 12 Jan 2024 09:13:51 +0100 Subject: [PATCH 16/21] update doc --- .../java/ch/srgssr/pillarbox/player/PillarboxExoPlayer.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxExoPlayer.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxExoPlayer.kt index 11df638bd..77327ef66 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxExoPlayer.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxExoPlayer.kt @@ -28,10 +28,10 @@ interface PillarboxExoPlayer : ExoPlayer { /** * Smooth seeking enabled * - * When [smoothSeekingEnabled] is enabled, next seek event is send only after the current is done. + * When [smoothSeekingEnabled] is true, next seek events is send only after the current is done. * * To have the best result it is important to - * 1) Pause the player while seeking + * 1) Pause the player while seeking. * 2) Set the [ExoPlayer.setSeekParameters] to [SeekParameters.CLOSEST_SYNC]. */ var smoothSeekingEnabled: Boolean From 9bf7d23887f83386a6775a4192c46eebf50281c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Wed, 17 Jan 2024 13:35:05 +0100 Subject: [PATCH 17/21] Disable none needed tracks for smooth seeking --- .../pillarbox/ui/SmoothProgressTrackerState.kt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt index 1de278bd7..c0bcbf467 100644 --- a/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt +++ b/pillarbox-ui/src/main/java/ch/srgssr/pillarbox/ui/SmoothProgressTrackerState.kt @@ -4,6 +4,7 @@ */ package ch.srgssr.pillarbox.ui +import androidx.media3.common.C import androidx.media3.common.Player import androidx.media3.exoplayer.SeekParameters import ch.srgssr.pillarbox.player.PillarboxExoPlayer @@ -24,6 +25,7 @@ class SmoothProgressTrackerState( private var storedSeekParameters = player.seekParameters private var storedPlayWhenReady = player.playWhenReady private var storedSmoothSeeking = player.smoothSeekingEnabled + private var storedTrackSelectionParameters = player.trackSelectionParameters private val simpleProgressTrackerState = SimpleProgressTrackerState(player, coroutineScope) private var startChanging = false override val progress: StateFlow = simpleProgressTrackerState.progress @@ -35,9 +37,17 @@ class SmoothProgressTrackerState( storedPlayWhenReady = player.playWhenReady storedSmoothSeeking = player.smoothSeekingEnabled storedSeekParameters = player.seekParameters + storedTrackSelectionParameters = player.trackSelectionParameters player.setSeekParameters(SeekParameters.CLOSEST_SYNC) player.smoothSeekingEnabled = true player.playWhenReady = false + player.trackSelectionParameters = player.trackSelectionParameters.buildUpon() + .setPreferredVideoRoleFlags(C.ROLE_FLAG_TRICK_PLAY) + .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true) + .setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, true) + .setTrackTypeDisabled(C.TRACK_TYPE_METADATA, true) + .setTrackTypeDisabled(C.TRACK_TYPE_IMAGE, true) + .build() } player.seekTo(progress.inWholeMilliseconds) } @@ -45,12 +55,9 @@ class SmoothProgressTrackerState( override fun onFinished() { startChanging = false simpleProgressTrackerState.onFinished() - player.playWhenReady = storedPlayWhenReady + player.trackSelectionParameters = storedTrackSelectionParameters player.smoothSeekingEnabled = storedSmoothSeeking player.setSeekParameters(storedSeekParameters) - } - - private companion object { - private const val SEEKING_PLAYBACK_SPEED = 16f + player.playWhenReady = storedPlayWhenReady } } From c6996b7bc4111b506f9134243eadd77b40f1cf03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Fri, 19 Jan 2024 16:02:39 +0100 Subject: [PATCH 18/21] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gaëtan Muller --- .../demo/ui/showcases/misc/SmoothSeekingShowcase.kt | 2 +- .../java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/SmoothSeekingShowcase.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/SmoothSeekingShowcase.kt index 37d11ff49..b6a03c1a7 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/SmoothSeekingShowcase.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/showcases/misc/SmoothSeekingShowcase.kt @@ -70,7 +70,7 @@ fun SmoothSeekingShowcase() { } Column { - Box(modifier = Modifier) { + Box { val playbackState by player.playbackStateAsState() val isBuffering = playbackState == Player.STATE_BUFFERING PlayerSurface(player = player, defaultAspectRatio = 16 / 9f) { diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt index e56eb20bf..bd7f8b017 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt @@ -132,9 +132,9 @@ class PillarboxLoadControl( private companion object { private const val BACK_BUFFER_DURATION_MS = 4_000 private val DEFAULT_BUFFER_DURATIONS = BufferDurations( - bufferForPlayback = 500L.milliseconds, - bufferForPlaybackAfterRebuffer = 1000L.milliseconds, - minBufferDuration = 1000L.milliseconds + bufferForPlayback = 500.milliseconds, + bufferForPlaybackAfterRebuffer = 1.seconds, + minBufferDuration = 1.seconds ) } } From 2e7590f150a1091df7aca5b3b575c6623d1ce7ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Fri, 19 Jan 2024 16:08:18 +0100 Subject: [PATCH 19/21] fix rebase --- .../main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt index bd7f8b017..53aff3a14 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt @@ -16,6 +16,7 @@ import androidx.media3.exoplayer.upstream.Allocator import androidx.media3.exoplayer.upstream.DefaultAllocator import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds /** * Pillarbox [LoadControl] implementation that optimize content loading. From 317bdd8bd4c0233b3389174e9e22fc46f34c0a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Fri, 19 Jan 2024 16:14:48 +0100 Subject: [PATCH 20/21] Remove implementation of depreciated functions --- .../pillarbox/player/PillarboxLoadControl.kt | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt index 53aff3a14..c229c58a6 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxLoadControl.kt @@ -83,15 +83,6 @@ class PillarboxLoadControl( defaultLoadControl.onTracksSelected(timeline, mediaPeriodId, renderers, trackGroups, trackSelections) } - @Deprecated("Deprecated in Java") - override fun onTracksSelected( - renderers: Array, - trackGroups: TrackGroupArray, - trackSelections: Array - ) { - defaultLoadControl.onTracksSelected(renderers, trackGroups, trackSelections) - } - override fun shouldStartPlayback( timeline: Timeline, mediaPeriodId: MediaSource.MediaPeriodId, @@ -103,16 +94,6 @@ class PillarboxLoadControl( return defaultLoadControl.shouldStartPlayback(timeline, mediaPeriodId, bufferedDurationUs, playbackSpeed, rebuffering, targetLiveOffsetUs) } - @Deprecated("Deprecated in Java") - override fun shouldStartPlayback( - bufferedDurationUs: Long, - playbackSpeed: Float, - rebuffering: Boolean, - targetLiveOffsetUs: Long - ): Boolean { - return defaultLoadControl.shouldStartPlayback(bufferedDurationUs, playbackSpeed, rebuffering, targetLiveOffsetUs) - } - /** * Buffer durations to use for [DefaultLoadControl.Builder.setBufferDurationsMs]. * From 215f5f3a58791952f374ad5858bb6461166168be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Fri, 19 Jan 2024 16:45:07 +0100 Subject: [PATCH 21/21] Update pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gaëtan Muller --- .../src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt index 7dcf697ed..e0172e54a 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/PillarboxPlayer.kt @@ -128,7 +128,6 @@ class PillarboxPlayer internal constructor( if (listener is PillarboxExoPlayer.Listener) { listeners.remove(listener) } - exoPlayer.isPlaying } override fun seekTo(positionMs: Long) {