Temporarily revert bounded live seeking. It's causing issues in its current form.

This commit is contained in:
Oliver Woodman 2015-05-28 17:11:28 +01:00
parent aa249e9f7f
commit ea29c71d94
4 changed files with 193 additions and 466 deletions

View file

@ -234,8 +234,7 @@ public class DashRendererBuilder implements RendererBuilder,
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher,
videoAdaptationSetIndex, videoRepresentationIndices, videoDataSource,
new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset,
mainHandler, player);
new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player,
DemoPlayer.TYPE_VIDEO);
@ -259,7 +258,7 @@ public class DashRendererBuilder implements RendererBuilder,
format.audioSamplingRate + "Hz)");
audioChunkSourceList.add(new DashChunkSource(manifestFetcher, audioAdaptationSetIndex,
new int[] {i}, audioDataSource, audioEvaluator, LIVE_EDGE_LATENCY_MS,
elapsedRealtimeOffset, mainHandler, player));
elapsedRealtimeOffset));
codecs.add(format.codecs);
}
@ -316,8 +315,7 @@ public class DashRendererBuilder implements RendererBuilder,
Representation representation = representations.get(j);
textTrackNameList.add(representation.format.id);
textChunkSourceList.add(new DashChunkSource(manifestFetcher, i, new int[] {j},
textDataSource, textEvaluator, LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset,
mainHandler, player));
textDataSource, textEvaluator, LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset));
}
}
}

View file

