Skip to content

Commit

Permalink
Improve live edge detection and provide Player extension (#505)
Browse files Browse the repository at this point in the history
Co-authored-by: Gaëtan Muller <[email protected]>
  • Loading branch information
StaehliJ and MGaetan89 authored Apr 18, 2024
1 parent 12ee176 commit 7bfecc3
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@ import androidx.media3.common.Player
import ch.srgssr.pillarbox.demo.ui.player.LiveIndicator
import ch.srgssr.pillarbox.demo.ui.theme.paddings
import ch.srgssr.pillarbox.player.extension.canSeek
import ch.srgssr.pillarbox.player.extension.isAtLiveEdge
import ch.srgssr.pillarbox.ui.extension.availableCommandsAsState
import ch.srgssr.pillarbox.ui.extension.currentMediaMetadataAsState
import ch.srgssr.pillarbox.ui.extension.currentPositionAsState
import ch.srgssr.pillarbox.ui.extension.getCurrentDefaultPositionAsState
import ch.srgssr.pillarbox.ui.extension.isCurrentMediaItemLiveAsState
import ch.srgssr.pillarbox.ui.extension.playWhenReadyAsState

private const val LiveEdgeThreshold = 5_000L

/**
* Player controls
*
Expand All @@ -54,10 +52,6 @@ fun PlayerControls(
) {
val mediaMetadata by player.currentMediaMetadataAsState()
val isCurrentItemLive by player.isCurrentMediaItemLiveAsState()
val currentPlaybackPosition by player.currentPositionAsState()
val currentDefaultPosition by player.getCurrentDefaultPositionAsState()
val playWhenReady by player.playWhenReadyAsState()
val isAtLiveEdge = playWhenReady && currentPlaybackPosition >= (currentDefaultPosition - LiveEdgeThreshold)
val availableCommand by player.availableCommandsAsState()
Box(
modifier = modifier.background(color = backgroundColor),
Expand Down Expand Up @@ -93,6 +87,9 @@ fun PlayerControls(
}

if (isCurrentItemLive) {
val currentPlaybackPosition by player.currentPositionAsState()
val isPlayWhenReady by player.playWhenReadyAsState()
val isAtLiveEdge = isPlayWhenReady && player.isAtLiveEdge(currentPlaybackPosition)
LiveIndicator(
modifier = Modifier
.padding(MaterialTheme.paddings.mini),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ package ch.srgssr.pillarbox.player.extension

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 kotlin.time.Duration.Companion.microseconds
import kotlin.time.Duration.Companion.milliseconds

/**
* Get a snapshot of the current media items
Expand Down Expand Up @@ -47,3 +52,29 @@ fun Player.currentPositionPercentage(): Float {
fun Player.setHandleAudioFocus(handleAudioFocus: Boolean) {
setAudioAttributes(audioAttributes, handleAudioFocus)
}

/**
* Is at live edge
*
* @param positionMs The position in milliseconds.
* @param window The optional Window.
* @return if [positionMs] is at live edge.
*/
fun Player.isAtLiveEdge(positionMs: Long = currentPosition, window: Window = Window()): Boolean {
if (!isCurrentMediaItemLive) return false
currentTimeline.getWindow(currentMediaItemIndex, window)
val offsetSeconds = when (val manifest = currentManifest) {
is HlsManifest -> {
manifest.mediaPlaylist.targetDurationUs.microseconds.inWholeSeconds
}

is DashManifest -> {
manifest.minBufferTimeMs.milliseconds.inWholeSeconds
}

else -> {
0L
}
}
return playWhenReady && positionMs.milliseconds.inWholeSeconds >= window.defaultPositionMs.milliseconds.inWholeSeconds - offsetSeconds
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.Player.Commands
import androidx.media3.common.Timeline
import androidx.media3.common.Timeline.Window
import androidx.media3.common.VideoSize
import ch.srgssr.pillarbox.player.DefaultUpdateInterval
import ch.srgssr.pillarbox.player.availableCommandsAsFlow
Expand All @@ -34,7 +31,6 @@ import ch.srgssr.pillarbox.player.durationAsFlow
import ch.srgssr.pillarbox.player.extension.getCurrentMediaItems
import ch.srgssr.pillarbox.player.extension.getPlaybackSpeed
import ch.srgssr.pillarbox.player.getAspectRatioAsFlow
import ch.srgssr.pillarbox.player.getCurrentDefaultPositionAsFlow
import ch.srgssr.pillarbox.player.getCurrentMediaItemIndexAsFlow
import ch.srgssr.pillarbox.player.getCurrentMediaItemsAsFlow
import ch.srgssr.pillarbox.player.getPlaybackSpeedAsFlow
Expand Down Expand Up @@ -252,19 +248,3 @@ fun Player.isCurrentMediaItemLiveAsState(): State<Boolean> {
}
return flow.collectAsState(initial = isCurrentMediaItemLive)
}

/**
* @return The current default position as state.
* @see Timeline.Window.getDefaultPositionMs
*/
@Composable
fun Player.getCurrentDefaultPositionAsState(): LongState {
val flow = remember(this) {
getCurrentDefaultPositionAsFlow()
}
val window = remember {
Window()
}
val initialValue = if (!currentTimeline.isEmpty) currentTimeline.getWindow(currentMediaItemIndex, window).defaultPositionMs else C.TIME_UNSET
return flow.collectAsState(initial = initialValue).asLongState()
}

0 comments on commit 7bfecc3

Please sign in to comment.