From 516a25fdf747209cbda8f26732ea25456794f9c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Fri, 10 May 2024 11:42:34 +0200 Subject: [PATCH] Custom blocked segments (#539) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gaƫtan Muller --- .../pillarbox/demo/shared/data/Playlist.kt | 6 +- .../pillarbox/demo/shared/di/PlayerModule.kt | 2 + .../source/BlockedTimeRangeAssetLoader.kt | 116 ++++++++++++++++++ .../player/tracker/TimeRangeTracker.kt | 2 + 4 files changed, 125 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..0f8e1fa7f --- /dev/null +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/source/BlockedTimeRangeAssetLoader.kt @@ -0,0 +1,116 @@ +/* + * 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 { + return Asset( + mediaSource = mediaSourceFactory.createMediaSource(MediaItem.fromUri(URL)), + blockedTimeRanges = createBlockedTimeRangesFromId(mediaItem.mediaId), + ) + } + + @Suppress("MagicNumber") + private fun createBlockedTimeRangesFromId(mediaId: String): List { + return when (mediaId) { + ID_START_END -> { + listOf( + BlockedTimeRange( + start = 0L, + end = 10.seconds.inWholeMilliseconds, + ), + BlockedTimeRange( + start = (videoDuration - 5.minutes).inWholeMilliseconds, + end = videoDuration.inWholeMilliseconds, + ) + ) + } + + ID_OVERLAP -> { + listOf( + BlockedTimeRange( + start = 10.seconds.inWholeMilliseconds, + end = 50.seconds.inWholeMilliseconds, + ), + BlockedTimeRange( + start = 15.seconds.inWholeMilliseconds, + end = 5.minutes.inWholeMilliseconds, + ) + ) + } + + ID_INCLUDED -> { + listOf( + BlockedTimeRange( + start = 15.seconds.inWholeMilliseconds, + end = 30.seconds.inWholeMilliseconds, + reason = "contained", + ), + BlockedTimeRange( + start = 10.seconds.inWholeMilliseconds, + end = 1.minutes.inWholeMilliseconds, + 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 = "Starts and ends with a blocked time range", + uri = ID_START_END, + description = "Blocked times ranges at 00:00 - 00:10 and 25:00 - 30:00", + ) + + /** + * [DemoItem] to test overlapping [BlockedTimeRange]. + */ + val DemoItemBlockedTimeRangeOverlaps = DemoItem( + title = "Blocked time ranges are overlapping", + uri = ID_OVERLAP, + description = "Blocked times ranges at 00:10 to 00:50 and 00:15 to 05:00" + ) + + /** + * [DemoItem] to test included [BlockedTimeRange]. + */ + val DemoItemBlockedTimeRangeIncluded = DemoItem( + title = "Blocked time range is included in an other one", + uri = ID_INCLUDED, + 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 5747b1508..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,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 ranges that end at the same time as the media. Otherwise infinite seek operations. + if (player.currentPosition >= player.duration) return@let callback(it) } }