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 a9ef160971..ba34fd709d 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
@@ -427,6 +427,7 @@ public final class DashMediaSource extends BaseMediaSource {
private static final String TAG = "DashMediaSource";
+ private final MediaItem originalMediaItem;
private final boolean sideloadedManifest;
private final DataSource.Factory manifestDataSourceFactory;
private final DashChunkSource.Factory chunkSourceFactory;
@@ -451,8 +452,7 @@ public final class DashMediaSource extends BaseMediaSource {
private IOException manifestFatalError;
private Handler handler;
- private MediaItem mediaItem;
- private MediaItem.PlaybackProperties playbackProperties;
+ private MediaItem updatedMediaItem;
private Uri manifestUri;
private Uri initialManifestUri;
private DashManifest manifest;
@@ -476,10 +476,10 @@ public final class DashMediaSource extends BaseMediaSource {
DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
long fallbackTargetLiveOffsetMs) {
- this.mediaItem = mediaItem;
- this.playbackProperties = checkNotNull(mediaItem.playbackProperties);
- this.manifestUri = playbackProperties.uri;
- this.initialManifestUri = playbackProperties.uri;
+ this.originalMediaItem = mediaItem;
+ this.updatedMediaItem = mediaItem;
+ this.manifestUri = checkNotNull(mediaItem.playbackProperties).uri;
+ this.initialManifestUri = mediaItem.playbackProperties.uri;
this.manifest = manifest;
this.manifestDataSourceFactory = manifestDataSourceFactory;
this.manifestParser = manifestParser;
@@ -531,12 +531,12 @@ public final class DashMediaSource extends BaseMediaSource {
@Override
@Nullable
public Object getTag() {
- return playbackProperties.tag;
+ return castNonNull(updatedMediaItem.playbackProperties).tag;
}
@Override
public MediaItem getMediaItem() {
- return mediaItem;
+ return updatedMediaItem;
}
@Override
@@ -692,9 +692,6 @@ public final class DashMediaSource extends BaseMediaSource {
staleManifestReloadAttempt = 0;
}
- mediaItem = mergeLiveConfiguration(mediaItem, fallbackTargetLiveOffsetMs, newManifest);
- playbackProperties = castNonNull(mediaItem.playbackProperties);
-
manifest = newManifest;
manifestLoadPending &= manifest.dynamic;
manifestLoadStartTimestampMs = elapsedRealtimeMs - loadDurationMs;
@@ -939,13 +936,13 @@ public final class DashMediaSource extends BaseMediaSource {
long windowDefaultStartPositionUs = 0;
if (manifest.dynamic) {
- ensureTargetLiveOffsetIsInLiveWindow(
+ updateMediaItemLiveConfiguration(
/* nowPeriodTimeUs= */ currentStartTimeUs + nowUnixTimeUs - C.msToUs(windowStartTimeMs),
/* windowStartPeriodTimeUs= */ currentStartTimeUs,
/* windowEndPeriodTimeUs= */ currentEndTimeUs);
windowDefaultStartPositionUs =
nowUnixTimeUs
- - C.msToUs(windowStartTimeMs + mediaItem.liveConfiguration.targetLiveOffsetMs);
+ - C.msToUs(windowStartTimeMs + updatedMediaItem.liveConfiguration.targetLiveOffsetMs);
long minimumDefaultStartPositionUs =
min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2);
if (windowDefaultStartPositionUs < minimumDefaultStartPositionUs) {
@@ -965,7 +962,7 @@ public final class DashMediaSource extends BaseMediaSource {
windowDurationUs,
windowDefaultStartPositionUs,
manifest,
- mediaItem);
+ updatedMediaItem);
refreshSourceInfo(timeline);
if (!sideloadedManifest) {
@@ -999,24 +996,81 @@ public final class DashMediaSource extends BaseMediaSource {
}
}
- private void ensureTargetLiveOffsetIsInLiveWindow(
+ private void updateMediaItemLiveConfiguration(
long nowPeriodTimeUs, long windowStartPeriodTimeUs, long windowEndPeriodTimeUs) {
- long targetLiveOffsetUs = C.msToUs(mediaItem.liveConfiguration.targetLiveOffsetMs);
- long minOffsetUs = nowPeriodTimeUs - windowEndPeriodTimeUs;
- if (targetLiveOffsetUs < minOffsetUs) {
- targetLiveOffsetUs = minOffsetUs;
+ long maxLiveOffsetMs;
+ if (originalMediaItem.liveConfiguration.maxLiveOffsetMs != C.TIME_UNSET) {
+ maxLiveOffsetMs = originalMediaItem.liveConfiguration.maxLiveOffsetMs;
+ } else if (manifest.serviceDescription != null
+ && manifest.serviceDescription.maxOffsetMs != C.TIME_UNSET) {
+ maxLiveOffsetMs = manifest.serviceDescription.maxOffsetMs;
+ } else {
+ maxLiveOffsetMs = C.usToMs(nowPeriodTimeUs - windowStartPeriodTimeUs);
}
- long maxOffsetUs = nowPeriodTimeUs - windowStartPeriodTimeUs;
- if (targetLiveOffsetUs > maxOffsetUs) {
+ long minLiveOffsetMs;
+ if (originalMediaItem.liveConfiguration.minLiveOffsetMs != C.TIME_UNSET) {
+ minLiveOffsetMs = originalMediaItem.liveConfiguration.minLiveOffsetMs;
+ } else if (manifest.serviceDescription != null
+ && manifest.serviceDescription.minOffsetMs != C.TIME_UNSET) {
+ minLiveOffsetMs = manifest.serviceDescription.minOffsetMs;
+ } else {
+ minLiveOffsetMs = C.usToMs(nowPeriodTimeUs - windowEndPeriodTimeUs);
+ 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);
+ }
+ }
+ long targetOffsetMs;
+ if (updatedMediaItem.liveConfiguration.targetLiveOffsetMs != C.TIME_UNSET) {
+ // Keep existing target offset even if the media configuration changes.
+ targetOffsetMs = updatedMediaItem.liveConfiguration.targetLiveOffsetMs;
+ } else if (manifest.serviceDescription != null
+ && manifest.serviceDescription.targetOffsetMs != C.TIME_UNSET) {
+ targetOffsetMs = manifest.serviceDescription.targetOffsetMs;
+ } else if (manifest.suggestedPresentationDelayMs != C.TIME_UNSET) {
+ targetOffsetMs = manifest.suggestedPresentationDelayMs;
+ } else {
+ targetOffsetMs = fallbackTargetLiveOffsetMs;
+ }
+ if (targetOffsetMs < minLiveOffsetMs) {
+ targetOffsetMs = minLiveOffsetMs;
+ }
+ if (targetOffsetMs > maxLiveOffsetMs) {
long windowDurationUs = windowEndPeriodTimeUs - windowStartPeriodTimeUs;
- targetLiveOffsetUs =
- maxOffsetUs - min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2);
+ long liveOffsetAtWindowStartUs = nowPeriodTimeUs - windowStartPeriodTimeUs;
+ long safeDistanceFromWindowStartUs =
+ min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2);
+ long maxTargetOffsetForSafeDistanceToWindowStartMs =
+ C.usToMs(liveOffsetAtWindowStartUs - safeDistanceFromWindowStartUs);
+ targetOffsetMs =
+ Util.constrainValue(
+ maxTargetOffsetForSafeDistanceToWindowStartMs, minLiveOffsetMs, maxLiveOffsetMs);
}
- long targetLiveOffsetMs = C.usToMs(targetLiveOffsetUs);
- if (mediaItem.liveConfiguration.targetLiveOffsetMs != targetLiveOffsetMs) {
- mediaItem = mediaItem.buildUpon().setLiveTargetOffsetMs(targetLiveOffsetMs).build();
- playbackProperties = castNonNull(mediaItem.playbackProperties);
+ float minPlaybackSpeed = C.RATE_UNSET;
+ if (originalMediaItem.liveConfiguration.minPlaybackSpeed != C.RATE_UNSET) {
+ minPlaybackSpeed = originalMediaItem.liveConfiguration.minPlaybackSpeed;
+ } else if (manifest.serviceDescription != null) {
+ minPlaybackSpeed = manifest.serviceDescription.minPlaybackSpeed;
}
+ float maxPlaybackSpeed = C.RATE_UNSET;
+ if (originalMediaItem.liveConfiguration.maxPlaybackSpeed != C.RATE_UNSET) {
+ maxPlaybackSpeed = originalMediaItem.liveConfiguration.maxPlaybackSpeed;
+ } else if (manifest.serviceDescription != null) {
+ maxPlaybackSpeed = manifest.serviceDescription.maxPlaybackSpeed;
+ }
+ updatedMediaItem =
+ originalMediaItem
+ .buildUpon()
+ .setLiveTargetOffsetMs(targetOffsetMs)
+ .setLiveMinOffsetMs(minLiveOffsetMs)
+ .setLiveMaxOffsetMs(maxLiveOffsetMs)
+ .setLiveMinPlaybackSpeed(minPlaybackSpeed)
+ .setLiveMaxPlaybackSpeed(maxPlaybackSpeed)
+ .build();
}
private void scheduleManifestRefresh(long delayUntilNextLoadMs) {
@@ -1087,41 +1141,6 @@ public final class DashMediaSource extends BaseMediaSource {
return LongMath.divide(intervalUs, 1000, RoundingMode.CEILING);
}
- private static MediaItem mergeLiveConfiguration(
- MediaItem mediaItem, long fallbackTargetLiveOffsetMs, DashManifest manifest) {
- // Evaluate live config properties from media item and manifest according to precedence.
- long liveTargetOffsetMs;
- if (mediaItem.liveConfiguration.targetLiveOffsetMs != C.TIME_UNSET) {
- liveTargetOffsetMs = mediaItem.liveConfiguration.targetLiveOffsetMs;
- } else if (manifest.serviceDescription != null
- && manifest.serviceDescription.targetOffsetMs != C.TIME_UNSET) {
- liveTargetOffsetMs = manifest.serviceDescription.targetOffsetMs;
- } else if (manifest.suggestedPresentationDelayMs != C.TIME_UNSET) {
- liveTargetOffsetMs = manifest.suggestedPresentationDelayMs;
- } else {
- liveTargetOffsetMs = fallbackTargetLiveOffsetMs;
- }
- float liveMinPlaybackSpeed = C.RATE_UNSET;
- if (mediaItem.liveConfiguration.minPlaybackSpeed != C.RATE_UNSET) {
- liveMinPlaybackSpeed = mediaItem.liveConfiguration.minPlaybackSpeed;
- } else if (manifest.serviceDescription != null) {
- liveMinPlaybackSpeed = manifest.serviceDescription.minPlaybackSpeed;
- }
- float liveMaxPlaybackSpeed = C.RATE_UNSET;
- if (mediaItem.liveConfiguration.maxPlaybackSpeed != C.RATE_UNSET) {
- liveMaxPlaybackSpeed = mediaItem.liveConfiguration.maxPlaybackSpeed;
- } else if (manifest.serviceDescription != null) {
- liveMaxPlaybackSpeed = manifest.serviceDescription.maxPlaybackSpeed;
- }
- // Update live configuration in the media item.
- return mediaItem
- .buildUpon()
- .setLiveTargetOffsetMs(liveTargetOffsetMs)
- .setLiveMinPlaybackSpeed(liveMinPlaybackSpeed)
- .setLiveMaxPlaybackSpeed(liveMaxPlaybackSpeed)
- .build();
- }
-
private static final class PeriodSeekInfo {
public static PeriodSeekInfo createPeriodSeekInfo(
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 a4b3c2c562..7e5beb7de4 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
@@ -55,8 +55,9 @@ public final class DashMediaSourceTest {
private static final String SAMPLE_MPD_LIVE_WITHOUT_LIVE_CONFIGURATION =
"media/mpd/sample_mpd_live_without_live_configuration";
- private static final String SAMPLE_MPD_LIVE_WITH_SUGGESTED_PRESENTATION_DELAY_2S =
- "media/mpd/sample_mpd_live_with_suggested_presentation_delay_2s";
+ private static final String
+ SAMPLE_MPD_LIVE_WITH_SUGGESTED_PRESENTATION_DELAY_2S_MIN_BUFFER_TIME_500MS =
+ "media/mpd/sample_mpd_live_with_suggested_presentation_delay_2s_min_buffer_time_500ms";
private static final String SAMPLE_MPD_LIVE_WITH_COMPLETE_SERVICE_DESCRIPTION =
"media/mpd/sample_mpd_live_with_complete_service_description";
private static final String SAMPLE_MPD_LIVE_WITH_OFFSET_INSIDE_WINDOW =
@@ -290,6 +291,8 @@ public final class DashMediaSourceTest {
assertThat(mediaItemFromSource.liveConfiguration.targetLiveOffsetMs)
.isEqualTo(DashMediaSource.DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS);
+ assertThat(mediaItemFromSource.liveConfiguration.minLiveOffsetMs).isEqualTo(0L);
+ assertThat(mediaItemFromSource.liveConfiguration.maxLiveOffsetMs).isEqualTo(58_000L);
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
}
@@ -306,6 +309,8 @@ public final class DashMediaSourceTest {
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
assertThat(mediaItemFromSource.liveConfiguration.targetLiveOffsetMs).isEqualTo(1234L);
+ assertThat(mediaItemFromSource.liveConfiguration.minLiveOffsetMs).isEqualTo(0L);
+ assertThat(mediaItemFromSource.liveConfiguration.maxLiveOffsetMs).isEqualTo(58_000L);
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
}
@@ -319,6 +324,8 @@ public final class DashMediaSourceTest {
.setLiveTargetOffsetMs(876L)
.setLiveMinPlaybackSpeed(23f)
.setLiveMaxPlaybackSpeed(42f)
+ .setLiveMinOffsetMs(500L)
+ .setLiveMaxOffsetMs(20_000L)
.build();
DashMediaSource mediaSource =
new DashMediaSource.Factory(
@@ -329,47 +336,58 @@ public final class DashMediaSourceTest {
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
assertThat(mediaItemFromSource.liveConfiguration.targetLiveOffsetMs).isEqualTo(876L);
+ assertThat(mediaItemFromSource.liveConfiguration.minLiveOffsetMs).isEqualTo(500L);
+ assertThat(mediaItemFromSource.liveConfiguration.maxLiveOffsetMs).isEqualTo(20_000L);
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(23f);
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(42f);
}
@Test
- public void prepare_withSuggestedPresentationDelay_usesManifestValue()
+ public void prepare_withSuggestedPresentationDelayAndMinBufferTime_usesManifestValue()
throws InterruptedException {
DashMediaSource mediaSource =
new DashMediaSource.Factory(
() ->
- createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITH_SUGGESTED_PRESENTATION_DELAY_2S))
+ createSampleMpdDataSource(
+ SAMPLE_MPD_LIVE_WITH_SUGGESTED_PRESENTATION_DELAY_2S_MIN_BUFFER_TIME_500MS))
.setFallbackTargetLiveOffsetMs(1234L)
.createMediaSource(MediaItem.fromUri(Uri.EMPTY));
MediaItem mediaItem = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
assertThat(mediaItem.liveConfiguration.targetLiveOffsetMs).isEqualTo(2_000L);
+ assertThat(mediaItem.liveConfiguration.minLiveOffsetMs).isEqualTo(500L);
+ assertThat(mediaItem.liveConfiguration.maxLiveOffsetMs).isEqualTo(58_000L);
assertThat(mediaItem.liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
assertThat(mediaItem.liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
}
@Test
- public void prepare_withSuggestedPresentationDelay_withMediaItemLiveProperties_usesMediaItem()
- throws InterruptedException {
+ public void
+ prepare_withSuggestedPresentationDelayAndMinBufferTime_withMediaItemLiveProperties_usesMediaItem()
+ throws InterruptedException {
MediaItem mediaItem =
new MediaItem.Builder()
.setUri(Uri.EMPTY)
.setLiveTargetOffsetMs(876L)
.setLiveMinPlaybackSpeed(23f)
.setLiveMaxPlaybackSpeed(42f)
+ .setLiveMinOffsetMs(200L)
+ .setLiveMaxOffsetMs(999L)
.build();
DashMediaSource mediaSource =
new DashMediaSource.Factory(
() ->
- createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITH_SUGGESTED_PRESENTATION_DELAY_2S))
+ createSampleMpdDataSource(
+ SAMPLE_MPD_LIVE_WITH_SUGGESTED_PRESENTATION_DELAY_2S_MIN_BUFFER_TIME_500MS))
.setFallbackTargetLiveOffsetMs(1234L)
.createMediaSource(mediaItem);
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
assertThat(mediaItemFromSource.liveConfiguration.targetLiveOffsetMs).isEqualTo(876L);
+ assertThat(mediaItem.liveConfiguration.minLiveOffsetMs).isEqualTo(200L);
+ assertThat(mediaItem.liveConfiguration.maxLiveOffsetMs).isEqualTo(999L);
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(23f);
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(42f);
}
@@ -386,6 +404,8 @@ public final class DashMediaSourceTest {
MediaItem mediaItem = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
assertThat(mediaItem.liveConfiguration.targetLiveOffsetMs).isEqualTo(4_000L);
+ assertThat(mediaItem.liveConfiguration.minLiveOffsetMs).isEqualTo(2_000L);
+ assertThat(mediaItem.liveConfiguration.maxLiveOffsetMs).isEqualTo(6_000L);
assertThat(mediaItem.liveConfiguration.minPlaybackSpeed).isEqualTo(0.96f);
assertThat(mediaItem.liveConfiguration.maxPlaybackSpeed).isEqualTo(1.04f);
}
@@ -399,6 +419,8 @@ public final class DashMediaSourceTest {
.setLiveTargetOffsetMs(876L)
.setLiveMinPlaybackSpeed(23f)
.setLiveMaxPlaybackSpeed(42f)
+ .setLiveMinOffsetMs(100L)
+ .setLiveMaxOffsetMs(999L)
.build();
DashMediaSource mediaSource =
new DashMediaSource.Factory(
@@ -409,6 +431,8 @@ public final class DashMediaSourceTest {
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
assertThat(mediaItemFromSource.liveConfiguration.targetLiveOffsetMs).isEqualTo(876L);
+ assertThat(mediaItemFromSource.liveConfiguration.minLiveOffsetMs).isEqualTo(100L);
+ assertThat(mediaItemFromSource.liveConfiguration.maxLiveOffsetMs).isEqualTo(999L);
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(23f);
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(42f);
}
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 f0e6e61255..f53268d38a 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
@@ -9,7 +9,7 @@
schemeIdUri="urn:mpeg:dash:utc:direct:2014"
value="2020-01-01T01:00:00Z" />
-
+
diff --git a/testdata/src/test/assets/media/mpd/sample_mpd_live_with_suggested_presentation_delay_2s b/testdata/src/test/assets/media/mpd/sample_mpd_live_with_suggested_presentation_delay_2s_min_buffer_time_500ms
similarity index 91%
rename from testdata/src/test/assets/media/mpd/sample_mpd_live_with_suggested_presentation_delay_2s
rename to testdata/src/test/assets/media/mpd/sample_mpd_live_with_suggested_presentation_delay_2s_min_buffer_time_500ms
index e79977ba79..efc7c5c21b 100644
--- a/testdata/src/test/assets/media/mpd/sample_mpd_live_with_suggested_presentation_delay_2s
+++ b/testdata/src/test/assets/media/mpd/sample_mpd_live_with_suggested_presentation_delay_2s_min_buffer_time_500ms
@@ -2,9 +2,10 @@
+ timeShiftBufferDepth="PT1M">