diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index 7659abe5f1..1f49bc22e2 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -2,6 +2,10 @@ { "name": "Clear DASH", "samples": [ + { + "name": "Test", + "uri": "https://01-str01-3201-prod.tv.cetin.rs/bpk-token/1AG@lk5yp2xm3d5rp5eik2lnd4r5y5s0h4d5lkhyurda/bpk-tv/RTS_1_2145/output1/manifest.mpd" + }, { "name": "HD (MP4, H264)", "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd" diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaSource.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaSource.java index af9d6ddbbd..e58e097f2c 100644 --- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaSource.java +++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaSource.java @@ -17,6 +17,8 @@ package androidx.media3.exoplayer.dash; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkState; +import static androidx.media3.common.util.Util.constrainValue; +import static androidx.media3.common.util.Util.usToMs; import static java.lang.Math.max; import static java.lang.Math.min; @@ -998,31 +1000,43 @@ public final class DashMediaSource extends BaseMediaSource { } private void updateMediaItemLiveConfiguration(long nowInWindowUs, long windowDurationUs) { - long maxLiveOffsetMs; + // Default maximum offset: start of window. + long maxPossibleLiveOffsetMs = usToMs(nowInWindowUs); + long maxLiveOffsetMs = maxPossibleLiveOffsetMs; + // Override maximum offset with user or media defined values if they are smaller. if (mediaItem.liveConfiguration.maxOffsetMs != C.TIME_UNSET) { - maxLiveOffsetMs = mediaItem.liveConfiguration.maxOffsetMs; + maxLiveOffsetMs = min(maxLiveOffsetMs, mediaItem.liveConfiguration.maxOffsetMs); } else if (manifest.serviceDescription != null && manifest.serviceDescription.maxOffsetMs != C.TIME_UNSET) { - maxLiveOffsetMs = manifest.serviceDescription.maxOffsetMs; - } else { - maxLiveOffsetMs = Util.usToMs(nowInWindowUs); + maxLiveOffsetMs = min(maxLiveOffsetMs, manifest.serviceDescription.maxOffsetMs); } - long minLiveOffsetMs; + // Default minimum offset: end of window. + long minLiveOffsetMs = usToMs(nowInWindowUs - windowDurationUs); + if (minLiveOffsetMs < 0 && maxLiveOffsetMs > 0) { + // The current time is in the window, so assume all clocks are synchronized and set the + // minimum to a live offset of zero. + minLiveOffsetMs = 0; + } + if (manifest.minBufferTimeMs != C.TIME_UNSET) { + // Ensure to leave one GOP as minimum and don't exceed the maximum possible offset. + minLiveOffsetMs = min(minLiveOffsetMs + manifest.minBufferTimeMs, maxPossibleLiveOffsetMs); + } + // Override minimum offset with user and media defined values if they are larger, but don't + // exceed the maximum possible offset. if (mediaItem.liveConfiguration.minOffsetMs != C.TIME_UNSET) { - minLiveOffsetMs = mediaItem.liveConfiguration.minOffsetMs; + minLiveOffsetMs = + constrainValue( + mediaItem.liveConfiguration.minOffsetMs, minLiveOffsetMs, maxPossibleLiveOffsetMs); } else if (manifest.serviceDescription != null && manifest.serviceDescription.minOffsetMs != C.TIME_UNSET) { - minLiveOffsetMs = manifest.serviceDescription.minOffsetMs; - } else { - minLiveOffsetMs = Util.usToMs(nowInWindowUs - windowDurationUs); - if (minLiveOffsetMs < 0 && maxLiveOffsetMs > 0) { - // The current time is in the window, so assume all clocks are synchronized and set the - // minimum to a live offset of zero. - minLiveOffsetMs = 0; - } - if (manifest.minBufferTimeMs != C.TIME_UNSET) { - minLiveOffsetMs = min(minLiveOffsetMs + manifest.minBufferTimeMs, maxLiveOffsetMs); - } + minLiveOffsetMs = + constrainValue( + manifest.serviceDescription.minOffsetMs, minLiveOffsetMs, maxPossibleLiveOffsetMs); + } + if (minLiveOffsetMs > maxLiveOffsetMs) { + // The values can be set by different sources and may disagree. Prefer the maximum offset + // under the assumption that it is safer for playback. + maxLiveOffsetMs = minLiveOffsetMs; } long targetOffsetMs; if (liveConfiguration.targetOffsetMs != C.TIME_UNSET) { @@ -1043,9 +1057,9 @@ public final class DashMediaSource extends BaseMediaSource { long safeDistanceFromWindowStartUs = min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2); long maxTargetOffsetForSafeDistanceToWindowStartMs = - Util.usToMs(nowInWindowUs - safeDistanceFromWindowStartUs); + usToMs(nowInWindowUs - safeDistanceFromWindowStartUs); targetOffsetMs = - Util.constrainValue( + constrainValue( maxTargetOffsetForSafeDistanceToWindowStartMs, minLiveOffsetMs, maxLiveOffsetMs); } float minPlaybackSpeed = C.RATE_UNSET; diff --git a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashMediaSourceTest.java b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashMediaSourceTest.java index 649ed8e83d..ef20c85225 100644 --- a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashMediaSourceTest.java +++ b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashMediaSourceTest.java @@ -353,7 +353,7 @@ public final class DashMediaSourceTest { .setTargetOffsetMs(876L) .setMinPlaybackSpeed(23f) .setMaxPlaybackSpeed(42f) - .setMinOffsetMs(200L) + .setMinOffsetMs(600L) .setMaxOffsetMs(999L) .build()) .build(); @@ -369,7 +369,7 @@ public final class DashMediaSourceTest { prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration; assertThat(liveConfiguration.targetOffsetMs).isEqualTo(876L); - assertThat(liveConfiguration.minOffsetMs).isEqualTo(200L); + assertThat(liveConfiguration.minOffsetMs).isEqualTo(600L); assertThat(liveConfiguration.maxOffsetMs).isEqualTo(999L); assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(23f); assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(42f); @@ -425,6 +425,33 @@ public final class DashMediaSourceTest { assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(42f); } + @Test + public void + prepare_withMinMaxOffsetOverridesOutsideOfLiveWindow_adjustsOverridesToBeWithinWindow() + throws Exception { + MediaItem mediaItem = + new MediaItem.Builder() + .setUri(Uri.EMPTY) + .setLiveConfiguration( + new MediaItem.LiveConfiguration.Builder() + .setMinOffsetMs(0L) + .setMaxOffsetMs(1_000_000_000L) + .build()) + .build(); + DashMediaSource mediaSource = + new DashMediaSource.Factory( + () -> + createSampleMpdDataSource( + SAMPLE_MPD_LIVE_WITH_SUGGESTED_PRESENTATION_DELAY_2S_MIN_BUFFER_TIME_500MS)) + .createMediaSource(mediaItem); + + MediaItem.LiveConfiguration liveConfiguration = + prepareAndWaitForTimelineRefresh(mediaSource).liveConfiguration; + + assertThat(liveConfiguration.minOffsetMs).isEqualTo(500L); + assertThat(liveConfiguration.maxOffsetMs).isEqualTo(58_000L); + } + @Test public void prepare_targetLiveOffsetInWindow_manifestTargetOffsetAndAlignedWindowStartPosition() throws InterruptedException { diff --git a/libraries/test_data/src/test/assets/media/mpd/sample_mpd_live_with_complete_service_description b/libraries/test_data/src/test/assets/media/mpd/sample_mpd_live_with_complete_service_description index f53268d38a..3912e3d155 100644 --- a/libraries/test_data/src/test/assets/media/mpd/sample_mpd_live_with_complete_service_description +++ b/libraries/test_data/src/test/assets/media/mpd/sample_mpd_live_with_complete_service_description @@ -4,7 +4,7 @@ suggestedPresentationDelay="PT2S" availabilityStartTime="2020-01-01T00:00:00Z" minimumUpdatePeriod="PT4M" - timeShiftBufferDepth="PT6.0S"> + timeShiftBufferDepth="PT8.0S">