mirror of
https://github.com/androidx/media.git
synced 2025-05-16 03:59:54 +08:00
Use low latency properties in DashMediaSource
Issue: #4904 PiperOrigin-RevId: 337046645
This commit is contained in:
parent
6f66e7d0ba
commit
41b58d503a
@ -16,6 +16,7 @@
|
||||
package com.google.android.exoplayer2.source.dash;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
@ -29,6 +30,7 @@ import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
@ -102,8 +104,8 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
@Nullable private DrmSessionManager drmSessionManager;
|
||||
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
||||
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||
private long livePresentationDelayMs;
|
||||
private boolean livePresentationDelayOverridesManifest;
|
||||
private long targetLiveOffsetOverrideMs;
|
||||
private long fallbackTargetLiveOffsetMs;
|
||||
@Nullable private ParsingLoadable.Parser<? extends DashManifest> manifestParser;
|
||||
private List<StreamKey> streamKeys;
|
||||
@Nullable private Object tag;
|
||||
@ -134,7 +136,8 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
this.manifestDataSourceFactory = manifestDataSourceFactory;
|
||||
mediaSourceDrmHelper = new MediaSourceDrmHelper();
|
||||
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
||||
livePresentationDelayMs = DEFAULT_LIVE_PRESENTATION_DELAY_MS;
|
||||
targetLiveOffsetOverrideMs = C.TIME_UNSET;
|
||||
fallbackTargetLiveOffsetMs = DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS;
|
||||
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
|
||||
streamKeys = Collections.emptyList();
|
||||
}
|
||||
@ -204,34 +207,31 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #setLivePresentationDelayMs(long, boolean)} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link MediaItem.Builder#setLiveTargetOffsetMs(long)} to override the
|
||||
* manifest, or {@link #setFallbackTargetLiveOffsetMs(long)} to provide a fallback value.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("deprecation")
|
||||
public Factory setLivePresentationDelayMs(long livePresentationDelayMs) {
|
||||
if (livePresentationDelayMs == DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS) {
|
||||
return setLivePresentationDelayMs(DEFAULT_LIVE_PRESENTATION_DELAY_MS, false);
|
||||
} else {
|
||||
return setLivePresentationDelayMs(livePresentationDelayMs, true);
|
||||
public Factory setLivePresentationDelayMs(
|
||||
long livePresentationDelayMs, boolean overridesManifest) {
|
||||
targetLiveOffsetOverrideMs = overridesManifest ? livePresentationDelayMs : C.TIME_UNSET;
|
||||
if (!overridesManifest) {
|
||||
setFallbackTargetLiveOffsetMs(livePresentationDelayMs);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration in milliseconds by which the default start position should precede the end
|
||||
* of the live window for live playbacks. The {@code overridesManifest} parameter specifies
|
||||
* whether the value is used in preference to one in the manifest, if present. The default value
|
||||
* is {@link #DEFAULT_LIVE_PRESENTATION_DELAY_MS}, and by default {@code overridesManifest} is
|
||||
* false.
|
||||
* Sets the {@link Player#getCurrentLiveOffset() target offset for live streams} that is used if
|
||||
* no value is defined in the {@link MediaItem} or the manifest.
|
||||
*
|
||||
* @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the
|
||||
* default start position should precede the end of the live window.
|
||||
* @param overridesManifest Whether the value is used in preference to one in the manifest, if
|
||||
* present.
|
||||
* <p>The default value is {@link #DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS}.
|
||||
*
|
||||
* @param fallbackTargetLiveOffsetMs The fallback live target offset in milliseconds.
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
public Factory setLivePresentationDelayMs(
|
||||
long livePresentationDelayMs, boolean overridesManifest) {
|
||||
this.livePresentationDelayMs = livePresentationDelayMs;
|
||||
this.livePresentationDelayOverridesManifest = overridesManifest;
|
||||
public Factory setFallbackTargetLiveOffsetMs(long fallbackTargetLiveOffsetMs) {
|
||||
this.fallbackTargetLiveOffsetMs = fallbackTargetLiveOffsetMs;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -306,12 +306,17 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
}
|
||||
boolean hasUri = mediaItem.playbackProperties != null;
|
||||
boolean hasTag = hasUri && mediaItem.playbackProperties.tag != null;
|
||||
boolean hasTargetLiveOffset = mediaItem.liveConfiguration.targetLiveOffsetMs != C.TIME_UNSET;
|
||||
mediaItem =
|
||||
mediaItem
|
||||
.buildUpon()
|
||||
.setMimeType(MimeTypes.APPLICATION_MPD)
|
||||
.setUri(hasUri ? mediaItem.playbackProperties.uri : Uri.EMPTY)
|
||||
.setTag(hasTag ? mediaItem.playbackProperties.tag : tag)
|
||||
.setLiveTargetOffsetMs(
|
||||
hasTargetLiveOffset
|
||||
? mediaItem.liveConfiguration.targetLiveOffsetMs
|
||||
: targetLiveOffsetOverrideMs)
|
||||
.setStreamKeys(streamKeys)
|
||||
.build();
|
||||
return new DashMediaSource(
|
||||
@ -323,8 +328,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
compositeSequenceableLoaderFactory,
|
||||
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
|
||||
loadErrorHandlingPolicy,
|
||||
livePresentationDelayMs,
|
||||
livePresentationDelayOverridesManifest);
|
||||
fallbackTargetLiveOffsetMs);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
|
||||
@ -365,12 +369,21 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
boolean needsTag = mediaItem.playbackProperties.tag == null && tag != null;
|
||||
boolean needsStreamKeys =
|
||||
mediaItem.playbackProperties.streamKeys.isEmpty() && !streamKeys.isEmpty();
|
||||
if (needsTag && needsStreamKeys) {
|
||||
mediaItem = mediaItem.buildUpon().setTag(tag).setStreamKeys(streamKeys).build();
|
||||
} else if (needsTag) {
|
||||
mediaItem = mediaItem.buildUpon().setTag(tag).build();
|
||||
} else if (needsStreamKeys) {
|
||||
mediaItem = mediaItem.buildUpon().setStreamKeys(streamKeys).build();
|
||||
boolean needsTargetLiveOffset =
|
||||
mediaItem.liveConfiguration.targetLiveOffsetMs == C.TIME_UNSET
|
||||
&& targetLiveOffsetOverrideMs != C.TIME_UNSET;
|
||||
if (needsTag || needsStreamKeys || needsTargetLiveOffset) {
|
||||
MediaItem.Builder builder = mediaItem.buildUpon();
|
||||
if (needsTag) {
|
||||
builder.setTag(tag);
|
||||
}
|
||||
if (needsStreamKeys) {
|
||||
builder.setStreamKeys(streamKeys);
|
||||
}
|
||||
if (needsTargetLiveOffset) {
|
||||
builder.setLiveTargetOffsetMs(targetLiveOffsetOverrideMs);
|
||||
}
|
||||
mediaItem = builder.build();
|
||||
}
|
||||
return new DashMediaSource(
|
||||
mediaItem,
|
||||
@ -381,8 +394,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
compositeSequenceableLoaderFactory,
|
||||
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
|
||||
loadErrorHandlingPolicy,
|
||||
livePresentationDelayMs,
|
||||
livePresentationDelayOverridesManifest);
|
||||
fallbackTargetLiveOffsetMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -392,17 +404,12 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
}
|
||||
|
||||
/**
|
||||
* The default presentation delay for live streams. The presentation delay is the duration by
|
||||
* which the default start position precedes the end of the live window.
|
||||
* The default target {@link Player#getCurrentLiveOffset() offset for live streams} that is used
|
||||
* if no value is defined in the {@link MediaItem} or the manifest.
|
||||
*/
|
||||
public static final long DEFAULT_LIVE_PRESENTATION_DELAY_MS = 30_000;
|
||||
/** @deprecated Use {@link #DEFAULT_LIVE_PRESENTATION_DELAY_MS}. */
|
||||
@Deprecated
|
||||
public static final long DEFAULT_LIVE_PRESENTATION_DELAY_FIXED_MS =
|
||||
DEFAULT_LIVE_PRESENTATION_DELAY_MS;
|
||||
/** @deprecated Use of this parameter is no longer necessary. */
|
||||
@Deprecated public static final long DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS = -1;
|
||||
|
||||
public static final long DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS = 30_000;
|
||||
/** @deprecated Use {@link #DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS} instead. */
|
||||
@Deprecated public static final long DEFAULT_LIVE_PRESENTATION_DELAY_MS = 30_000;
|
||||
/** The media id used by media items of dash media sources without a manifest URI. */
|
||||
public static final String DUMMY_MEDIA_ID =
|
||||
"com.google.android.exoplayer2.source.dash.DashMediaSource";
|
||||
@ -426,8 +433,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
||||
private final DrmSessionManager drmSessionManager;
|
||||
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||
private final long livePresentationDelayMs;
|
||||
private final boolean livePresentationDelayOverridesManifest;
|
||||
private final long fallbackTargetLiveOffsetMs;
|
||||
private final EventDispatcher manifestEventDispatcher;
|
||||
private final ParsingLoadable.Parser<? extends DashManifest> manifestParser;
|
||||
private final ManifestCallback manifestCallback;
|
||||
@ -437,8 +443,6 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
private final Runnable simulateManifestRefreshRunnable;
|
||||
private final PlayerEmsgCallback playerEmsgCallback;
|
||||
private final LoaderErrorThrower manifestLoadErrorThrower;
|
||||
private final MediaItem mediaItem;
|
||||
private final MediaItem.PlaybackProperties playbackProperties;
|
||||
|
||||
private DataSource dataSource;
|
||||
private Loader loader;
|
||||
@ -447,6 +451,8 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
private IOException manifestFatalError;
|
||||
private Handler handler;
|
||||
|
||||
private MediaItem mediaItem;
|
||||
private MediaItem.PlaybackProperties playbackProperties;
|
||||
private Uri manifestUri;
|
||||
private Uri initialManifestUri;
|
||||
private DashManifest manifest;
|
||||
@ -469,8 +475,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
|
||||
DrmSessionManager drmSessionManager,
|
||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
||||
long livePresentationDelayMs,
|
||||
boolean livePresentationDelayOverridesManifest) {
|
||||
long fallbackTargetLiveOffsetMs) {
|
||||
this.mediaItem = mediaItem;
|
||||
this.playbackProperties = checkNotNull(mediaItem.playbackProperties);
|
||||
this.manifestUri = playbackProperties.uri;
|
||||
@ -481,8 +486,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
this.chunkSourceFactory = chunkSourceFactory;
|
||||
this.drmSessionManager = drmSessionManager;
|
||||
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
||||
this.livePresentationDelayMs = livePresentationDelayMs;
|
||||
this.livePresentationDelayOverridesManifest = livePresentationDelayOverridesManifest;
|
||||
this.fallbackTargetLiveOffsetMs = fallbackTargetLiveOffsetMs;
|
||||
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
|
||||
sideloadedManifest = manifest != null;
|
||||
manifestEventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null);
|
||||
@ -688,6 +692,9 @@ 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;
|
||||
@ -921,23 +928,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
for (int i = 0; i < manifest.getPeriodCount() - 1; i++) {
|
||||
windowDurationUs += manifest.getPeriodDurationUs(i);
|
||||
}
|
||||
long windowDefaultStartPositionUs = 0;
|
||||
if (manifest.dynamic) {
|
||||
long presentationDelayForManifestMs = livePresentationDelayMs;
|
||||
if (!livePresentationDelayOverridesManifest
|
||||
&& manifest.suggestedPresentationDelayMs != C.TIME_UNSET) {
|
||||
presentationDelayForManifestMs = manifest.suggestedPresentationDelayMs;
|
||||
}
|
||||
// Snap the default position to the start of the segment containing it.
|
||||
windowDefaultStartPositionUs = windowDurationUs - C.msToUs(presentationDelayForManifestMs);
|
||||
if (windowDefaultStartPositionUs < MIN_LIVE_DEFAULT_START_POSITION_US) {
|
||||
// The default start position is too close to the start of the live window. Set it to the
|
||||
// minimum default start position provided the window is at least twice as big. Else set
|
||||
// it to the middle of the window.
|
||||
windowDefaultStartPositionUs =
|
||||
min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2);
|
||||
}
|
||||
}
|
||||
|
||||
long windowStartTimeMs = C.TIME_UNSET;
|
||||
if (manifest.availabilityStartTimeMs != C.TIME_UNSET) {
|
||||
windowStartTimeMs =
|
||||
@ -945,6 +936,25 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
+ manifest.getPeriod(0).startMs
|
||||
+ C.usToMs(currentStartTimeUs);
|
||||
}
|
||||
|
||||
long windowDefaultStartPositionUs = 0;
|
||||
if (manifest.dynamic) {
|
||||
ensureTargetLiveOffsetIsInLiveWindow(
|
||||
/* nowPeriodTimeUs= */ currentStartTimeUs + nowUnixTimeUs - C.msToUs(windowStartTimeMs),
|
||||
/* windowStartPeriodTimeUs= */ currentStartTimeUs,
|
||||
/* windowEndPeriodTimeUs= */ currentEndTimeUs);
|
||||
windowDefaultStartPositionUs =
|
||||
nowUnixTimeUs
|
||||
- C.msToUs(windowStartTimeMs + mediaItem.liveConfiguration.targetLiveOffsetMs);
|
||||
long minimumDefaultStartPositionUs =
|
||||
min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2);
|
||||
if (windowDefaultStartPositionUs < minimumDefaultStartPositionUs) {
|
||||
// The default start position is too close to the start of the live window. Set it to the
|
||||
// minimum default start position provided the window is at least twice as big. Else set
|
||||
// it to the middle of the window.
|
||||
windowDefaultStartPositionUs = minimumDefaultStartPositionUs;
|
||||
}
|
||||
}
|
||||
DashTimeline timeline =
|
||||
new DashTimeline(
|
||||
manifest.availabilityStartTimeMs,
|
||||
@ -989,6 +999,26 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureTargetLiveOffsetIsInLiveWindow(
|
||||
long nowPeriodTimeUs, long windowStartPeriodTimeUs, long windowEndPeriodTimeUs) {
|
||||
long targetLiveOffsetUs = C.msToUs(mediaItem.liveConfiguration.targetLiveOffsetMs);
|
||||
long minOffsetUs = nowPeriodTimeUs - windowEndPeriodTimeUs;
|
||||
if (targetLiveOffsetUs < minOffsetUs) {
|
||||
targetLiveOffsetUs = minOffsetUs;
|
||||
}
|
||||
long maxOffsetUs = nowPeriodTimeUs - windowStartPeriodTimeUs;
|
||||
if (targetLiveOffsetUs > maxOffsetUs) {
|
||||
long windowDurationUs = windowEndPeriodTimeUs - windowStartPeriodTimeUs;
|
||||
targetLiveOffsetUs =
|
||||
maxOffsetUs - min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2);
|
||||
}
|
||||
long targetLiveOffsetMs = C.usToMs(targetLiveOffsetUs);
|
||||
if (mediaItem.liveConfiguration.targetLiveOffsetMs != targetLiveOffsetMs) {
|
||||
mediaItem = mediaItem.buildUpon().setLiveTargetOffsetMs(targetLiveOffsetMs).build();
|
||||
playbackProperties = castNonNull(mediaItem.playbackProperties);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleManifestRefresh(long delayUntilNextLoadMs) {
|
||||
handler.postDelayed(refreshManifestRunnable, delayUntilNextLoadMs);
|
||||
}
|
||||
@ -1057,6 +1087,41 @@ 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(
|
||||
@ -1244,8 +1309,9 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
}
|
||||
// If there are multiple video adaptation sets with unaligned segments, the initial time may
|
||||
// not correspond to the start of a segment in both, but this is an edge case.
|
||||
DashSegmentIndex snapIndex = period.adaptationSets.get(videoAdaptationSetIndex)
|
||||
.representations.get(0).getIndex();
|
||||
@Nullable
|
||||
DashSegmentIndex snapIndex =
|
||||
period.adaptationSets.get(videoAdaptationSetIndex).representations.get(0).getIndex();
|
||||
if (snapIndex == null || snapIndex.getSegmentCount(periodDurationUs) == 0) {
|
||||
// Video adaptation set does not include a non-empty index for snapping.
|
||||
return windowDefaultStartPositionUs;
|
||||
|
@ -16,27 +16,56 @@
|
||||
package com.google.android.exoplayer2.source.dash;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.robolectric.annotation.LooperMode.Mode.PAUSED;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.Timeline.Window;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSource.MediaSourceCaller;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import com.google.android.exoplayer2.upstream.ByteArrayDataSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.FileDataSource;
|
||||
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
|
||||
/** Unit test for {@link DashMediaSource}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LooperMode(PAUSED)
|
||||
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_COMPLETE_SERVICE_DESCRIPTION =
|
||||
"media/mpd/sample_mpd_live_with_complete_service_description";
|
||||
private static final String SAMPLE_MPD_LIVE_WITH_OFFSET_INSIDE_WINDOW =
|
||||
"media/mpd/sample_mpd_live_with_offset_inside_window";
|
||||
private static final String SAMPLE_MPD_LIVE_WITH_OFFSET_TOO_SHORT =
|
||||
"media/mpd/sample_mpd_live_with_offset_too_short";
|
||||
private static final String SAMPLE_MPD_LIVE_WITH_OFFSET_TOO_LONG =
|
||||
"media/mpd/sample_mpd_live_with_offset_too_long";
|
||||
|
||||
@Test
|
||||
public void iso8601ParserParse() throws IOException {
|
||||
DashMediaSource.Iso8601Parser parser = new DashMediaSource.Iso8601Parser();
|
||||
@ -157,7 +186,7 @@ public final class DashMediaSourceTest {
|
||||
// Tests backwards compatibility
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void factorySetStreamKeys_withMediaItemStreamKeys_doesNotsOverrideMediaItemStreamKeys() {
|
||||
public void factorySetStreamKeys_withMediaItemStreamKeys_doesNotOverrideMediaItemStreamKeys() {
|
||||
StreamKey mediaItemStreamKey = new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 1);
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
@ -187,6 +216,280 @@ public final class DashMediaSourceTest {
|
||||
assertThat(mediaSource.getMediaItem()).isEqualTo(mediaItem);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void factorySetFallbackTargetLiveOffsetMs_withMediaLiveTargetOffsetMs_usesMediaOffset() {
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setLiveTargetOffsetMs(2L).build();
|
||||
DashMediaSource.Factory factory =
|
||||
new DashMediaSource.Factory(new FileDataSource.Factory())
|
||||
.setFallbackTargetLiveOffsetMs(1234L);
|
||||
|
||||
MediaItem dashMediaItem = factory.createMediaSource(mediaItem).getMediaItem();
|
||||
|
||||
assertThat(dashMediaItem.liveConfiguration.targetLiveOffsetMs).isEqualTo(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void factorySetLivePresentationDelayMs_withMediaLiveTargetOffset_usesMediaOffset() {
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).setLiveTargetOffsetMs(2L).build();
|
||||
DashMediaSource.Factory factory =
|
||||
new DashMediaSource.Factory(new FileDataSource.Factory())
|
||||
.setLivePresentationDelayMs(1234L, /* overridesManifest= */ true);
|
||||
|
||||
MediaItem dashMediaItem = factory.createMediaSource(mediaItem).getMediaItem();
|
||||
|
||||
assertThat(dashMediaItem.liveConfiguration.targetLiveOffsetMs).isEqualTo(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void factorySetLivePresentationDelayMs_overridingManifest_mixedIntoMediaItem() {
|
||||
MediaItem mediaItem = new MediaItem.Builder().setUri(Uri.EMPTY).build();
|
||||
DashMediaSource.Factory factory =
|
||||
new DashMediaSource.Factory(new FileDataSource.Factory())
|
||||
.setLivePresentationDelayMs(2000L, /* overridesManifest= */ true);
|
||||
|
||||
MediaItem dashMediaItem = factory.createMediaSource(mediaItem).getMediaItem();
|
||||
|
||||
assertThat(dashMediaItem.liveConfiguration.targetLiveOffsetMs).isEqualTo(2000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void factorySetLivePresentationDelayMs_notOverridingManifest_unsetInMediaItem() {
|
||||
MediaItem mediaItem = new MediaItem.Builder().setUri(Uri.EMPTY).build();
|
||||
DashMediaSource.Factory factory =
|
||||
new DashMediaSource.Factory(new FileDataSource.Factory())
|
||||
.setLivePresentationDelayMs(2000L, /* overridesManifest= */ false);
|
||||
|
||||
MediaItem dashMediaItem = factory.createMediaSource(mediaItem).getMediaItem();
|
||||
|
||||
assertThat(dashMediaItem.liveConfiguration.targetLiveOffsetMs).isEqualTo(C.TIME_UNSET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void factorySetFallbackTargetLiveOffsetMs_doesNotChangeMediaItem() {
|
||||
DashMediaSource.Factory factory =
|
||||
new DashMediaSource.Factory(new FileDataSource.Factory())
|
||||
.setFallbackTargetLiveOffsetMs(2000L);
|
||||
|
||||
MediaItem dashMediaItem =
|
||||
factory.createMediaSource(MediaItem.fromUri(Uri.EMPTY)).getMediaItem();
|
||||
|
||||
assertThat(dashMediaItem.liveConfiguration.targetLiveOffsetMs).isEqualTo(C.TIME_UNSET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepare_withoutLiveConfiguration_withoutMediaItemLiveProperties_usesDefaultFallback()
|
||||
throws InterruptedException {
|
||||
DashMediaSource mediaSource =
|
||||
new DashMediaSource.Factory(
|
||||
() -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITHOUT_LIVE_CONFIGURATION))
|
||||
.createMediaSource(MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
|
||||
assertThat(mediaItemFromSource.liveConfiguration.targetLiveOffsetMs)
|
||||
.isEqualTo(DashMediaSource.DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepare_withoutLiveConfiguration_withoutMediaItemLiveProperties_usesFallback()
|
||||
throws InterruptedException {
|
||||
DashMediaSource mediaSource =
|
||||
new DashMediaSource.Factory(
|
||||
() -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITHOUT_LIVE_CONFIGURATION))
|
||||
.setFallbackTargetLiveOffsetMs(1234L)
|
||||
.createMediaSource(MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
|
||||
assertThat(mediaItemFromSource.liveConfiguration.targetLiveOffsetMs).isEqualTo(1234L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepare_withoutLiveConfiguration_withMediaItemLiveProperties_usesMediaItem()
|
||||
throws InterruptedException {
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveTargetOffsetMs(876L)
|
||||
.setLiveMinPlaybackSpeed(23f)
|
||||
.setLiveMaxPlaybackSpeed(42f)
|
||||
.build();
|
||||
DashMediaSource mediaSource =
|
||||
new DashMediaSource.Factory(
|
||||
() -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITHOUT_LIVE_CONFIGURATION))
|
||||
.setFallbackTargetLiveOffsetMs(1234L)
|
||||
.createMediaSource(mediaItem);
|
||||
|
||||
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
|
||||
assertThat(mediaItemFromSource.liveConfiguration.targetLiveOffsetMs).isEqualTo(876L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(23f);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(42f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepare_withSuggestedPresentationDelay_usesManifestValue()
|
||||
throws InterruptedException {
|
||||
DashMediaSource mediaSource =
|
||||
new DashMediaSource.Factory(
|
||||
() ->
|
||||
createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITH_SUGGESTED_PRESENTATION_DELAY_2S))
|
||||
.setFallbackTargetLiveOffsetMs(1234L)
|
||||
.createMediaSource(MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
MediaItem mediaItem = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
|
||||
assertThat(mediaItem.liveConfiguration.targetLiveOffsetMs).isEqualTo(2_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 {
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveTargetOffsetMs(876L)
|
||||
.setLiveMinPlaybackSpeed(23f)
|
||||
.setLiveMaxPlaybackSpeed(42f)
|
||||
.build();
|
||||
DashMediaSource mediaSource =
|
||||
new DashMediaSource.Factory(
|
||||
() ->
|
||||
createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITH_SUGGESTED_PRESENTATION_DELAY_2S))
|
||||
.setFallbackTargetLiveOffsetMs(1234L)
|
||||
.createMediaSource(mediaItem);
|
||||
|
||||
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
|
||||
assertThat(mediaItemFromSource.liveConfiguration.targetLiveOffsetMs).isEqualTo(876L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(23f);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(42f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepare_withCompleteServiceDescription_usesManifestValue()
|
||||
throws InterruptedException {
|
||||
DashMediaSource mediaSource =
|
||||
new DashMediaSource.Factory(
|
||||
() -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITH_COMPLETE_SERVICE_DESCRIPTION))
|
||||
.setFallbackTargetLiveOffsetMs(1234L)
|
||||
.createMediaSource(MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
MediaItem mediaItem = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
|
||||
assertThat(mediaItem.liveConfiguration.targetLiveOffsetMs).isEqualTo(4_000L);
|
||||
assertThat(mediaItem.liveConfiguration.minPlaybackSpeed).isEqualTo(0.96f);
|
||||
assertThat(mediaItem.liveConfiguration.maxPlaybackSpeed).isEqualTo(1.04f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepare_withCompleteServiceDescription_withMediaItemLiveProperties_usesMediaItem()
|
||||
throws InterruptedException {
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveTargetOffsetMs(876L)
|
||||
.setLiveMinPlaybackSpeed(23f)
|
||||
.setLiveMaxPlaybackSpeed(42f)
|
||||
.build();
|
||||
DashMediaSource mediaSource =
|
||||
new DashMediaSource.Factory(
|
||||
() -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITH_COMPLETE_SERVICE_DESCRIPTION))
|
||||
.setFallbackTargetLiveOffsetMs(1234L)
|
||||
.createMediaSource(mediaItem);
|
||||
|
||||
MediaItem mediaItemFromSource = prepareAndWaitForTimelineRefresh(mediaSource).mediaItem;
|
||||
|
||||
assertThat(mediaItemFromSource.liveConfiguration.targetLiveOffsetMs).isEqualTo(876L);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.minPlaybackSpeed).isEqualTo(23f);
|
||||
assertThat(mediaItemFromSource.liveConfiguration.maxPlaybackSpeed).isEqualTo(42f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepare_targetLiveOffsetInWindow_manifestTargetOffsetAndAlignedWindowStartPosition()
|
||||
throws InterruptedException {
|
||||
DashMediaSource mediaSource =
|
||||
new DashMediaSource.Factory(
|
||||
() -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITH_OFFSET_INSIDE_WINDOW))
|
||||
.createMediaSource(MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
Window window = prepareAndWaitForTimelineRefresh(mediaSource);
|
||||
|
||||
// Expect the target live offset as defined in the manifest.
|
||||
assertThat(window.mediaItem.liveConfiguration.targetLiveOffsetMs).isEqualTo(3000);
|
||||
// Expect the default position at the first segment start before the live edge.
|
||||
assertThat(window.getDefaultPositionMs()).isEqualTo(2_000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepare_targetLiveOffsetTooLong_correctedTargetOffsetAndAlignedWindowStartPosition()
|
||||
throws InterruptedException {
|
||||
DashMediaSource mediaSource =
|
||||
new DashMediaSource.Factory(
|
||||
() -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITH_OFFSET_TOO_LONG))
|
||||
.createMediaSource(MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
Window window = prepareAndWaitForTimelineRefresh(mediaSource);
|
||||
|
||||
// Expect the default position at the first segment start below the minimum live start position.
|
||||
assertThat(window.getDefaultPositionMs()).isEqualTo(4_000);
|
||||
// Expect the target live offset reaching from now time to the minimum live start position.
|
||||
assertThat(window.mediaItem.liveConfiguration.targetLiveOffsetMs).isEqualTo(9000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepare_targetLiveOffsetTooShort_correctedTargetOffsetAndAlignedWindowStartPosition()
|
||||
throws InterruptedException {
|
||||
// Load manifest with now time far behind the start of the window.
|
||||
DashMediaSource mediaSource =
|
||||
new DashMediaSource.Factory(
|
||||
() -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITH_OFFSET_TOO_SHORT))
|
||||
.createMediaSource(MediaItem.fromUri(Uri.EMPTY));
|
||||
|
||||
Window window = prepareAndWaitForTimelineRefresh(mediaSource);
|
||||
|
||||
// Expect the default position at the start of the last segment.
|
||||
assertThat(window.getDefaultPositionMs()).isEqualTo(12_000);
|
||||
// Expect the target live offset reaching from now time to the end of the window.
|
||||
assertThat(window.mediaItem.liveConfiguration.targetLiveOffsetMs).isEqualTo(60_000 - 16_000);
|
||||
}
|
||||
|
||||
private static Window prepareAndWaitForTimelineRefresh(MediaSource mediaSource)
|
||||
throws InterruptedException {
|
||||
AtomicReference<Window> windowReference = new AtomicReference<>();
|
||||
CountDownLatch countDownLatch = new CountDownLatch(/* count= */ 1);
|
||||
MediaSourceCaller caller =
|
||||
(MediaSource source, Timeline timeline) -> {
|
||||
if (windowReference.get() == null) {
|
||||
windowReference.set(timeline.getWindow(0, new Timeline.Window()));
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
};
|
||||
mediaSource.prepareSource(caller, /* mediaTransferListener= */ null);
|
||||
while (!countDownLatch.await(/* timeout= */ 10, MILLISECONDS)) {
|
||||
ShadowLooper.idleMainLooper();
|
||||
}
|
||||
return windowReference.get();
|
||||
}
|
||||
|
||||
private static DataSource createSampleMpdDataSource(String fileName) {
|
||||
byte[] manifestData = new byte[0];
|
||||
try {
|
||||
manifestData = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), fileName);
|
||||
} catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
return new ByteArrayDataSource(manifestData);
|
||||
}
|
||||
|
||||
private static void assertParseStringToLong(
|
||||
long expected, ParsingLoadable.Parser<Long> parser, String data) throws IOException {
|
||||
long actual = parser.parse(null, new ByteArrayInputStream(Util.getUtf8Bytes(data)));
|
||||
|
27
testdata/src/test/assets/media/mpd/sample_mpd_live_with_complete_service_description
vendored
Normal file
27
testdata/src/test/assets/media/mpd/sample_mpd_live_with_complete_service_description
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MPD
|
||||
type="dynamic"
|
||||
suggestedPresentationDelay="PT2S"
|
||||
availabilityStartTime="2020-01-01T00:00:00Z"
|
||||
minimumUpdatePeriod="PT4M"
|
||||
timeShiftBufferDepth="PT6.0S">
|
||||
<UTCTiming
|
||||
schemeIdUri="urn:mpeg:dash:utc:direct:2014"
|
||||
value="2020-01-01T01:00:00Z" />
|
||||
<ServiceDescription id="0">
|
||||
<Latency target="4000" />
|
||||
<PlaybackRate max="1.04" min="0.96" />
|
||||
</ServiceDescription>
|
||||
<Period start="PT0.0S">
|
||||
<AdaptationSet contentType="video">
|
||||
<Representation id="0" mimeType="video/mp4">
|
||||
<SegmentTemplate
|
||||
timescale="1000000"
|
||||
duration="2000000"
|
||||
availabilityTimeOffset="2"
|
||||
startNumber="1">
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
24
testdata/src/test/assets/media/mpd/sample_mpd_live_with_offset_inside_window
vendored
Normal file
24
testdata/src/test/assets/media/mpd/sample_mpd_live_with_offset_inside_window
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MPD
|
||||
type="dynamic"
|
||||
minimumUpdatePeriod="PT4M"
|
||||
availabilityStartTime="2020-01-01T00:00:00Z"
|
||||
timeShiftBufferDepth="PT6.0S">
|
||||
<UTCTiming
|
||||
schemeIdUri="urn:mpeg:dash:utc:direct:2014"
|
||||
value="2020-01-01T01:00:00Z" />
|
||||
<ServiceDescription id="0">
|
||||
<Latency target="3000" />
|
||||
</ServiceDescription>
|
||||
<Period start="PT0.0S">
|
||||
<AdaptationSet contentType="video">
|
||||
<Representation id="0" mimeType="video/mp4">
|
||||
<SegmentTemplate
|
||||
timescale="1000000"
|
||||
duration="2000000"
|
||||
availabilityTimeOffset="2"
|
||||
startNumber="1"/>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
24
testdata/src/test/assets/media/mpd/sample_mpd_live_with_offset_too_long
vendored
Normal file
24
testdata/src/test/assets/media/mpd/sample_mpd_live_with_offset_too_long
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MPD
|
||||
type="dynamic"
|
||||
minimumUpdatePeriod="PT4M"
|
||||
availabilityStartTime="2020-01-01T00:00:00Z"
|
||||
timeShiftBufferDepth="PT16.0S">
|
||||
<UTCTiming
|
||||
schemeIdUri="urn:mpeg:dash:utc:direct:2014"
|
||||
value="2020-01-01T00:00:20Z" />
|
||||
<ServiceDescription id="0">
|
||||
<Latency target="30000" />
|
||||
</ServiceDescription>
|
||||
<Period start="PT0.0S">
|
||||
<AdaptationSet contentType="video">
|
||||
<Representation id="0" mimeType="video/mp4">
|
||||
<SegmentTemplate
|
||||
timescale="1000000"
|
||||
duration="2000000"
|
||||
availabilityTimeOffset="2"
|
||||
startNumber="1"/>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
25
testdata/src/test/assets/media/mpd/sample_mpd_live_with_offset_too_short
vendored
Normal file
25
testdata/src/test/assets/media/mpd/sample_mpd_live_with_offset_too_short
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MPD
|
||||
type="dynamic"
|
||||
timeShiftBufferDepth="PT16S"
|
||||
minimumUpdatePeriod="PT4M"
|
||||
availabilityStartTime="1970-01-01T00:00:00Z">
|
||||
<!-- Now is 60 seconds after the start of the window. -->
|
||||
<UTCTiming
|
||||
schemeIdUri="urn:mpeg:dash:utc:direct:2014"
|
||||
value="2020-01-01T00:01:00Z" />
|
||||
<ServiceDescription id="0">
|
||||
<Latency target="4000" />
|
||||
</ServiceDescription>
|
||||
<Period id="1" start="PT0S">
|
||||
<AdaptationSet id="0" contentType="video">
|
||||
<SegmentTemplate presentationTimeOffset="0" timescale="1000" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<!-- t = 2020-01-01T00:00:00Z (UTC) -->
|
||||
<S t="1577836800000" d="4000" r="3"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
<Representation id="0"/>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
23
testdata/src/test/assets/media/mpd/sample_mpd_live_with_suggested_presentation_delay_2s
vendored
Normal file
23
testdata/src/test/assets/media/mpd/sample_mpd_live_with_suggested_presentation_delay_2s
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MPD
|
||||
type="dynamic"
|
||||
suggestedPresentationDelay="PT2S"
|
||||
minimumUpdatePeriod="PT4M"
|
||||
availabilityStartTime="2020-01-01T00:00:00Z"
|
||||
timeShiftBufferDepth="PT6.0S">
|
||||
<UTCTiming
|
||||
schemeIdUri="urn:mpeg:dash:utc:direct:2014"
|
||||
value="2020-01-01T01:00:00Z" />
|
||||
<Period start="PT0.0S">
|
||||
<AdaptationSet contentType="video">
|
||||
<Representation id="0" mimeType="video/mp4">
|
||||
<SegmentTemplate
|
||||
timescale="1000000"
|
||||
duration="2000000"
|
||||
availabilityTimeOffset="2"
|
||||
startNumber="1">
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
22
testdata/src/test/assets/media/mpd/sample_mpd_live_without_live_configuration
vendored
Normal file
22
testdata/src/test/assets/media/mpd/sample_mpd_live_without_live_configuration
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MPD
|
||||
type="dynamic"
|
||||
minimumUpdatePeriod="PT4M"
|
||||
availabilityStartTime="2020-01-01T00:00:00Z"
|
||||
timeShiftBufferDepth="PT1M">
|
||||
<UTCTiming
|
||||
schemeIdUri="urn:mpeg:dash:utc:direct:2014"
|
||||
value="2020-01-01T01:00:00Z" />
|
||||
<Period start="PT0.0S">
|
||||
<AdaptationSet contentType="video">
|
||||
<Representation id="0" mimeType="video/mp4">
|
||||
<SegmentTemplate
|
||||
timescale="1000000"
|
||||
duration="2000000"
|
||||
availabilityTimeOffset="2"
|
||||
startNumber="1">
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
Loading…
x
Reference in New Issue
Block a user