Skip to content

Commit

Permalink
Simplify TimeRange tracking.
Browse files Browse the repository at this point in the history
  • Loading branch information
StaehliJ committed May 1, 2024
1 parent 56fa6b4 commit 5bb1bd5
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 212 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import ch.srgssr.pillarbox.demo.tv.ui.player.compose.settings.PlaybackSettingsDr
import ch.srgssr.pillarbox.demo.tv.ui.theme.paddings
import ch.srgssr.pillarbox.ui.extension.currentMediaMetadataAsState
import ch.srgssr.pillarbox.ui.extension.getCurrentChapterAsState
import ch.srgssr.pillarbox.ui.extension.getCurrentTimeRangeAsState
import ch.srgssr.pillarbox.ui.extension.getCurrentCreditAsState
import ch.srgssr.pillarbox.ui.extension.playerErrorAsState
import ch.srgssr.pillarbox.ui.widget.maintainVisibleOnFocus
import ch.srgssr.pillarbox.ui.widget.player.PlayerSurface
Expand All @@ -64,7 +64,7 @@ fun PlayerView(
) {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val visibilityState = rememberDelayedVisibilityState(player = player, visible = true)
val timeInterval by player.getCurrentTimeRangeAsState()
val timeInterval by player.getCurrentCreditAsState()

LaunchedEffect(drawerState.currentValue) {
when (drawerState.currentValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import kotlin.time.Duration.Companion.seconds
* @param visibilityDelay
*/
@Composable
fun BlockedIntervalWarning(
fun BlockedTimeRangeWarning(
player: Player,
modifier: Modifier = Modifier,
visibilityDelay: Duration = 5.seconds,
Expand Down Expand Up @@ -67,34 +67,34 @@ fun BlockedIntervalWarning(
visible = currentBlockedTimeRangeInterval != null
) {
currentBlockedTimeRangeInterval?.let {
BlockedSegmentInfo(modifier = Modifier.fillMaxWidth(), blockedTimeRangeInterval = it)
BlockedTimeRangeInfo(modifier = Modifier.fillMaxWidth(), blockedTimeRange = it)
}
}
}

@Composable
private fun BlockedSegmentInfo(
blockedTimeRangeInterval: BlockedTimeRange,
private fun BlockedTimeRangeInfo(
blockedTimeRange: BlockedTimeRange,
modifier: Modifier = Modifier
) {
Text(
modifier = modifier
.background(color = Color.Blue.copy(0.8f))
.padding(MaterialTheme.paddings.baseline),
text = "Reach a blocked segment! ${blockedTimeRangeInterval.reason}",
text = "Reach a blocked segment! ${blockedTimeRange.reason}",
color = Color.White,
style = MaterialTheme.typography.labelSmall
)
}

@Preview(showBackground = true)
@Composable
private fun BlockedSegmentPreview() {
private fun BlockedTimeRangeInfoPreview() {
val blockedTimeRangeSection = BlockedTimeRange(start = 0, end = 0, reason = "GeoBlock")
PillarboxTheme {
BlockedSegmentInfo(
BlockedTimeRangeInfo(
modifier = Modifier.fillMaxWidth(),
blockedTimeRangeInterval = blockedTimeRangeSection
blockedTimeRange = blockedTimeRangeSection
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import ch.srgssr.pillarbox.demo.ui.theme.paddings
import ch.srgssr.pillarbox.ui.ProgressTrackerState
import ch.srgssr.pillarbox.ui.ScaleMode
import ch.srgssr.pillarbox.ui.exoplayer.ExoPlayerSubtitleView
import ch.srgssr.pillarbox.ui.extension.getCurrentTimeRangeAsState
import ch.srgssr.pillarbox.ui.extension.getCurrentCreditAsState
import ch.srgssr.pillarbox.ui.extension.hasMediaItemsAsState
import ch.srgssr.pillarbox.ui.extension.playbackStateAsState
import ch.srgssr.pillarbox.ui.extension.playerErrorAsState
Expand Down Expand Up @@ -85,7 +85,7 @@ fun PlayerView(
autoHideEnabled = !isSliderDragged,
visible = controlsVisible
)
val timeInterval by player.getCurrentTimeRangeAsState()
val currentSkipCredits by player.getCurrentCreditAsState()

ToggleableBox(
modifier = modifier,
Expand All @@ -96,7 +96,7 @@ fun PlayerView(
player = player,
interactionSource = interactionSource,
progressTracker = progressTracker,
timeInterval = timeInterval,
credit = currentSkipCredits,
content = content
)
}
Expand All @@ -118,16 +118,16 @@ fun PlayerView(
ExoPlayerSubtitleView(player = player)
}

if (timeInterval != null && !visibilityState.isVisible) {
if (currentSkipCredits != null && !visibilityState.isVisible) {
SkipButton(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(MaterialTheme.paddings.baseline),
onClick = { player.seekTo(timeInterval?.end ?: 0L) },
onClick = { player.seekTo(currentSkipCredits?.end ?: 0L) },
)
}

BlockedIntervalWarning(
BlockedTimeRangeWarning(
player = player,
modifier = Modifier
.align(Alignment.TopStart)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import kotlinx.coroutines.flow.map
* @param backgroundColor The background color to apply behind the controls.
* @param interactionSource The interaction source of the slider.
* @param progressTracker The progress tracker.
* @param timeInterval The current time interval, or `null`.
* @param credit The current time interval, or `null`.
* @param content The content to display under the slider.
* @receiver
*/
Expand All @@ -55,7 +55,7 @@ fun PlayerControls(
backgroundColor: Color = Color.Black.copy(0.5f),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
progressTracker: ProgressTrackerState = rememberProgressTrackerState(player = player, smoothTracker = true),
timeInterval: Credit? = null,
credit: Credit? = null,
content: @Composable ColumnScope.() -> Unit,
) {
val currentMediaMetadata by player.currentMediaMetadataAsState()
Expand Down Expand Up @@ -88,12 +88,12 @@ fun PlayerControls(
modifier = Modifier
.align(Alignment.BottomCenter)
) {
if (timeInterval != null) {
if (credit != null) {
SkipButton(
modifier = Modifier
.align(Alignment.End)
.padding(MaterialTheme.paddings.baseline),
onClick = { player.seekTo(timeInterval.end) },
onClick = { player.seekTo(credit.end) },
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,18 @@ import androidx.media3.exoplayer.LoadControl
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter
import androidx.media3.exoplayer.util.EventLogger
import ch.srgssr.pillarbox.player.asset.PillarboxData
import ch.srgssr.pillarbox.player.asset.timeRange.BlockedTimeRange
import ch.srgssr.pillarbox.player.asset.timeRange.Chapter
import ch.srgssr.pillarbox.player.asset.timeRange.Credit
import ch.srgssr.pillarbox.player.extension.getChapterAtPosition
import ch.srgssr.pillarbox.player.extension.getCreditAtPosition
import ch.srgssr.pillarbox.player.extension.getPlaybackSpeed
import ch.srgssr.pillarbox.player.extension.setPreferredAudioRoleFlagsToAccessibilityManagerSettings
import ch.srgssr.pillarbox.player.extension.setSeekIncrements
import ch.srgssr.pillarbox.player.source.PillarboxMediaSourceFactory
import ch.srgssr.pillarbox.player.tracker.AnalyticsMediaItemTracker
import ch.srgssr.pillarbox.player.tracker.BlockedTimeRangeTracker
import ch.srgssr.pillarbox.player.tracker.CurrentMediaItemPillarboxDataTracker
import ch.srgssr.pillarbox.player.tracker.MediaItemTrackerProvider
import ch.srgssr.pillarbox.player.tracker.MediaItemTrackerRepository
import ch.srgssr.pillarbox.player.tracker.TimeRangeTracker
import ch.srgssr.pillarbox.player.tracker.TimeRangeTracks

/**
* Pillarbox player
Expand Down Expand Up @@ -84,26 +80,28 @@ class PillarboxExoPlayer internal constructor(
}
get() = analyticsTracker.enabled

private val blockedTimeRangeTracker = BlockedTimeRangeTracker(this)
private val chapterTracker = TimeRangeTracker(
player = this,
getTimeRangeAt = Player::getChapterAtPosition,
getAllTimeRanges = PillarboxData::chapters,
notifyTimeRangeChanged = { notifyCurrentChapterChanged(it) },
)
private val timeRangeTracker = TimeRangeTracker(
player = this,
getTimeRangeAt = Player::getCreditAtPosition,
getAllTimeRanges = PillarboxData::credits,
notifyTimeRangeChanged = { notifyTimeRangeChanged(it) },
private val timeRangeTracker = TimeRangeTracks(
this,
object : TimeRangeTracks.Callback {
override fun onBlockedTimeRange(blockedTimeRange: BlockedTimeRange) {
notifyBlockedTimeRangeReached(blockedTimeRange)
handleBlockedTimeRange(blockedTimeRange)
}

override fun onChapterChanged(chapter: Chapter?) {
notifyCurrentChapterChanged(chapter)
}

override fun onCreditsChanged(credit: Credit?) {
notifyTimeRangeChanged(credit)
}
}
)

init {
exoPlayer.addListener(ComponentListener())
itemPillarboxDataTracker.addCallback(blockedTimeRangeTracker)
itemPillarboxDataTracker.addCallback(analyticsTracker)
itemPillarboxDataTracker.addCallback(chapterTracker)
itemPillarboxDataTracker.addCallback(timeRangeTracker)
itemPillarboxDataTracker.addCallback(analyticsTracker)
if (BuildConfig.DEBUG) {
addAnalyticsListener(EventLogger())
}
Expand Down Expand Up @@ -238,9 +236,9 @@ class PillarboxExoPlayer internal constructor(
exoPlayer.replaceMediaItems(fromIndex, toIndex, mediaItems.map { it.clearTag() })
}

internal fun seekToWithoutSmoothSeeking(positionMs: Long) {
internal fun handleBlockedTimeRange(timeRange: BlockedTimeRange) {
clearSeeking()
exoPlayer.seekTo(positionMs)
exoPlayer.seekTo(timeRange.end + 1)
}

override fun seekTo(positionMs: Long) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,6 @@ fun Player.videoSizeAsFlow(): Flow<VideoSize> = callbackFlow {
*
* @param defaultAspectRatio The aspect ratio when the video size is unknown, or for audio content.
*/
@OptIn(ExperimentalCoroutinesApi::class)
fun Player.getAspectRatioAsFlow(defaultAspectRatio: Float): Flow<Float> {
return combine(
getCurrentTracksAsFlow(),
Expand Down Expand Up @@ -408,14 +407,14 @@ fun Player.getCurrentChapterAsFlow(): Flow<Chapter?> = callbackFlow {
/**
* @return Get the current time range as flow, when the time interval changes.
*/
fun Player.getCurrentSkipableTimeRangeAsFlow(): Flow<Credit?> = callbackFlow {
fun Player.getCurrentCreditAsFlow(): Flow<Credit?> = callbackFlow {
val listener = object : PillarboxPlayer.Listener {
override fun onCreditChanged(credit: Credit?) {
trySend(credit)
}
}
trySend(getCreditAtPosition())
addPlayerListener(this@getCurrentSkipableTimeRangeAsFlow, listener)
addPlayerListener(this@getCurrentCreditAsFlow, listener)
}

private suspend fun <T> ProducerScope<T>.addPlayerListener(player: Player, listener: Listener) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package ch.srgssr.pillarbox.player.asset.timeRange

import androidx.media3.common.C
import kotlin.math.abs

/**
Expand Down Expand Up @@ -38,3 +39,11 @@ sealed interface TimeRange {
return positionMs in start..<end
}
}

/**
* @return the first not null [TimeRange] at [position].
*/
fun <T : TimeRange> List<T>.firstOrNullAtPosition(position: Long): T? {
if (position == C.TIME_UNSET) return null
return firstOrNull { position in it }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
*/
package ch.srgssr.pillarbox.player.extension

import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.Timeline.Window
import androidx.media3.exoplayer.dash.manifest.DashManifest
import androidx.media3.exoplayer.hls.HlsManifest
import ch.srgssr.pillarbox.player.asset.timeRange.BlockedTimeRange
import ch.srgssr.pillarbox.player.asset.timeRange.Chapter
import ch.srgssr.pillarbox.player.asset.timeRange.Credit
import ch.srgssr.pillarbox.player.asset.timeRange.firstOrNullAtPosition
import kotlin.time.Duration.Companion.microseconds
import kotlin.time.Duration.Companion.milliseconds

Expand Down Expand Up @@ -78,8 +77,7 @@ fun Player.getCurrentCredits(): List<Credit> {
* @return `null` if there is no chapter at [positionMs].
*/
fun Player.getChapterAtPosition(positionMs: Long = currentPosition): Chapter? {
if (positionMs == C.TIME_UNSET) return null
return getCurrentChapters().firstOrNull { positionMs in it }
return getCurrentChapters().firstOrNullAtPosition(positionMs)
}

/**
Expand All @@ -89,11 +87,7 @@ fun Player.getChapterAtPosition(positionMs: Long = currentPosition): Chapter? {
* @return `null` if there is no credit at [positionMs].
*/
fun Player.getCreditAtPosition(positionMs: Long = currentPosition): Credit? {
return if (positionMs == C.TIME_UNSET) {
null
} else {
getCurrentCredits().firstOrNull { positionMs in it }
}
return getCurrentCredits().firstOrNullAtPosition(positionMs)
}

/**
Expand Down Expand Up @@ -121,21 +115,3 @@ fun Player.isAtLiveEdge(positionMs: Long = currentPosition, window: Window = Win
}
return playWhenReady && positionMs.milliseconds.inWholeSeconds >= window.defaultPositionMs.milliseconds.inWholeSeconds - offsetSeconds
}

/**
* @return The current media item blocked intervals or an empty list.
*/
fun Player.getCurrentBlockedTimeRanges(): List<BlockedTimeRange> {
return currentMediaItem?.pillarboxData?.blockedTimeRanges ?: emptyList()
}

/**
* Get the blocked interval at [position][positionMs].
*
* @param positionMs The position, in milliseconds, to find the block interval from.
* @return `null` if there is no [BlockedTimeRange] at [positionMs].
*/
fun Player.getBlockedTimeRangeAtPosition(positionMs: Long = currentPosition): BlockedTimeRange? {
if (positionMs == C.TIME_UNSET) return null
return getCurrentBlockedTimeRanges().firstOrNull { positionMs in it }
}
Loading

0 comments on commit 5bb1bd5

Please sign in to comment.