diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index d3a490b08b..d83e194e60 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -17,6 +17,8 @@ package com.google.android.exoplayer2.source.dash; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; +import static com.google.android.exoplayer2.util.Util.constrainValue; +import static com.google.android.exoplayer2.util.Util.usToMs; import static java.lang.Math.max; import static java.lang.Math.min; @@ -996,31 +998,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) { @@ -1041,9 +1055,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/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java index de6045f8e6..74ef8ce851 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaSourceTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/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/testdata/src/test/assets/media/mpd/sample_mpd_live_with_complete_service_description b/testdata/src/test/assets/media/mpd/sample_mpd_live_with_complete_service_description index f53268d38a..3912e3d155 100644 --- a/testdata/src/test/assets/media/mpd/sample_mpd_live_with_complete_service_description +++ b/testdata/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">