-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
708 revisit timerange trackers (#713)
Co-authored-by: Gaëtan Muller <[email protected]>
- Loading branch information
Showing
6 changed files
with
255 additions
and
247 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/BlockedTimeRangeTracker.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* | ||
* Copyright (c) SRG SSR. All rights reserved. | ||
* License information is available from the LICENSE file. | ||
*/ | ||
package ch.srgssr.pillarbox.player.tracker | ||
|
||
import androidx.media3.common.Player | ||
import androidx.media3.exoplayer.PlayerMessage | ||
import ch.srgssr.pillarbox.player.PillarboxExoPlayer | ||
import ch.srgssr.pillarbox.player.asset.PillarboxData | ||
import ch.srgssr.pillarbox.player.asset.timeRange.BlockedTimeRange | ||
import ch.srgssr.pillarbox.player.asset.timeRange.TimeRange | ||
import ch.srgssr.pillarbox.player.asset.timeRange.firstOrNullAtPosition | ||
|
||
internal class BlockedTimeRangeTracker( | ||
private val callback: (TimeRange?) -> Unit | ||
) : CurrentMediaItemPillarboxDataTracker.Callback, Player.Listener { | ||
private val playerMessages = mutableListOf<PlayerMessage>() | ||
private var timeRanges: List<BlockedTimeRange>? = null | ||
private lateinit var player: PillarboxExoPlayer | ||
|
||
fun setPlayer(player: PillarboxExoPlayer) { | ||
this.player = player | ||
player.addListener(this) | ||
} | ||
|
||
/* | ||
* Called when the callback is added, and we already have a [PillarboxData]. | ||
*/ | ||
override fun onPillarboxDataChanged(data: PillarboxData?) { | ||
clear() | ||
data?.let { | ||
timeRanges = it.blockedTimeRanges | ||
it.blockedTimeRanges.firstOrNullAtPosition(player.currentPosition)?.let { timeRange -> | ||
callback(timeRange) | ||
} | ||
createMessages(it.blockedTimeRanges) | ||
} | ||
} | ||
|
||
override fun onEvents(player: Player, events: Player.Events) { | ||
val blockedInterval = timeRanges?.firstOrNullAtPosition(player.currentPosition) | ||
blockedInterval?.let { | ||
// Ignore blocked time ranges that end at the same time as the media. Otherwise, infinite seeks operations. | ||
if (player.currentPosition >= player.duration) return@let | ||
callback(it) | ||
} | ||
} | ||
|
||
private fun createMessages(timeRanges: List<BlockedTimeRange>) { | ||
val target = PlayerMessage.Target { _, message -> | ||
callback(message as BlockedTimeRange) | ||
} | ||
playerMessages.addAll( | ||
timeRanges.map { | ||
player.createMessage(target).apply { | ||
deleteAfterDelivery = false | ||
looper = player.applicationLooper | ||
payload = it | ||
setPosition(it.start) | ||
send() | ||
} | ||
} | ||
) | ||
} | ||
|
||
private fun clear() { | ||
playerMessages.forEach { playerMessage -> | ||
playerMessage.cancel() | ||
} | ||
playerMessages.clear() | ||
timeRanges = null | ||
} | ||
|
||
fun release() { | ||
clear() | ||
} | ||
} |
145 changes: 145 additions & 0 deletions
145
...-player/src/main/java/ch/srgssr/pillarbox/player/tracker/PillarboxMediaMetaDataTracker.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/* | ||
* Copyright (c) SRG SSR. All rights reserved. | ||
* License information is available from the LICENSE file. | ||
*/ | ||
package ch.srgssr.pillarbox.player.tracker | ||
|
||
import androidx.media3.common.MediaMetadata | ||
import androidx.media3.common.Player | ||
import androidx.media3.exoplayer.PlayerMessage | ||
import ch.srgssr.pillarbox.player.PillarboxExoPlayer | ||
import ch.srgssr.pillarbox.player.asset.timeRange.Chapter | ||
import ch.srgssr.pillarbox.player.asset.timeRange.Credit | ||
import ch.srgssr.pillarbox.player.asset.timeRange.TimeRange | ||
import ch.srgssr.pillarbox.player.asset.timeRange.firstOrNullAtPosition | ||
import ch.srgssr.pillarbox.player.extension.chapters | ||
import ch.srgssr.pillarbox.player.extension.credits | ||
|
||
internal class PillarboxMediaMetaDataTracker(private val callback: (TimeRange?) -> Unit) : Player.Listener { | ||
private var currentChapterTracker: Tracker<Chapter>? = null | ||
private var currentCreditTracker: Tracker<Credit>? = null | ||
private lateinit var player: PillarboxExoPlayer | ||
|
||
private fun clear() { | ||
currentCreditTracker?.clear() | ||
currentChapterTracker?.clear() | ||
currentChapterTracker = null | ||
currentCreditTracker = null | ||
} | ||
|
||
override fun onPositionDiscontinuity(oldPosition: Player.PositionInfo, newPosition: Player.PositionInfo, reason: Int) { | ||
when (reason) { | ||
Player.DISCONTINUITY_REASON_SEEK, Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT -> { | ||
if (oldPosition.mediaItemIndex == newPosition.mediaItemIndex) { | ||
val position = newPosition.positionMs | ||
currentCreditTracker?.setCurrentPosition(position) | ||
currentChapterTracker?.setCurrentPosition(position) | ||
} else { | ||
clear() | ||
} | ||
} | ||
|
||
else -> { | ||
clear() | ||
} | ||
} | ||
} | ||
|
||
fun setPlayer(player: PillarboxExoPlayer) { | ||
this.player = player | ||
this.player.addListener(this) | ||
} | ||
|
||
fun release() { | ||
clear() | ||
} | ||
|
||
override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) { | ||
mediaMetadata.chapters?.let { | ||
if (currentChapterTracker?.timeRanges != it) { | ||
currentChapterTracker?.clear() | ||
currentChapterTracker = Tracker(player = player, timeRanges = it, callback = callback) | ||
} | ||
} | ||
|
||
mediaMetadata.credits?.let { | ||
if (currentCreditTracker?.timeRanges != it) { | ||
currentCreditTracker?.clear() | ||
currentCreditTracker = Tracker(player = player, timeRanges = it, callback = callback) | ||
} | ||
} | ||
} | ||
|
||
private class Tracker<T : TimeRange>( | ||
private val player: PillarboxExoPlayer, | ||
val timeRanges: List<T>, | ||
private val callback: (T?) -> Unit, | ||
) { | ||
private val messages: List<PlayerMessage> = createMessages() | ||
|
||
private var currentTimeRange: T? = null | ||
set(value) { | ||
if (field != value) { | ||
callback(value) | ||
field = value | ||
} | ||
} | ||
|
||
init { | ||
currentTimeRange = timeRanges.firstOrNullAtPosition(player.currentPosition) | ||
} | ||
|
||
fun setCurrentPosition(currentPosition: Long) { | ||
val currentTimeRange = currentTimeRange | ||
?.takeIf { timeRange -> currentPosition in timeRange } | ||
?: timeRanges.firstOrNullAtPosition(currentPosition) | ||
this.currentTimeRange = currentTimeRange | ||
} | ||
|
||
private fun createMessages(): List<PlayerMessage> { | ||
val messageHandler = PlayerMessage.Target { messageType, message -> | ||
@Suppress("UNCHECKED_CAST") | ||
val timeRange = message as? T ?: return@Target | ||
when (messageType) { | ||
TYPE_ENTER -> currentTimeRange = timeRange | ||
TYPE_EXIT -> { | ||
val nextTimeRange = timeRanges.firstOrNullAtPosition(player.currentPosition) | ||
if (nextTimeRange == null) currentTimeRange = null | ||
} | ||
} | ||
} | ||
val playerMessages = mutableListOf<PlayerMessage>() | ||
timeRanges.forEach { timeRange -> | ||
val messageEnter = player.createMessage(messageHandler).apply { | ||
deleteAfterDelivery = false | ||
looper = player.applicationLooper | ||
payload = timeRange | ||
setPosition(timeRange.start) | ||
type = TYPE_ENTER | ||
send() | ||
} | ||
val messageExit = player.createMessage(messageHandler).apply { | ||
deleteAfterDelivery = false | ||
looper = player.applicationLooper | ||
payload = timeRange | ||
setPosition(timeRange.end) | ||
type = TYPE_EXIT | ||
send() | ||
} | ||
playerMessages.add(messageEnter) | ||
playerMessages.add(messageExit) | ||
} | ||
return playerMessages | ||
} | ||
|
||
fun clear() { | ||
messages.forEach { it.cancel() } | ||
currentTimeRange = null | ||
} | ||
|
||
private companion object { | ||
private const val TYPE_ENTER = 1 | ||
private const val TYPE_EXIT = 2 | ||
} | ||
} | ||
} |
Oops, something went wrong.