@ -27,7 +27,6 @@ import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.MultiTrackChunkSource;
import com.google.android.exoplayer.dash.DashChunkSource;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.hls.HlsSampleSource;
import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
@ -55,7 +54,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener,
HlsSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener,
StreamingDrmSessionManager.EventListener, DashChunkSource.EventListener, TextRenderer {
StreamingDrmSessionManager.EventListener, TextRenderer {
/**
* Builds renderers for the player.
@ -516,13 +515,6 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
processCues(cues);
}
@Override
public void onSeekRangeChanged(TimeRange seekRange) {
if (infoListener != null) {
infoListener.onSeekRangeChanged(seekRange);
}
}
/* package */ MetadataTrackRenderer.MetadataRenderer<Map<String, Object>>
getId3MetadataRenderer() {
return new MetadataTrackRenderer.MetadataRenderer<Map<String, Object>>() {

View file

@ -18,7 +18,6 @@ package com.google.android.exoplayer.dash;
import com.google.android.exoplayer.BehindLiveWindowException;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.TimeRange;
import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.Chunk;
@ -51,8 +50,6 @@ import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.SystemClock;
import android.os.Handler;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
@ -66,20 +63,6 @@ import java.util.List;
*/
public class DashChunkSource implements ChunkSource {
/**
* Interface definition for a callback to be notified of {@link DashChunkSource} events.
*/
public interface EventListener {
/**
* Invoked when the available seek range of the stream has changed.
*
* @param seekRange The range which specifies available content that can be seeked to.
*/
public void onSeekRangeChanged(TimeRange seekRange);
}
/**
* Thrown when an AdaptationSet is missing from the MPD.
*/
@ -96,9 +79,6 @@ public class DashChunkSource implements ChunkSource {
*/
public static final int USE_ALL_TRACKS = -1;
private final Handler eventHandler;
private final EventListener eventListener;
private final TrackInfo trackInfo;
private final DataSource dataSource;
private final FormatEvaluator formatEvaluator;
@ -121,11 +101,6 @@ public class DashChunkSource implements ChunkSource {
private boolean finishedCurrentManifest;
private DrmInitData drmInitData;
private TimeRange seekRange;
private long[] seekRangeValues;
private int firstAvailableSegmentNum;
private int lastAvailableSegmentNum;
private boolean lastChunkWasInitialization;
private IOException fatalError;
@ -167,7 +142,7 @@ public class DashChunkSource implements ChunkSource {
public DashChunkSource(MediaPresentationDescription manifest, int adaptationSetIndex,
int[] representationIndices, DataSource dataSource, FormatEvaluator formatEvaluator) {
this(null, manifest, adaptationSetIndex, representationIndices, dataSource, formatEvaluator,
new SystemClock(), 0, 0, null, null);
new SystemClock(), 0, 0);
}
/**
@ -192,24 +167,19 @@ public class DashChunkSource implements ChunkSource {
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
* as the server's unix time minus the local elapsed time. It unknown, set to 0.
* @param eventHandler A handler to use when delivering events to {@code EventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
int adaptationSetIndex, int[] representationIndices, DataSource dataSource,
FormatEvaluator formatEvaluator, long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs,
Handler eventHandler, EventListener eventListener) {
FormatEvaluator formatEvaluator, long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs) {
this(manifestFetcher, manifestFetcher.getManifest(), adaptationSetIndex, representationIndices,
dataSource, formatEvaluator, new SystemClock(), liveEdgeLatencyMs * 1000,
elapsedRealtimeOffsetMs * 1000, eventHandler, eventListener);
elapsedRealtimeOffsetMs * 1000);
}
/* package */ DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
MediaPresentationDescription initialManifest, int adaptationSetIndex,
int[] representationIndices, DataSource dataSource, FormatEvaluator formatEvaluator,
Clock systemClock, long liveEdgeLatencyUs, long elapsedRealtimeOffsetUs,
Handler eventHandler, EventListener eventListener) {
Clock systemClock, long liveEdgeLatencyUs, long elapsedRealtimeOffsetUs) {
this.manifestFetcher = manifestFetcher;
this.currentManifest = initialManifest;
this.adaptationSetIndex = adaptationSetIndex;
@ -219,11 +189,8 @@ public class DashChunkSource implements ChunkSource {
this.systemClock = systemClock;
this.liveEdgeLatencyUs = liveEdgeLatencyUs;
this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetUs;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.evaluation = new Evaluation();
this.headerBuilder = new StringBuilder();
this.seekRangeValues = new long[2];
drmInitData = getDrmInitData(currentManifest, adaptationSetIndex);
Representation[] representations = getFilteredRepresentations(currentManifest,
@ -262,11 +229,6 @@ public class DashChunkSource implements ChunkSource {
return trackInfo;
}
// VisibleForTesting
/* package */ TimeRange getSeekRange() {
return seekRange;
}
@Override
public void enable() {
fatalError = null;
@ -274,16 +236,6 @@ public class DashChunkSource implements ChunkSource {
if (manifestFetcher != null) {
manifestFetcher.enable();
}
DashSegmentIndex segmentIndex =
representationHolders.get(formats[0].id).representation.getIndex();
if (segmentIndex == null) {
seekRange = new TimeRange(TimeRange.TYPE_SNAPSHOT, 0, currentManifest.duration * 1000);
notifySeekRangeChanged(seekRange);
} else {
long nowUs = getNowUs();
updateAvailableSegmentBounds(segmentIndex, nowUs);
updateSeekRange(segmentIndex, nowUs);
}
}
@Override
@ -292,7 +244,6 @@ public class DashChunkSource implements ChunkSource {
if (manifestFetcher != null) {
manifestFetcher.disable();
}
seekRange = null;
}
@Override
@ -334,10 +285,6 @@ public class DashChunkSource implements ChunkSource {
}
currentManifest = newManifest;
finishedCurrentManifest = false;
long nowUs = getNowUs();
updateAvailableSegmentBounds(newRepresentations[0].getIndex(), nowUs);
updateSeekRange(newRepresentations[0].getIndex(), nowUs);
}
// TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where
@ -407,21 +354,36 @@ public class DashChunkSource implements ChunkSource {
return;
}
long nowUs;
if (elapsedRealtimeOffsetUs != 0) {
nowUs = (systemClock.elapsedRealtime() * 1000) + elapsedRealtimeOffsetUs;
} else {
nowUs = System.currentTimeMillis() * 1000;
}
int firstAvailableSegmentNum = segmentIndex.getFirstSegmentNum();
int lastAvailableSegmentNum = segmentIndex.getLastSegmentNum();
boolean indexUnbounded = lastAvailableSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED;
if (indexUnbounded) {
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
if (currentManifest.timeShiftBufferDepth != -1) {
long bufferDepthUs = currentManifest.timeShiftBufferDepth * 1000;
firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum,
segmentIndex.getSegmentNum(liveEdgeTimestampUs - bufferDepthUs));
}
// getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
// index of the last completed segment.
lastAvailableSegmentNum = segmentIndex.getSegmentNum(liveEdgeTimestampUs) - 1;
}
int segmentNum;
boolean indexUnbounded = segmentIndex.getLastSegmentNum() == DashSegmentIndex.INDEX_UNBOUNDED;
if (queue.isEmpty()) {
if (currentManifest.dynamic) {
seekRangeValues = seekRange.getCurrentBoundsUs(seekRangeValues);
seekPositionUs = Math.max(seekPositionUs, seekRangeValues[0]);
seekPositionUs = Math.min(seekPositionUs, seekRangeValues[1]);
seekPositionUs = getLiveSeekPosition(nowUs, indexUnbounded, segmentIndex.isExplicit());
}
segmentNum = segmentIndex.getSegmentNum(seekPositionUs);
// if the index is unbounded then the result of getSegmentNum isn't clamped to ensure that
// it doesn't exceed the last available segment. Clamp it here.
if (indexUnbounded) {
segmentNum = Math.min(segmentNum, lastAvailableSegmentNum);
}
} else {
MediaChunk previous = queue.get(out.queueSize - 1);
segmentNum = previous.isLastChunk ? -1
@ -490,59 +452,6 @@ public class DashChunkSource implements ChunkSource {
// Do nothing.
}
private void updateAvailableSegmentBounds(DashSegmentIndex segmentIndex, long nowUs) {
int indexFirstAvailableSegmentNum = segmentIndex.getFirstSegmentNum();
int indexLastAvailableSegmentNum = segmentIndex.getLastSegmentNum();
if (indexLastAvailableSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED) {
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
if (currentManifest.timeShiftBufferDepth != -1) {
long bufferDepthUs = currentManifest.timeShiftBufferDepth * 1000;
indexFirstAvailableSegmentNum = Math.max(indexFirstAvailableSegmentNum,
segmentIndex.getSegmentNum(liveEdgeTimestampUs - bufferDepthUs));
}
// getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
// index of the last completed segment.
indexLastAvailableSegmentNum = segmentIndex.getSegmentNum(liveEdgeTimestampUs) - 1;
}
firstAvailableSegmentNum = indexFirstAvailableSegmentNum;
lastAvailableSegmentNum = indexLastAvailableSegmentNum;
}
private void updateSeekRange(DashSegmentIndex segmentIndex, long nowUs) {
long earliestSeekPosition = segmentIndex.getTimeUs(firstAvailableSegmentNum);
long latestSeekPosition = segmentIndex.getTimeUs(lastAvailableSegmentNum)
+ segmentIndex.getDurationUs(lastAvailableSegmentNum);
if (currentManifest.dynamic) {
long liveEdgeTimestampUs;
if (segmentIndex.getLastSegmentNum() == DashSegmentIndex.INDEX_UNBOUNDED) {
liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
} else {
liveEdgeTimestampUs = segmentIndex.getTimeUs(segmentIndex.getLastSegmentNum())
+ segmentIndex.getDurationUs(segmentIndex.getLastSegmentNum());
if (!segmentIndex.isExplicit()) {
// Some segments defined by the index may not be available yet. Bound the calculated live
// edge based on the elapsed time since the manifest became available.
liveEdgeTimestampUs = Math.min(liveEdgeTimestampUs,
nowUs - currentManifest.availabilityStartTime * 1000);
}
}
// it's possible that the live edge latency actually puts our latest position before
// the earliest position in the case of a DVR-like stream that's just starting up, so
// in that case just return the earliest position instead
latestSeekPosition = Math.max(earliestSeekPosition, liveEdgeTimestampUs - liveEdgeLatencyUs);
}
TimeRange newSeekRange = new TimeRange(TimeRange.TYPE_SNAPSHOT, earliestSeekPosition,
latestSeekPosition);
if (seekRange == null || !seekRange.equals(newSeekRange)) {
seekRange = newSeekRange;
notifySeekRangeChanged(seekRange);
}
}
private static boolean mimeTypeIsWebm(String mimeType) {
return mimeType.startsWith(MimeTypes.VIDEO_WEBM) || mimeType.startsWith(MimeTypes.AUDIO_WEBM);
}
@ -603,12 +512,36 @@ public class DashChunkSource implements ChunkSource {
}
}
private long getNowUs() {
if (elapsedRealtimeOffsetUs != 0) {
return (systemClock.elapsedRealtime() * 1000) + elapsedRealtimeOffsetUs;
/**
* For live playbacks, determines the seek position that snaps playback to be
* {@link #liveEdgeLatencyUs} behind the live edge of the current manifest
*
* @param nowUs An estimate of the current server time, in microseconds.
* @param indexUnbounded True if the segment index for this source is unbounded. False otherwise.
* @param indexExplicit True if the segment index is explicit. False otherwise.
* @return The seek position in microseconds.
*/
private long getLiveSeekPosition(long nowUs, boolean indexUnbounded, boolean indexExplicit) {
long liveEdgeTimestampUs;
if (indexUnbounded) {
liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
} else {
return System.currentTimeMillis() * 1000;
liveEdgeTimestampUs = Long.MIN_VALUE;
for (RepresentationHolder representationHolder : representationHolders.values()) {
DashSegmentIndex segmentIndex = representationHolder.segmentIndex;
int lastSegmentNum = segmentIndex.getLastSegmentNum();
long indexLiveEdgeTimestampUs = segmentIndex.getTimeUs(lastSegmentNum)
+ segmentIndex.getDurationUs(lastSegmentNum);
liveEdgeTimestampUs = Math.max(liveEdgeTimestampUs, indexLiveEdgeTimestampUs);
}
if (!indexExplicit) {
// Some segments defined by the index may not be available yet. Bound the calculated live
// edge based on the elapsed time since the manifest became available.
liveEdgeTimestampUs = Math.min(liveEdgeTimestampUs,
nowUs - currentManifest.availabilityStartTime * 1000);
}
}
return liveEdgeTimestampUs - liveEdgeLatencyUs;
}
private static Representation[] getFilteredRepresentations(MediaPresentationDescription manifest,
@ -659,17 +592,6 @@ public class DashChunkSource implements ChunkSource {
Collections.singletonList(period));
}
private void notifySeekRangeChanged(final TimeRange seekRange) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onSeekRangeChanged(seekRange);
}
});
}
}
private static class RepresentationHolder {
public final Representation representation;

View file

@ -15,11 +15,9 @@
*/
package com.google.android.exoplayer.dash;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.TimeRange;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
import com.google.android.exoplayer.chunk.Format;
@ -57,19 +55,12 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
private static final FormatEvaluator EVALUATOR = new FixedEvaluator();
private static final long VOD_DURATION = 30000;
private static final long LIVE_SEGMENT_COUNT = 5;
private static final long LIVE_SEGMENT_DURATION_MS = 1000;
private static final long LIVE_TIMESHIFT_BUFFER_DEPTH_MS =
LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS;
private static final long AVAILABILITY_START_TIME_MS = 60000;
private static final long AVAILABILITY_REALTIME_OFFSET_MS = 1000;
private static final long AVAILABILITY_CURRENT_TIME_MS =
AVAILABILITY_START_TIME_MS + LIVE_TIMESHIFT_BUFFER_DEPTH_MS - AVAILABILITY_REALTIME_OFFSET_MS;
private static final long LIVE_SEEK_BEYOND_EDGE_MS = 60000;
private static final long AVAILABILITY_START_TIME = 0;
private static final long AVAILABILITY_LATENCY = 5000;
private static final long AVAILABILITY_REALTIME_OFFSET = 1000;
private static final long AVAILABILITY_CURRENT_TIME =
AVAILABILITY_START_TIME + AVAILABILITY_LATENCY - AVAILABILITY_REALTIME_OFFSET;
private static final FakeClock AVAILABILITY_CLOCK = new FakeClock(AVAILABILITY_CURRENT_TIME);
private static final int TALL_HEIGHT = 200;
private static final int WIDE_WIDTH = 400;
@ -99,21 +90,6 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
assertEquals(TALL_HEIGHT, out.getMaxVideoHeight());
}
public void testGetSeekRangeOnVod() {
DashChunkSource chunkSource = new DashChunkSource(generateVodMpd(), AdaptationSet.TYPE_VIDEO,
null, null, mock(FormatEvaluator.class));
chunkSource.enable();
TimeRange seekRange = chunkSource.getSeekRange();
long[] seekRangeValuesUs = seekRange.getCurrentBoundsUs(null);
assertEquals(0, seekRangeValuesUs[0]);
assertEquals(VOD_DURATION * 1000, seekRangeValuesUs[1]);
long[] seekRangeValuesMs = seekRange.getCurrentBoundsMs(null);
assertEquals(0, seekRangeValuesMs[0]);
assertEquals(VOD_DURATION, seekRangeValuesMs[1]);
}
public void testMaxVideoDimensionsLegacy() {
SingleSegmentBase segmentBase1 = new SingleSegmentBase("https://example.com/1.mp4");
Representation representation1 =
@ -131,254 +107,147 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
assertEquals(TALL_HEIGHT, out.getMaxVideoHeight());
}
public void testLiveEdgeNoLatency() {
long startTimeMs = 0;
long liveEdgeLatencyMs = 0;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 0;
long seekRangeEndMs = LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 4000;
long chunkEndTimeMs = 5000;
public void testLiveEdgeNoLatencyWithTimeline() {
DashChunkSource chunkSource = setupLiveEdgeTimelineTest(0L);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, 0, 0, out);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
}
public void testLiveEdgeAlmostNoLatency() {
long startTimeMs = 0;
long liveEdgeLatencyMs = 1;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 0;
long seekRangeEndMs = LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 4000;
long chunkEndTimeMs = 5000;
public void testLiveEdge500msLatencyWithTimeline() {
DashChunkSource chunkSource = setupLiveEdgeTimelineTest(500L);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, 0, 0, out);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
}
public void testLiveEdge500msLatency() {
long startTimeMs = 0;
long liveEdgeLatencyMs = 500;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 0;
long seekRangeEndMs = LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 4000;
long chunkEndTimeMs = 5000;
public void testLiveEdge1000msLatencyWithTimeline() {
DashChunkSource chunkSource = setupLiveEdgeTimelineTest(1000L);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, 0, 0, out);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
}
public void testLiveEdge1000msLatency() {
long startTimeMs = 0;
long liveEdgeLatencyMs = 1000;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 0;
long seekRangeEndMs = LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 4000;
long chunkEndTimeMs = 5000;
public void testLiveEdge1001msLatencyWithTimeline() {
DashChunkSource chunkSource = setupLiveEdgeTimelineTest(1001L);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, 0, 0, out);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
assertEquals(3000000L, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(4000000L, ((MediaChunk) out.chunk).endTimeUs);
}
public void testLiveEdge1001msLatency() {
long startTimeMs = 0;
long liveEdgeLatencyMs = 1001;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 0;
long seekRangeEndMs = LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 3000;
long chunkEndTimeMs = 4000;
public void testLiveEdge2500msLatencyWithTimeline() {
DashChunkSource chunkSource = setupLiveEdgeTimelineTest(2500L);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, 0, 0, out);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
assertEquals(2000000L, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(3000000L, ((MediaChunk) out.chunk).endTimeUs);
}
public void testLiveEdge2500msLatency() {
long startTimeMs = 0;
long liveEdgeLatencyMs = 2500;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 0;
long seekRangeEndMs = LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 2000;
long chunkEndTimeMs = 3000;
public void testLiveEdgeVeryHighLatencyWithTimeline() {
DashChunkSource chunkSource = setupLiveEdgeTimelineTest(10000L);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, 0, 0, out);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
assertEquals(0L, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(1000000L, ((MediaChunk) out.chunk).endTimeUs);
}
public void testLiveEdgeVeryHighLatency() {
long startTimeMs = 0;
long liveEdgeLatencyMs = 10000;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 0;
long seekRangeEndMs = 0;
long chunkStartTimeMs = 0;
long chunkEndTimeMs = 1000;
public void testLiveEdgeNoLatencyWithTemplate() {
DashChunkSource chunkSource = setupLiveEdgeTemplateTest(0L);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, 0, 0, out);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
// this should actually return the "5th" segment, but it currently returns the "6th", which
// doesn't actually exist yet; this will be resolved in a subsequent cl (cl/87518875).
//assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
//assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
}
public void testLiveEdgeNoLatencyInProgress() {
long startTimeMs = 3000;
long liveEdgeLatencyMs = 0;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 3000;
long seekRangeEndMs = 3000 + LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 7000;
long chunkEndTimeMs = 8000;
public void testLiveEdgeAlmostNoLatencyWithTemplate() {
DashChunkSource chunkSource = setupLiveEdgeTemplateTest(1L);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, 0, 0, out);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
}
public void testLiveEdgeAlmostNoLatencyInProgress() {
long startTimeMs = 3000;
long liveEdgeLatencyMs = 1;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 3000;
long seekRangeEndMs = 3000 + LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 7000;
long chunkEndTimeMs = 8000;
public void testLiveEdge500msLatencyWithTemplate() {
DashChunkSource chunkSource = setupLiveEdgeTemplateTest(500L);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, 0, 0, out);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
}
public void testLiveEdge500msLatencyInProgress() {
long startTimeMs = 3000;
long liveEdgeLatencyMs = 500;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 3000;
long seekRangeEndMs = 3000 + LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 7000;
long chunkEndTimeMs = 8000;
public void testLiveEdge1000msLatencyWithTemplate() {
DashChunkSource chunkSource = setupLiveEdgeTemplateTest(1000L);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, 0, 0, out);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
assertEquals(4000000L, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(5000000L, ((MediaChunk) out.chunk).endTimeUs);
}
public void testLiveEdge1000msLatencyInProgress() {
long startTimeMs = 3000;
long liveEdgeLatencyMs = 1000;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 3000;
long seekRangeEndMs = 3000 + LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 7000;
long chunkEndTimeMs = 8000;
public void testLiveEdge1001msLatencyWithTemplate() {
DashChunkSource chunkSource = setupLiveEdgeTemplateTest(1001L);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, 0, 0, out);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
assertEquals(3000000L, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(4000000L, ((MediaChunk) out.chunk).endTimeUs);
}
public void testLiveEdge1001msLatencyInProgress() {
long startTimeMs = 3000;
long liveEdgeLatencyMs = 1001;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 3000;
long seekRangeEndMs = 3000 + LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 6000;
long chunkEndTimeMs = 7000;
public void testLiveEdge2500msLatencyWithTemplate() {
DashChunkSource chunkSource = setupLiveEdgeTemplateTest(2500L);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, 0, 0, out);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
assertEquals(2000000L, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(3000000L, ((MediaChunk) out.chunk).endTimeUs);
}
public void testLiveEdge2500msLatencyInProgress() {
long startTimeMs = 3000;
long liveEdgeLatencyMs = 2500;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 3000;
long seekRangeEndMs = 3000 + LIVE_SEGMENT_COUNT * LIVE_SEGMENT_DURATION_MS - liveEdgeLatencyMs;
long chunkStartTimeMs = 5000;
long chunkEndTimeMs = 6000;
public void testLiveEdgeVeryHighLatencyWithTemplate() {
DashChunkSource chunkSource = setupLiveEdgeTemplateTest(10000L);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, 0, 0, out);
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
}
public void testLiveEdgeVeryHighLatencyInProgress() {
long startTimeMs = 3000;
long liveEdgeLatencyMs = 10000;
long seekPositionMs = LIVE_SEEK_BEYOND_EDGE_MS;
long seekRangeStartMs = 3000;
long seekRangeEndMs = 3000;
long chunkStartTimeMs = 3000;
long chunkEndTimeMs = 4000;
checkLiveEdgeLatencyWithTimeline(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, 0, 0, 1000);
checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(startTimeMs, liveEdgeLatencyMs,
seekPositionMs, seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs);
assertEquals(0L, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(1000000L, ((MediaChunk) out.chunk).endTimeUs);
}
private static MediaPresentationDescription generateMpd(boolean live,
List<Representation> representations, boolean limitTimeshiftBuffer) {
List<Representation> representations) {
Representation firstRepresentation = representations.get(0);
AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_UNKNOWN, representations);
Period period = new Period(null, firstRepresentation.periodStartMs,
firstRepresentation.periodDurationMs, Collections.singletonList(adaptationSet));
long duration = (live) ? TrackRenderer.UNKNOWN_TIME_US
: firstRepresentation.periodDurationMs - firstRepresentation.periodStartMs;
return new MediaPresentationDescription(AVAILABILITY_START_TIME_MS, duration, -1, live, -1,
(limitTimeshiftBuffer) ? LIVE_TIMESHIFT_BUFFER_DEPTH_MS : -1,
return new MediaPresentationDescription(AVAILABILITY_START_TIME, duration, -1, live, -1, -1,
null, Collections.singletonList(period));
}
@ -387,126 +256,72 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
SingleSegmentBase segmentBase1 = new SingleSegmentBase("https://example.com/1.mp4");
Representation representation1 =
Representation.newInstance(0, VOD_DURATION, null, 0, TALL_VIDEO, segmentBase1);
Representation.newInstance(0, 0, null, 0, TALL_VIDEO, segmentBase1);
representations.add(representation1);
SingleSegmentBase segmentBase2 = new SingleSegmentBase("https://example.com/2.mp4");
Representation representation2 =
Representation.newInstance(0, VOD_DURATION, null, 0, WIDE_VIDEO, segmentBase2);
Representation.newInstance(0, 0, null, 0, WIDE_VIDEO, segmentBase2);
representations.add(representation2);
return generateMpd(false, representations, false);
return generateMpd(false, representations);
}
private static MediaPresentationDescription generateLiveMpdWithTimeline(long startTime) {
private static MediaPresentationDescription generateLiveMpdWithTimeline() {
List<Representation> representations = new ArrayList<>();
List<SegmentTimelineElement> segmentTimeline = new ArrayList<>();
segmentTimeline.add(new SegmentTimelineElement(0L, 1000L));
segmentTimeline.add(new SegmentTimelineElement(1000L, 1000L));
segmentTimeline.add(new SegmentTimelineElement(2000L, 1000L));
segmentTimeline.add(new SegmentTimelineElement(3000L, 1000L));
segmentTimeline.add(new SegmentTimelineElement(4000L, 1000L));
List<RangedUri> mediaSegments = new ArrayList<>();
long byteStart = 0;
for (int i = 0; i < LIVE_SEGMENT_COUNT; i++) {
segmentTimeline.add(new SegmentTimelineElement(startTime, LIVE_SEGMENT_DURATION_MS));
mediaSegments.add(new RangedUri("", "", byteStart, 500L));
startTime += LIVE_SEGMENT_DURATION_MS;
byteStart += 500;
}
mediaSegments.add(new RangedUri("", "", 0L, 500L));
mediaSegments.add(new RangedUri("", "", 500L, 500L));
mediaSegments.add(new RangedUri("", "", 1000L, 500L));
mediaSegments.add(new RangedUri("", "", 1500L, 500L));
mediaSegments.add(new RangedUri("", "", 2000L, 500L));
MultiSegmentBase segmentBase = new SegmentList(null, 1000, 0,
TrackRenderer.UNKNOWN_TIME_US, 0, TrackRenderer.UNKNOWN_TIME_US, segmentTimeline,
TrackRenderer.UNKNOWN_TIME_US, 1, TrackRenderer.UNKNOWN_TIME_US, segmentTimeline,
mediaSegments);
Representation representation = Representation.newInstance(startTime,
TrackRenderer.UNKNOWN_TIME_US, null, 0, REGULAR_VIDEO, segmentBase);
Representation representation = Representation.newInstance(0, TrackRenderer.UNKNOWN_TIME_US,
null, 0, REGULAR_VIDEO, segmentBase);
representations.add(representation);
return generateMpd(true, representations, false);
return generateMpd(true, representations);
}
private static MediaPresentationDescription generateLiveMpdWithTemplate(
boolean limitTimeshiftBuffer) {
private static MediaPresentationDescription generateLiveMpdWithTemplate() {
List<Representation> representations = new ArrayList<>();
UrlTemplate initializationTemplate = null;
UrlTemplate mediaTemplate = UrlTemplate.compile("$RepresentationID$/$Number$");
MultiSegmentBase segmentBase = new SegmentTemplate(null, 1000, 0,
TrackRenderer.UNKNOWN_TIME_US, 0, LIVE_SEGMENT_DURATION_MS, null,
TrackRenderer.UNKNOWN_TIME_US, 1, 1000, null,
initializationTemplate, mediaTemplate, "http://www.youtube.com");
Representation representation = Representation.newInstance(0, TrackRenderer.UNKNOWN_TIME_US,
null, 0, REGULAR_VIDEO, segmentBase);
representations.add(representation);
return generateMpd(true, representations, limitTimeshiftBuffer);
return generateMpd(true, representations);
}
private DashChunkSource setupLiveEdgeTimelineTest(long startTime, long liveEdgeLatencyMs) {
MediaPresentationDescription manifest = generateLiveMpdWithTimeline(startTime);
private DashChunkSource setupLiveEdgeTimelineTest(long liveEdgeLatencyMs) {
MediaPresentationDescription manifest = generateLiveMpdWithTimeline();
when(mockManifestFetcher.getManifest()).thenReturn(manifest);
DashChunkSource chunkSource = new DashChunkSource(mockManifestFetcher, manifest,
AdaptationSet.TYPE_VIDEO, null, mockDataSource, EVALUATOR,
new FakeClock(AVAILABILITY_CURRENT_TIME_MS + startTime), liveEdgeLatencyMs * 1000,
AVAILABILITY_REALTIME_OFFSET_MS * 1000, null, null);
chunkSource.enable();
return chunkSource;
return new DashChunkSource(mockManifestFetcher, manifest, AdaptationSet.TYPE_VIDEO, null,
mockDataSource, EVALUATOR, AVAILABILITY_CLOCK, liveEdgeLatencyMs * 1000,
AVAILABILITY_REALTIME_OFFSET * 1000);
}
private DashChunkSource setupLiveEdgeTemplateTest(long startTime, long liveEdgeLatencyMs,
boolean limitTimeshiftBuffer) {
MediaPresentationDescription manifest = generateLiveMpdWithTemplate(limitTimeshiftBuffer);
private DashChunkSource setupLiveEdgeTemplateTest(long liveEdgeLatencyMs) {
MediaPresentationDescription manifest = generateLiveMpdWithTemplate();
when(mockManifestFetcher.getManifest()).thenReturn(manifest);
DashChunkSource chunkSource = new DashChunkSource(mockManifestFetcher, manifest,
AdaptationSet.TYPE_VIDEO, null, mockDataSource, EVALUATOR,
new FakeClock(AVAILABILITY_CURRENT_TIME_MS + startTime), liveEdgeLatencyMs * 1000,
AVAILABILITY_REALTIME_OFFSET_MS * 1000, null, null);
chunkSource.enable();
return chunkSource;
}
private void checkLiveEdgeLatencyWithTimeline(long startTimeMs, long liveEdgeLatencyMs,
long seekPositionMs, long seekRangeStartMs, long seekRangeEndMs, long chunkStartTimeMs,
long chunkEndTimeMs) {
DashChunkSource chunkSource = setupLiveEdgeTimelineTest(startTimeMs, liveEdgeLatencyMs);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, seekPositionMs * 1000, 0, out);
TimeRange seekRange = chunkSource.getSeekRange();
assertNotNull(out.chunk);
long[] seekRangeValuesUs = seekRange.getCurrentBoundsUs(null);
assertEquals(seekRangeStartMs * 1000, seekRangeValuesUs[0]);
assertEquals(seekRangeEndMs * 1000, seekRangeValuesUs[1]);
assertEquals(chunkStartTimeMs * 1000, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(chunkEndTimeMs * 1000, ((MediaChunk) out.chunk).endTimeUs);
}
private void checkLiveEdgeLatencyWithTemplate(long startTimeMs, long liveEdgeLatencyMs,
long seekPositionMs, long seekRangeStartMs, long seekRangeEndMs, long chunkStartTimeMs,
long chunkEndTimeMs, boolean limitTimeshiftBuffer) {
DashChunkSource chunkSource = setupLiveEdgeTemplateTest(startTimeMs, liveEdgeLatencyMs,
limitTimeshiftBuffer);
List<MediaChunk> queue = new ArrayList<>();
ChunkOperationHolder out = new ChunkOperationHolder();
chunkSource.getChunkOperation(queue, seekPositionMs * 1000, 0, out);
TimeRange seekRange = chunkSource.getSeekRange();
assertNotNull(out.chunk);
long[] seekRangeValuesUs = seekRange.getCurrentBoundsUs(null);
assertEquals(seekRangeStartMs * 1000, seekRangeValuesUs[0]);
assertEquals(seekRangeEndMs * 1000, seekRangeValuesUs[1]);
assertEquals(chunkStartTimeMs * 1000, ((MediaChunk) out.chunk).startTimeUs);
assertEquals(chunkEndTimeMs * 1000, ((MediaChunk) out.chunk).endTimeUs);
}
private void checkLiveEdgeLatencyWithTemplateAndUnlimitedTimeshift(long startTimeMs,
long liveEdgeLatencyMs, long seekPositionMs, long seekRangeEndMs,
long chunkStartTimeMs, long chunkEndTimeMs) {
checkLiveEdgeLatencyWithTemplate(startTimeMs, liveEdgeLatencyMs, seekPositionMs, 0,
seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs, false);
}
private void checkLiveEdgeLatencyWithTemplateAndLimitedTimeshift(long startTimeMs,
long liveEdgeLatencyMs, long seekPositionMs, long seekRangeStartMs, long seekRangeEndMs,
long chunkStartTimeMs, long chunkEndTimeMs) {
checkLiveEdgeLatencyWithTemplate(startTimeMs, liveEdgeLatencyMs, seekPositionMs,
seekRangeStartMs, seekRangeEndMs, chunkStartTimeMs, chunkEndTimeMs, true);
return new DashChunkSource(mockManifestFetcher, manifest, AdaptationSet.TYPE_VIDEO, null,
mockDataSource, EVALUATOR, AVAILABILITY_CLOCK, liveEdgeLatencyMs * 1000,
AVAILABILITY_REALTIME_OFFSET * 1000);
}
}