From 02eda96a4d6d211286178a56e0fb52478b70ae2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20Sta=CC=88hli?= Date: Wed, 8 May 2024 16:45:32 +0200 Subject: [PATCH 1/3] Add some BlockedTimeRange edge cases --- .../pillarbox/demo/shared/data/Playlist.kt | 6 +- .../pillarbox/demo/shared/di/PlayerModule.kt | 2 + .../source/BlockedTimeRangeAssetLoader.kt | 96 +++++++++++++++++++ 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/source/BlockedTimeRangeAssetLoader.kt diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/data/Playlist.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/data/Playlist.kt index ccc3a891d..19fbe9ac0 100644 --- a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/data/Playlist.kt +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/data/Playlist.kt @@ -8,6 +8,7 @@ package ch.srgssr.pillarbox.demo.shared.data import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata +import ch.srgssr.pillarbox.demo.shared.source.BlockedTimeRangeAssetLoader import java.io.Serializable /** @@ -464,7 +465,10 @@ data class Playlist(val title: String, val items: List, val descriptio title = "Custom MediaSource", uri = "https://custom-media.ch/fondue", description = "Using a custom CustomMediaSource" - ) + ), + BlockedTimeRangeAssetLoader.DemoItemBlockedTimeRangeAtStartAndEnd, + BlockedTimeRangeAssetLoader.DemoItemBlockedTimeRangeOverlaps, + BlockedTimeRangeAssetLoader.DemoItemBlockedTimeRangeIncluded, ) ) 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 e34e2c45f..607e1aa61 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 @@ -11,6 +11,7 @@ import ch.srgssr.dataprovider.paging.DataProviderPaging import ch.srgssr.pillarbox.core.business.integrationlayer.service.IlHost import ch.srgssr.pillarbox.core.business.source.SRGAssetLoader import ch.srgssr.pillarbox.core.business.tracker.DefaultMediaItemTrackerRepository +import ch.srgssr.pillarbox.demo.shared.source.BlockedTimeRangeAssetLoader import ch.srgssr.pillarbox.demo.shared.source.CustomAssetLoader import ch.srgssr.pillarbox.demo.shared.ui.integrationLayer.data.ILRepository import ch.srgssr.pillarbox.player.PillarboxExoPlayer @@ -31,6 +32,7 @@ object PlayerModule { mediaSourceFactory = PillarboxMediaSourceFactory(context).apply { addAssetLoader(SRGAssetLoader(context)) addAssetLoader(CustomAssetLoader(context)) + addAssetLoader(BlockedTimeRangeAssetLoader(context)) }, mediaItemTrackerProvider = DefaultMediaItemTrackerRepository() ) diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/source/BlockedTimeRangeAssetLoader.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/source/BlockedTimeRangeAssetLoader.kt new file mode 100644 index 000000000..966480e0c --- /dev/null +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/source/BlockedTimeRangeAssetLoader.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.demo.shared.source + +import android.content.Context +import androidx.media3.common.MediaItem +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory +import ch.srgssr.pillarbox.demo.shared.data.DemoItem +import ch.srgssr.pillarbox.player.asset.Asset +import ch.srgssr.pillarbox.player.asset.AssetLoader +import ch.srgssr.pillarbox.player.asset.timeRange.BlockedTimeRange +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds + +/** + * An AssetLoader to demonstrate some edge cases with [BlockedTimeRange]. + */ +class BlockedTimeRangeAssetLoader(context: Context) : AssetLoader(DefaultMediaSourceFactory(context)) { + + override fun canLoadAsset(mediaItem: MediaItem): Boolean { + return mediaItem.localConfiguration?.uri?.toString()?.startsWith("blocked:") ?: false + } + + override suspend fun loadAsset(mediaItem: MediaItem): Asset { + val mediaId = mediaItem.mediaId + return Asset( + mediaSource = mediaSourceFactory.createMediaSource(MediaItem.fromUri(URL)), + blockedTimeRanges = createBlockedTimeRangesFromId(mediaId) + ) + } + + @Suppress("MagicNumber") + private fun createBlockedTimeRangesFromId(mediaId: String): List { + return when (mediaId) { + ID_START_END -> { + listOf( + BlockedTimeRange(0, 10_000L), + BlockedTimeRange(start = (videoDuration - 5.minutes).inWholeMilliseconds, end = videoDuration.inWholeMilliseconds) + ) + } + + ID_OVERLAP -> { + listOf( + BlockedTimeRange(10_000L, 50_000L), + BlockedTimeRange(15_000L, 5.minutes.inWholeMilliseconds) + ) + } + + ID_INCLUDED -> { + listOf( + BlockedTimeRange(15_000L, 30_000L, reason = "contained"), + BlockedTimeRange(10_000L, 60_000L, reason = "big"), + ) + } + + else -> emptyList() + } + } + + companion object { + private val URL = DemoItem.AppleBasic_16_9_TS_HLS.uri + private val videoDuration = 1800.05.seconds + + private const val ID_START_END = "blocked://StartEnd" + private const val ID_OVERLAP = "blocked://Overlap" + private const val ID_INCLUDED = "blocked://Included" + + /** + * DemoItem to test BlockedTimeRange at start and end of the media. + */ + val DemoItemBlockedTimeRangeAtStartAndEnd = DemoItem( + title = "Start and ends with a blocked time range", + uri = ID_START_END, + description = "Blocked times ranges at 00:00" + ) + /** + * DemoItem to test overlapping BlockedTimeRange. + */ + val DemoItemBlockedTimeRangeOverlaps = DemoItem( + title = "Blocked time range are overlapping", + uri = ID_OVERLAP, + description = "Two blocked time range are overlapping at 10s" + ) + + /** + * DemoItem to test included BlockedTimeRange + */ + val DemoItemBlockedTimeRangeIncluded = DemoItem( + title = "Blocked time range is included", + uri = ID_INCLUDED, + description = "One blocked time range is included inside the other" + ) + } +} From 1503527548ec3bcf3c856d73e7d2dfd7c319a7c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20Sta=CC=88hli?= Date: Wed, 8 May 2024 16:46:08 +0200 Subject: [PATCH 2/3] Fix endless seek when blocked time range occurs at the end. Drawback: The last frame of the video is visible in that case. --- .../java/ch/srgssr/pillarbox/player/tracker/TimeRangeTracker.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/TimeRangeTracker.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/TimeRangeTracker.kt index 5747b1508..27016a217 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/TimeRangeTracker.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/TimeRangeTracker.kt @@ -188,6 +188,8 @@ private class BlockedTimeRangeTracker( override fun onEvents(player: Player, events: Player.Events) { val blockedInterval = timeRanges.firstOrNullAtPosition(player.currentPosition) blockedInterval?.let { + // Ignore Blocked time range that end at the same time as the media. Otherwise infinite seek operations. + if (player.currentPosition >= player.duration) return@let callback(it) } } From 06c040b3ac6fc1706212706a9f9e4935c2934ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Fri, 10 May 2024 11:23:48 +0200 Subject: [PATCH 3/3] Update demo items --- .../source/BlockedTimeRangeAssetLoader.kt | 54 +++++++++++++------ .../player/tracker/TimeRangeTracker.kt | 2 +- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/source/BlockedTimeRangeAssetLoader.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/source/BlockedTimeRangeAssetLoader.kt index 966480e0c..0f8e1fa7f 100644 --- a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/source/BlockedTimeRangeAssetLoader.kt +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/source/BlockedTimeRangeAssetLoader.kt @@ -24,10 +24,9 @@ class BlockedTimeRangeAssetLoader(context: Context) : AssetLoader(DefaultMediaSo } override suspend fun loadAsset(mediaItem: MediaItem): Asset { - val mediaId = mediaItem.mediaId return Asset( mediaSource = mediaSourceFactory.createMediaSource(MediaItem.fromUri(URL)), - blockedTimeRanges = createBlockedTimeRangesFromId(mediaId) + blockedTimeRanges = createBlockedTimeRangesFromId(mediaItem.mediaId), ) } @@ -36,22 +35,42 @@ class BlockedTimeRangeAssetLoader(context: Context) : AssetLoader(DefaultMediaSo return when (mediaId) { ID_START_END -> { listOf( - BlockedTimeRange(0, 10_000L), - BlockedTimeRange(start = (videoDuration - 5.minutes).inWholeMilliseconds, end = videoDuration.inWholeMilliseconds) + BlockedTimeRange( + start = 0L, + end = 10.seconds.inWholeMilliseconds, + ), + BlockedTimeRange( + start = (videoDuration - 5.minutes).inWholeMilliseconds, + end = videoDuration.inWholeMilliseconds, + ) ) } ID_OVERLAP -> { listOf( - BlockedTimeRange(10_000L, 50_000L), - BlockedTimeRange(15_000L, 5.minutes.inWholeMilliseconds) + BlockedTimeRange( + start = 10.seconds.inWholeMilliseconds, + end = 50.seconds.inWholeMilliseconds, + ), + BlockedTimeRange( + start = 15.seconds.inWholeMilliseconds, + end = 5.minutes.inWholeMilliseconds, + ) ) } ID_INCLUDED -> { listOf( - BlockedTimeRange(15_000L, 30_000L, reason = "contained"), - BlockedTimeRange(10_000L, 60_000L, reason = "big"), + BlockedTimeRange( + start = 15.seconds.inWholeMilliseconds, + end = 30.seconds.inWholeMilliseconds, + reason = "contained", + ), + BlockedTimeRange( + start = 10.seconds.inWholeMilliseconds, + end = 1.minutes.inWholeMilliseconds, + reason = "big", + ), ) } @@ -68,29 +87,30 @@ class BlockedTimeRangeAssetLoader(context: Context) : AssetLoader(DefaultMediaSo private const val ID_INCLUDED = "blocked://Included" /** - * DemoItem to test BlockedTimeRange at start and end of the media. + * [DemoItem] to test [BlockedTimeRange] at start and end of the media. */ val DemoItemBlockedTimeRangeAtStartAndEnd = DemoItem( - title = "Start and ends with a blocked time range", + title = "Starts and ends with a blocked time range", uri = ID_START_END, - description = "Blocked times ranges at 00:00" + description = "Blocked times ranges at 00:00 - 00:10 and 25:00 - 30:00", ) + /** - * DemoItem to test overlapping BlockedTimeRange. + * [DemoItem] to test overlapping [BlockedTimeRange]. */ val DemoItemBlockedTimeRangeOverlaps = DemoItem( - title = "Blocked time range are overlapping", + title = "Blocked time ranges are overlapping", uri = ID_OVERLAP, - description = "Two blocked time range are overlapping at 10s" + description = "Blocked times ranges at 00:10 to 00:50 and 00:15 to 05:00" ) /** - * DemoItem to test included BlockedTimeRange + * [DemoItem] to test included [BlockedTimeRange]. */ val DemoItemBlockedTimeRangeIncluded = DemoItem( - title = "Blocked time range is included", + title = "Blocked time range is included in an other one", uri = ID_INCLUDED, - description = "One blocked time range is included inside the other" + description = "Blocked times ranges at 00:15 - 00:30 and 00:10 - 01:00" ) } } diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/TimeRangeTracker.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/TimeRangeTracker.kt index 27016a217..2589b246d 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/TimeRangeTracker.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/tracker/TimeRangeTracker.kt @@ -188,7 +188,7 @@ private class BlockedTimeRangeTracker( override fun onEvents(player: Player, events: Player.Events) { val blockedInterval = timeRanges.firstOrNullAtPosition(player.currentPosition) blockedInterval?.let { - // Ignore Blocked time range that end at the same time as the media. Otherwise infinite seek operations. + // Ignore blocked time ranges that end at the same time as the media. Otherwise infinite seek operations. if (player.currentPosition >= player.duration) return@let callback(it) }