Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,6 @@ class PillarboxCastReceiverPlayer(
tracksConverter = tracksConverter,
)

override var smoothSeekingEnabled: Boolean
get() = player.smoothSeekingEnabled
set(value) {
player.smoothSeekingEnabled = value
}

override var trackingEnabled: Boolean
get() = player.trackingEnabled
set(value) {
Expand All @@ -95,9 +89,6 @@ class PillarboxCastReceiverPlayer(
override val isMetricsAvailable: Boolean
get() = player.isMetricsAvailable

override val isSeekParametersAvailable: Boolean
get() = player.isSeekParametersAvailable

override val isImageOutputAvailable: Boolean
get() = player.isImageOutputAvailable

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import androidx.media3.common.TrackSelectionParameters
import androidx.media3.common.Tracks
import androidx.media3.common.util.Clock
import androidx.media3.common.util.Util
import androidx.media3.exoplayer.SeekParameters
import androidx.media3.exoplayer.analytics.DefaultAnalyticsCollector
import androidx.media3.exoplayer.image.ImageOutput
import androidx.media3.exoplayer.util.EventLogger
Expand Down Expand Up @@ -95,7 +94,7 @@ fun <Builder : PillarboxCastPlayerBuilder> PillarboxCastPlayer(
/**
* A [PillarboxPlayer] implementation that works with Cast devices.
*
* It disables smooth seeking and tracking capabilities as these are not supported or relevant in the context of Cast playback.
* It disables scrubbing mode and tracking capabilities as these are not supported or relevant in the context of Cast playback.
*
* @param context A [Context] used to populate [getDeviceInfo]. If `null`, [getDeviceInfo] will always return [DEVICE_INFO_REMOTE_EMPTY].
* @param castContext The context from which the cast session is obtained.
Expand Down Expand Up @@ -127,12 +126,6 @@ class PillarboxCastPlayer internal constructor(
private val analyticsCollector = DefaultAnalyticsCollector(clock).apply { addListener(EventLogger()) }
private val mediaRouter = if (isMediaRouter2Available()) MediaRouter2Wrapper(context) else null

/**
* Smooth seeking is not supported on [CastPlayer]. By its very nature (ie. being remote), seeking **smoothly** is impossible to achieve.
*/
override var smoothSeekingEnabled: Boolean = false
set(value) {}

/**
* This flag is not supported on [CastPlayer]. The receiver should implement tracking on its own.
*/
Expand All @@ -144,11 +137,6 @@ class PillarboxCastPlayer internal constructor(
*/
override val isMetricsAvailable: Boolean = false

/**
* [CastPlayer] does not support [SeekParameters].
*/
override val isSeekParametersAvailable: Boolean = false

/**
* [CastPlayer] does not support [ImageOutput].
*/
Expand Down Expand Up @@ -209,11 +197,9 @@ class PillarboxCastPlayer internal constructor(
analyticsCollector.setPlayer(this, applicationLooper)
}

override fun setSeekParameters(seekParameters: SeekParameters?) = Unit
override fun isScrubbingModeEnabled(): Boolean = false

override fun getSeekParameters(): SeekParameters {
return SeekParameters.DEFAULT
}
override fun setScrubbingModeEnabled(scrubbingModeEnabled: Boolean) = Unit

override fun setImageOutput(imageOutput: ImageOutput?) = Unit

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import androidx.media3.exoplayer.image.ImageOutput
import ch.srgssr.pillarbox.demo.shared.ui.settings.AppSettings
import ch.srgssr.pillarbox.demo.shared.ui.settings.AppSettingsRepository
import ch.srgssr.pillarbox.player.PillarboxPlayer
import ch.srgssr.pillarbox.ui.ImageProgressTrackerState
import ch.srgssr.pillarbox.ui.ProgressTrackerState
import ch.srgssr.pillarbox.ui.SimpleProgressTrackerState
import ch.srgssr.pillarbox.ui.SmoothProgressTrackerState
import kotlinx.coroutines.CoroutineScope

/**
Expand All @@ -29,7 +29,7 @@ import kotlinx.coroutines.CoroutineScope
*/
@Composable
fun rememberProgressTrackerState(
player: Player,
player: PillarboxPlayer,
coroutineScope: CoroutineScope = rememberCoroutineScope(),
imageOutput: ImageOutput = ImageOutput.NO_OP,
): ProgressTrackerState {
Expand All @@ -38,11 +38,11 @@ fun rememberProgressTrackerState(
val appSettings by appSettingsRepository.getAppSettings().collectAsState(AppSettings())
val smoothSeekingEnabled = appSettings.smoothSeekingEnabled

return remember(player, smoothSeekingEnabled) {
if (smoothSeekingEnabled && player is PillarboxPlayer) {
SmoothProgressTrackerState(player, coroutineScope, imageOutput)
return remember(player, smoothSeekingEnabled, imageOutput) {
if (imageOutput != ImageOutput.NO_OP) {
ImageProgressTrackerState(player, coroutineScope, imageOutput)
} else {
SimpleProgressTrackerState(player, coroutineScope)
SimpleProgressTrackerState(player = player, coroutineScope = coroutineScope, useScrubbingMode = smoothSeekingEnabled)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.media3.common.C
import androidx.media3.common.MediaMetadata
import androidx.media3.common.Player
import androidx.media3.common.Timeline.Window
import androidx.tv.material3.DrawerValue
import androidx.tv.material3.LocalContentColor
Expand Down Expand Up @@ -329,7 +328,7 @@ private fun ChapterInfo(

@Composable
private fun PlayerTimeRow(
player: Player,
player: PillarboxPlayer,
modifier: Modifier = Modifier,
progressTracker: ProgressTrackerState = rememberProgressTrackerState(player = player),
onProgressChange: () -> Unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.media3.common.C
import androidx.media3.common.Player
import androidx.media3.common.Timeline.Window
import ch.srgssr.pillarbox.demo.shared.ui.components.PillarboxSlider
import ch.srgssr.pillarbox.demo.shared.ui.getFormatter
import ch.srgssr.pillarbox.demo.shared.ui.localTimeFormatter
import ch.srgssr.pillarbox.demo.shared.ui.player.rememberProgressTrackerState
import ch.srgssr.pillarbox.demo.ui.theme.paddings
import ch.srgssr.pillarbox.player.PillarboxPlayer
import ch.srgssr.pillarbox.player.extension.canSeek
import ch.srgssr.pillarbox.player.extension.getUnixTimeMs
import ch.srgssr.pillarbox.ui.ProgressTrackerState
Expand All @@ -44,14 +44,14 @@ import kotlin.time.Instant
/**
* Component used to display the time progression of the media being played, and manually changing the progression, if supported.
*
* @param player The [Player] to observe.
* @param player The [PillarboxPlayer] to observe.
* @param modifier The [Modifier] to apply to the layout.
* @param progressTracker The progress tracker.
* @param interactionSource The [PillarboxSlider] interaction source.
*/
@Composable
fun PlayerTimeSlider(
player: Player,
player: PillarboxPlayer,
modifier: Modifier = Modifier,
progressTracker: ProgressTrackerState = rememberProgressTrackerState(player = player),
interactionSource: MutableInteractionSource? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.media3.common.Player
import androidx.media3.common.util.Clock
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.LoadControl
import androidx.media3.exoplayer.ScrubbingModeParameters
import ch.srgssr.pillarbox.player.analytics.PillarboxAnalyticsCollector
import ch.srgssr.pillarbox.player.asset.AssetLoader
import ch.srgssr.pillarbox.player.monitoring.Logcat
Expand Down Expand Up @@ -255,6 +256,11 @@ abstract class PillarboxBuilder {
.setTrackSelector(PillarboxTrackSelector(context))
.setAnalyticsCollector(PillarboxAnalyticsCollector(clock))
.setDeviceVolumeControlEnabled(true) // Allow the player to control the device volume
.setScrubbingModeParameters(
ScrubbingModeParameters.DEFAULT.buildUpon()
.setFractionalSeekTolerance(1.0, 1.0)
.build()
)
.apply { playbackLooper?.let(::setPlaybackLooper) }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,26 +94,8 @@ class PillarboxExoPlayer internal constructor(
coroutineContext = coroutineContext,
)

override var smoothSeekingEnabled: Boolean = false
set(value) {
if (value != field) {
field = value
if (!value) {
seekEnd()
}
clearSeeking()
listeners.sendEvent(PillarboxPlayer.EVENT_SMOOTH_SEEKING_ENABLED_CHANGED) { listener ->
listener.onSmoothSeekingEnabledChanged(value)
}
}
}
private var pendingSeek: Long? = null
private var isSeeking: Boolean = false

override val isMetricsAvailable: Boolean = true

override val isSeekParametersAvailable: Boolean = true

override val isImageOutputAvailable: Boolean = true

/**
Expand Down Expand Up @@ -194,96 +176,16 @@ class PillarboxExoPlayer internal constructor(
}

private fun handleBlockedTimeRange(timeRange: BlockedTimeRange) {
clearSeeking()
exoPlayer.seekTo(timeRange.end + 1)
}

override fun seekTo(positionMs: Long) {
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 (isSeeking) {
pendingSeek = positionMs
return
}
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.seekForward()
}

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.
*
* Release call automatically [stop] if the player is not in [Player.STATE_IDLE].
*/
override fun release() {
clearSeeking()
exoPlayer.release()
listeners.release()
mediaMetadataTracker.release()
Expand Down Expand Up @@ -333,33 +235,15 @@ class PillarboxExoPlayer internal constructor(
return exoPlayer.getSecondaryRenderer(index)
}

private fun seekEnd() {
isSeeking = false
pendingSeek?.let { pendingPosition ->
pendingSeek = null
seekTo(pendingPosition)
}
}

private fun clearSeeking() {
isSeeking = false
pendingSeek = null
}

private inner class ComponentListener : Player.Listener {
private val window = Window()

override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
clearSeeking()
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
currentPillarboxMetadata = PillarboxMetadata.EMPTY
}
}

override fun onRenderedFirstFrame() {
seekEnd()
}

override fun onTracksChanged(tracks: Tracks) {
if (tracks.containsType(PillarboxMetadataTrackGroup.TRACK_TYPE_PILLARBOX_METADATA)) {
val group = tracks.groups.first { it.type == PillarboxMetadataTrackGroup.TRACK_TYPE_PILLARBOX_METADATA }
Expand All @@ -370,24 +254,7 @@ class PillarboxExoPlayer internal constructor(
}
}

override fun onPlaybackStateChanged(playbackState: Int) {
when (playbackState) {
Player.STATE_READY -> {
if (isSeeking) {
seekEnd()
}
}

Player.STATE_IDLE, Player.STATE_ENDED -> {
clearSeeking()
}

Player.STATE_BUFFERING -> Unit
}
}

override fun onPlayerError(error: PlaybackException) {
clearSeeking()
if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) {
setPlaybackSpeed(NormalSpeed)
seekToDefaultPosition()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,12 @@ class PillarboxLoadControl(
)

private companion object {
private const val BACK_BUFFER_DURATION_MS = 4_000
private const val BACK_BUFFER_DURATION_MS = 1_000
private val DEFAULT_BUFFER_DURATIONS = BufferDurations(
bufferForPlayback = 500.milliseconds,
bufferForPlaybackAfterRebuffer = 1.seconds,
minBufferDuration = 1.seconds,
bufferForPlayback = 0.milliseconds,
bufferForPlaybackAfterRebuffer = 0.milliseconds,
minBufferDuration = 10.seconds,
maxBufferDuration = 60.seconds,
)
}
}
Loading