Skip to content

Commit

Permalink
Set min/max supported live offset in DashMediaSource.
Browse files Browse the repository at this point in the history
In order to ensure we can update the values for new manifests but still use
the user provided override, we need to save the original and the updated MediaItem
seperately.

And in order to incorporate the existing logic for the min/max supported live
offset, which we already use to correct the target offset, also move both places
together so that all the adjustment happens in one place.

Logical adjustments to the previous min/max supported live offset:
 - Use the user-provided MediaItem values if set
 - Use the newly parsed ServiceDescription values if available.
 - Limit the minimum to 0 if the current time is in the window and we can
   assume to have low-latency stream.
 - Add minBufferTime from the manifest to ensure we don't reduce the live
   offset below this value.

Issue: #4904
PiperOrigin-RevId: 339452816
  • Loading branch information
tonihei authored and ojw28 committed Nov 2, 2020
1 parent 32a72fa commit aab6aef
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ public int[] getSupportedTypes() {

private static final String TAG = "DashMediaSource";

private final MediaItem originalMediaItem;
private final boolean sideloadedManifest;
private final DataSource.Factory manifestDataSourceFactory;
private final DashChunkSource.Factory chunkSourceFactory;
Expand All @@ -451,8 +452,7 @@ public int[] getSupportedTypes() {
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;
Expand All @@ -476,10 +476,10 @@ private DashMediaSource(
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;
Expand Down Expand Up @@ -531,12 +531,12 @@ public void replaceManifestUri(Uri manifestUri) {
@Override
@Nullable
public Object getTag() {
return playbackProperties.tag;
return castNonNull(updatedMediaItem.playbackProperties).tag;
}

@Override
public MediaItem getMediaItem() {
return mediaItem;
return updatedMediaItem;
}

@Override
Expand Down Expand Up @@ -692,9 +692,6 @@ protected void releaseSourceInternal() {
staleManifestReloadAttempt = 0;
}

mediaItem = mergeLiveConfiguration(mediaItem, fallbackTargetLiveOffsetMs, newManifest);
playbackProperties = castNonNull(mediaItem.playbackProperties);

manifest = newManifest;
manifestLoadPending &= manifest.dynamic;
manifestLoadStartTimestampMs = elapsedRealtimeMs - loadDurationMs;
Expand Down Expand Up @@ -939,13 +936,13 @@ private void processManifest(boolean scheduleRefresh) {

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) {
Expand All @@ -965,7 +962,7 @@ private void processManifest(boolean scheduleRefresh) {
windowDurationUs,
windowDefaultStartPositionUs,
manifest,
mediaItem);
updatedMediaItem);
refreshSourceInfo(timeline);

if (!sideloadedManifest) {
Expand Down Expand Up @@ -999,24 +996,81 @@ private void processManifest(boolean scheduleRefresh) {
}
}

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 windowDurationUs = windowEndPeriodTimeUs - windowStartPeriodTimeUs;
targetLiveOffsetUs =
maxOffsetUs - min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2);
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;
}
long targetLiveOffsetMs = C.usToMs(targetLiveOffsetUs);
if (mediaItem.liveConfiguration.targetLiveOffsetMs != targetLiveOffsetMs) {
mediaItem = mediaItem.buildUpon().setLiveTargetOffsetMs(targetLiveOffsetMs).build();
playbackProperties = castNonNull(mediaItem.playbackProperties);
if (targetOffsetMs > maxLiveOffsetMs) {
long windowDurationUs = windowEndPeriodTimeUs - windowStartPeriodTimeUs;
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);
}
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) {
Expand Down Expand Up @@ -1087,41 +1141,6 @@ private static long getIntervalUntilNextManifestRefreshMs(
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -290,6 +291,8 @@ public void prepare_withoutLiveConfiguration_withoutMediaItemLiveProperties_uses

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);
}
Expand All @@ -306,6 +309,8 @@ public void prepare_withoutLiveConfiguration_withoutMediaItemLiveProperties_uses
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);
}
Expand All @@ -319,6 +324,8 @@ public void prepare_withoutLiveConfiguration_withMediaItemLiveProperties_usesMed
.setLiveTargetOffsetMs(876L)
.setLiveMinPlaybackSpeed(23f)
.setLiveMaxPlaybackSpeed(42f)
.setLiveMinOffsetMs(500L)
.setLiveMaxOffsetMs(20_000L)
.build();
DashMediaSource mediaSource =
new DashMediaSource.Factory(
Expand All @@ -329,47 +336,58 @@ public void prepare_withoutLiveConfiguration_withMediaItemLiveProperties_usesMed
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);
}
Expand All @@ -386,6 +404,8 @@ public void prepare_withCompleteServiceDescription_usesManifestValue()
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);
}
Expand All @@ -399,6 +419,8 @@ public void prepare_withCompleteServiceDescription_withMediaItemLiveProperties_u
.setLiveTargetOffsetMs(876L)
.setLiveMinPlaybackSpeed(23f)
.setLiveMaxPlaybackSpeed(42f)
.setLiveMinOffsetMs(100L)
.setLiveMaxOffsetMs(999L)
.build();
DashMediaSource mediaSource =
new DashMediaSource.Factory(
Expand All @@ -409,6 +431,8 @@ public void prepare_withCompleteServiceDescription_withMediaItemLiveProperties_u
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
schemeIdUri="urn:mpeg:dash:utc:direct:2014"
value="2020-01-01T01:00:00Z" />
<ServiceDescription id="0">
<Latency target="4000" />
<Latency min="2000" target="4000" max="6000"/>
<PlaybackRate max="1.04" min="0.96" />
</ServiceDescription>
<Period start="PT0.0S">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
<MPD
type="dynamic"
suggestedPresentationDelay="PT2S"
minBufferTime="PT0.5S"
minimumUpdatePeriod="PT4M"
availabilityStartTime="2020-01-01T00:00:00Z"
timeShiftBufferDepth="PT6.0S">
timeShiftBufferDepth="PT1M">
<UTCTiming
schemeIdUri="urn:mpeg:dash:utc:direct:2014"
value="2020-01-01T01:00:00Z" />
Expand Down

0 comments on commit aab6aef

Please sign in to comment.