Commit a5a7e988 by olly Committed by Oliver Woodman

Clip DASH periods to their durations

Issue: #4185

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=212619419
parent 35c230f3
...@@ -41,7 +41,9 @@ ...@@ -41,7 +41,9 @@
* Fix the bitrate being unset on primary track sample formats * Fix the bitrate being unset on primary track sample formats
([#3297](https://github.com/google/ExoPlayer/issues/3297)). ([#3297](https://github.com/google/ExoPlayer/issues/3297)).
* DASH: * DASH:
* Support messageData attribute of in-manifest events. * Support `messageData` attribute for in-manifest event streams.
* Clip periods to their specified durations
([#4185](https://github.com/google/ExoPlayer/issues/4185)).
* Improve seeking support for progressive streams: * Improve seeking support for progressive streams:
* Support seeking in MPEG-TS * Support seeking in MPEG-TS
([#966](https://github.com/google/ExoPlayer/issues/966)). ([#966](https://github.com/google/ExoPlayer/issues/966)).
......
...@@ -4,13 +4,11 @@ ...@@ -4,13 +4,11 @@
"samples": [ "samples": [
{ {
"name": "Google Glass (MP4,H264)", "name": "Google Glass (MP4,H264)",
"uri": "https://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0", "uri": "https://s3-eu-west-1.amazonaws.com/worm-bucket-prod/videos/5b6c35c32aee110004d40559/3DA76C26-A84A-4A53-8AD1-55EBCF5A9A09-ffr.MP4"
"extension": "mpd"
}, },
{ {
"name": "Google Play (MP4,H264)", "name": "Google Play (MP4,H264)",
"uri": "https://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=A2716F75795F5D2AF0E88962FFCD10DB79384F29.84308FF04844498CE6FBCE4731507882B8307798&key=ik0", "uri": "https://s3.amazonaws.com/worm-streaming/test/playlist.m3u8"
"extension": "mpd"
}, },
{ {
"name": "Google Glass (WebM,VP9)", "name": "Google Glass (WebM,VP9)",
......
...@@ -26,10 +26,15 @@ import com.google.android.exoplayer2.upstream.DataSpec; ...@@ -26,10 +26,15 @@ import com.google.android.exoplayer2.upstream.DataSpec;
public abstract class BaseMediaChunk extends MediaChunk { public abstract class BaseMediaChunk extends MediaChunk {
/** /**
* The media time from which output will begin, or {@link C#TIME_UNSET} if the whole chunk should * The time from which output will begin, or {@link C#TIME_UNSET} if output will begin from the
* be output. * start of the chunk.
*/ */
public final long seekTimeUs; public final long clippedStartTimeUs;
/**
* The time from which output will end, or {@link C#TIME_UNSET} if output will end at the end of
* the chunk.
*/
public final long clippedEndTimeUs;
private BaseMediaChunkOutput output; private BaseMediaChunkOutput output;
private int[] firstSampleIndices; private int[] firstSampleIndices;
...@@ -42,8 +47,10 @@ public abstract class BaseMediaChunk extends MediaChunk { ...@@ -42,8 +47,10 @@ public abstract class BaseMediaChunk extends MediaChunk {
* @param trackSelectionData See {@link #trackSelectionData}. * @param trackSelectionData See {@link #trackSelectionData}.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param seekTimeUs The media time from which output will begin, or {@link C#TIME_UNSET} if the * @param clippedStartTimeUs The time in the chunk from which output will begin, or {@link
* whole chunk should be output. * C#TIME_UNSET} to output from the start of the chunk.
* @param clippedEndTimeUs The time in the chunk from which output will end, or {@link
* C#TIME_UNSET} to output to the end of the chunk.
* @param chunkIndex The index of the chunk, or {@link C#INDEX_UNSET} if it is not known. * @param chunkIndex The index of the chunk, or {@link C#INDEX_UNSET} if it is not known.
*/ */
public BaseMediaChunk( public BaseMediaChunk(
...@@ -54,11 +61,13 @@ public abstract class BaseMediaChunk extends MediaChunk { ...@@ -54,11 +61,13 @@ public abstract class BaseMediaChunk extends MediaChunk {
Object trackSelectionData, Object trackSelectionData,
long startTimeUs, long startTimeUs,
long endTimeUs, long endTimeUs,
long seekTimeUs, long clippedStartTimeUs,
long clippedEndTimeUs,
long chunkIndex) { long chunkIndex) {
super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs, super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs,
endTimeUs, chunkIndex); endTimeUs, chunkIndex);
this.seekTimeUs = seekTimeUs; this.clippedStartTimeUs = clippedStartTimeUs;
this.clippedEndTimeUs = clippedEndTimeUs;
} }
/** /**
......
...@@ -64,6 +64,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput { ...@@ -64,6 +64,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput {
private boolean extractorInitialized; private boolean extractorInitialized;
private TrackOutputProvider trackOutputProvider; private TrackOutputProvider trackOutputProvider;
private long endTimeUs;
private SeekMap seekMap; private SeekMap seekMap;
private Format[] sampleFormats; private Format[] sampleFormats;
...@@ -101,21 +102,25 @@ public final class ChunkExtractorWrapper implements ExtractorOutput { ...@@ -101,21 +102,25 @@ public final class ChunkExtractorWrapper implements ExtractorOutput {
* TrackOutputProvider}, and configures the extractor to receive data from a new chunk. * TrackOutputProvider}, and configures the extractor to receive data from a new chunk.
* *
* @param trackOutputProvider The provider of {@link TrackOutput}s that will receive sample data. * @param trackOutputProvider The provider of {@link TrackOutput}s that will receive sample data.
* @param seekTimeUs The seek position within the new chunk, or {@link C#TIME_UNSET} to output the * @param startTimeUs The start position in the new chunk, or {@link C#TIME_UNSET} to output
* whole chunk. * samples from the start of the chunk.
* @param endTimeUs The end position in the new chunk, or {@link C#TIME_UNSET} to output samples
* to the end of the chunk.
*/ */
public void init(@Nullable TrackOutputProvider trackOutputProvider, long seekTimeUs) { public void init(
@Nullable TrackOutputProvider trackOutputProvider, long startTimeUs, long endTimeUs) {
this.trackOutputProvider = trackOutputProvider; this.trackOutputProvider = trackOutputProvider;
this.endTimeUs = endTimeUs;
if (!extractorInitialized) { if (!extractorInitialized) {
extractor.init(this); extractor.init(this);
if (seekTimeUs != C.TIME_UNSET) { if (startTimeUs != C.TIME_UNSET) {
extractor.seek(/* position= */ 0, seekTimeUs); extractor.seek(/* position= */ 0, startTimeUs);
} }
extractorInitialized = true; extractorInitialized = true;
} else { } else {
extractor.seek(/* position= */ 0, seekTimeUs == C.TIME_UNSET ? 0 : seekTimeUs); extractor.seek(/* position= */ 0, startTimeUs == C.TIME_UNSET ? 0 : startTimeUs);
for (int i = 0; i < bindingTrackOutputs.size(); i++) { for (int i = 0; i < bindingTrackOutputs.size(); i++) {
bindingTrackOutputs.valueAt(i).bind(trackOutputProvider); bindingTrackOutputs.valueAt(i).bind(trackOutputProvider, endTimeUs);
} }
} }
} }
...@@ -131,7 +136,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput { ...@@ -131,7 +136,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput {
// TODO: Manifest formats for embedded tracks should also be passed here. // TODO: Manifest formats for embedded tracks should also be passed here.
bindingTrackOutput = new BindingTrackOutput(id, type, bindingTrackOutput = new BindingTrackOutput(id, type,
type == primaryTrackType ? primaryTrackManifestFormat : null); type == primaryTrackType ? primaryTrackManifestFormat : null);
bindingTrackOutput.bind(trackOutputProvider); bindingTrackOutput.bind(trackOutputProvider, endTimeUs);
bindingTrackOutputs.put(id, bindingTrackOutput); bindingTrackOutputs.put(id, bindingTrackOutput);
} }
return bindingTrackOutput; return bindingTrackOutput;
...@@ -158,21 +163,25 @@ public final class ChunkExtractorWrapper implements ExtractorOutput { ...@@ -158,21 +163,25 @@ public final class ChunkExtractorWrapper implements ExtractorOutput {
private final int id; private final int id;
private final int type; private final int type;
private final Format manifestFormat; private final Format manifestFormat;
private final DummyTrackOutput dummyTrackOutput;
public Format sampleFormat; public Format sampleFormat;
private TrackOutput trackOutput; private TrackOutput trackOutput;
private long endTimeUs;
public BindingTrackOutput(int id, int type, Format manifestFormat) { public BindingTrackOutput(int id, int type, Format manifestFormat) {
this.id = id; this.id = id;
this.type = type; this.type = type;
this.manifestFormat = manifestFormat; this.manifestFormat = manifestFormat;
dummyTrackOutput = new DummyTrackOutput();
} }
public void bind(TrackOutputProvider trackOutputProvider) { public void bind(TrackOutputProvider trackOutputProvider, long endTimeUs) {
if (trackOutputProvider == null) { if (trackOutputProvider == null) {
trackOutput = new DummyTrackOutput(); trackOutput = dummyTrackOutput;
return; return;
} }
this.endTimeUs = endTimeUs;
trackOutput = trackOutputProvider.track(id, type); trackOutput = trackOutputProvider.track(id, type);
if (sampleFormat != null) { if (sampleFormat != null) {
trackOutput.format(sampleFormat); trackOutput.format(sampleFormat);
...@@ -200,6 +209,9 @@ public final class ChunkExtractorWrapper implements ExtractorOutput { ...@@ -200,6 +209,9 @@ public final class ChunkExtractorWrapper implements ExtractorOutput {
@Override @Override
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
CryptoData cryptoData) { CryptoData cryptoData) {
if (endTimeUs != C.TIME_UNSET && timeUs >= endTimeUs) {
trackOutput = dummyTrackOutput;
}
trackOutput.sampleMetadata(timeUs, flags, size, offset, cryptoData); trackOutput.sampleMetadata(timeUs, flags, size, offset, cryptoData);
} }
......
...@@ -290,7 +290,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -290,7 +290,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
for (int i = 0; i < mediaChunks.size(); i++) { for (int i = 0; i < mediaChunks.size(); i++) {
BaseMediaChunk mediaChunk = mediaChunks.get(i); BaseMediaChunk mediaChunk = mediaChunks.get(i);
long mediaChunkStartTimeUs = mediaChunk.startTimeUs; long mediaChunkStartTimeUs = mediaChunk.startTimeUs;
if (mediaChunkStartTimeUs == positionUs && mediaChunk.seekTimeUs == C.TIME_UNSET) { if (mediaChunkStartTimeUs == positionUs && mediaChunk.clippedStartTimeUs == C.TIME_UNSET) {
seekToMediaChunk = mediaChunk; seekToMediaChunk = mediaChunk;
break; break;
} else if (mediaChunkStartTimeUs > positionUs) { } else if (mediaChunkStartTimeUs > positionUs) {
......
...@@ -50,8 +50,10 @@ public class ContainerMediaChunk extends BaseMediaChunk { ...@@ -50,8 +50,10 @@ public class ContainerMediaChunk extends BaseMediaChunk {
* @param trackSelectionData See {@link #trackSelectionData}. * @param trackSelectionData See {@link #trackSelectionData}.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param seekTimeUs The media time from which output will begin, or {@link C#TIME_UNSET} if the * @param clippedStartTimeUs The time in the chunk from which output will begin, or {@link
* whole chunk should be output. * C#TIME_UNSET} to output from the start of the chunk.
* @param clippedEndTimeUs The time in the chunk from which output will end, or {@link
* C#TIME_UNSET} to output to the end of the chunk.
* @param chunkIndex The index of the chunk, or {@link C#INDEX_UNSET} if it is not known. * @param chunkIndex The index of the chunk, or {@link C#INDEX_UNSET} if it is not known.
* @param chunkCount The number of chunks in the underlying media that are spanned by this * @param chunkCount The number of chunks in the underlying media that are spanned by this
* instance. Normally equal to one, but may be larger if multiple chunks as defined by the * instance. Normally equal to one, but may be larger if multiple chunks as defined by the
...@@ -67,7 +69,8 @@ public class ContainerMediaChunk extends BaseMediaChunk { ...@@ -67,7 +69,8 @@ public class ContainerMediaChunk extends BaseMediaChunk {
Object trackSelectionData, Object trackSelectionData,
long startTimeUs, long startTimeUs,
long endTimeUs, long endTimeUs,
long seekTimeUs, long clippedStartTimeUs,
long clippedEndTimeUs,
long chunkIndex, long chunkIndex,
int chunkCount, int chunkCount,
long sampleOffsetUs, long sampleOffsetUs,
...@@ -80,7 +83,8 @@ public class ContainerMediaChunk extends BaseMediaChunk { ...@@ -80,7 +83,8 @@ public class ContainerMediaChunk extends BaseMediaChunk {
trackSelectionData, trackSelectionData,
startTimeUs, startTimeUs,
endTimeUs, endTimeUs,
seekTimeUs, clippedStartTimeUs,
clippedEndTimeUs,
chunkIndex); chunkIndex);
this.chunkCount = chunkCount; this.chunkCount = chunkCount;
this.sampleOffsetUs = sampleOffsetUs; this.sampleOffsetUs = sampleOffsetUs;
...@@ -117,7 +121,11 @@ public class ContainerMediaChunk extends BaseMediaChunk { ...@@ -117,7 +121,11 @@ public class ContainerMediaChunk extends BaseMediaChunk {
BaseMediaChunkOutput output = getOutput(); BaseMediaChunkOutput output = getOutput();
output.setSampleOffsetUs(sampleOffsetUs); output.setSampleOffsetUs(sampleOffsetUs);
extractorWrapper.init( extractorWrapper.init(
output, seekTimeUs == C.TIME_UNSET ? 0 : (seekTimeUs - sampleOffsetUs)); output,
clippedStartTimeUs == C.TIME_UNSET
? C.TIME_UNSET
: (clippedStartTimeUs - sampleOffsetUs),
clippedEndTimeUs == C.TIME_UNSET ? C.TIME_UNSET : (clippedEndTimeUs - sampleOffsetUs));
} }
// Load and decode the sample data. // Load and decode the sample data.
try { try {
......
...@@ -76,7 +76,10 @@ public final class InitializationChunk extends Chunk { ...@@ -76,7 +76,10 @@ public final class InitializationChunk extends Chunk {
ExtractorInput input = new DefaultExtractorInput(dataSource, ExtractorInput input = new DefaultExtractorInput(dataSource,
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
if (nextLoadPosition == 0) { if (nextLoadPosition == 0) {
extractorWrapper.init(/* trackOutputProvider= */ null, C.TIME_UNSET); extractorWrapper.init(
/* trackOutputProvider= */ null,
/* startTimeUs= */ C.TIME_UNSET,
/* endTimeUs= */ C.TIME_UNSET);
} }
// Load and decode the initialization data. // Load and decode the initialization data.
try { try {
......
...@@ -68,7 +68,8 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk { ...@@ -68,7 +68,8 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
trackSelectionData, trackSelectionData,
startTimeUs, startTimeUs,
endTimeUs, endTimeUs,
C.TIME_UNSET, /* clippedStartTimeUs= */ C.TIME_UNSET,
/* clippedEndTimeUs= */ C.TIME_UNSET,
chunkIndex); chunkIndex);
this.trackType = trackType; this.trackType = trackType;
this.sampleFormat = sampleFormat; this.sampleFormat = sampleFormat;
......
...@@ -345,13 +345,32 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -345,13 +345,32 @@ public class DefaultDashChunkSource implements DashChunkSource {
} }
if (segmentNum > lastAvailableSegmentNum if (segmentNum > lastAvailableSegmentNum
|| (missingLastSegment && segmentNum >= lastAvailableSegmentNum)) { || (missingLastSegment && segmentNum >= lastAvailableSegmentNum)) {
// This is beyond the last chunk in the current manifest. // The segment is beyond the end of the period. We know the period will not be extended if the
// manifest is static, or if there's a period after this one.
out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1); out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1);
return; return;
} }
long periodDurationUs = representationHolder.periodDurationUs;
if (periodDurationUs != C.TIME_UNSET
&& representationHolder.getSegmentStartTimeUs(segmentNum) >= periodDurationUs) {
// The period duration clips the period to a position before the segment.
out.endOfStream = true;
return;
}
int maxSegmentCount = int maxSegmentCount =
(int) Math.min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1); (int) Math.min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1);
if (periodDurationUs != C.TIME_UNSET) {
while (maxSegmentCount > 1
&& representationHolder.getSegmentStartTimeUs(segmentNum + maxSegmentCount - 1)
>= periodDurationUs) {
// The period duration clips the period to a position before the last segment in the range
// [segmentNum, segmentNum + maxSegmentCount - 1]. Reduce maxSegmentCount.
maxSegmentCount--;
}
}
long seekTimeUs = queue.isEmpty() ? loadPositionUs : C.TIME_UNSET; long seekTimeUs = queue.isEmpty() ? loadPositionUs : C.TIME_UNSET;
out.chunk = out.chunk =
newMediaChunk( newMediaChunk(
...@@ -523,6 +542,11 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -523,6 +542,11 @@ public class DefaultDashChunkSource implements DashChunkSource {
segmentCount++; segmentCount++;
} }
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum + segmentCount - 1); long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum + segmentCount - 1);
long periodDurationUs = representationHolder.periodDurationUs;
long clippedEndTimeUs =
periodDurationUs != C.TIME_UNSET && periodDurationUs < endTimeUs
? periodDurationUs
: C.TIME_UNSET;
DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl), DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl),
segmentUri.start, segmentUri.length, representation.getCacheKey()); segmentUri.start, segmentUri.length, representation.getCacheKey());
long sampleOffsetUs = -representation.presentationTimeOffsetUs; long sampleOffsetUs = -representation.presentationTimeOffsetUs;
...@@ -535,6 +559,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -535,6 +559,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
startTimeUs, startTimeUs,
endTimeUs, endTimeUs,
seekTimeUs, seekTimeUs,
clippedEndTimeUs,
firstSegmentNum, firstSegmentNum,
segmentCount, segmentCount,
sampleOffsetUs, sampleOffsetUs,
......
...@@ -288,6 +288,7 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -288,6 +288,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
chunkStartTimeUs, chunkStartTimeUs,
chunkEndTimeUs, chunkEndTimeUs,
chunkSeekTimeUs, chunkSeekTimeUs,
/* clippedEndTimeUs= */ C.TIME_UNSET,
chunkIndex, chunkIndex,
/* chunkCount= */ 1, /* chunkCount= */ 1,
sampleOffsetUs, sampleOffsetUs,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment