Clip DASH periods to their durations

Issue: #4185

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=212619419
This commit is contained in:
olly 2018-09-12 04:51:51 -07:00 committed by Oliver Woodman
parent 35c230f3c6
commit a5a7e988e3
10 changed files with 90 additions and 31 deletions

View File

@ -41,7 +41,9 @@
* Fix the bitrate being unset on primary track sample formats
([#3297](https://github.com/google/ExoPlayer/issues/3297)).
* 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:
* Support seeking in MPEG-TS
([#966](https://github.com/google/ExoPlayer/issues/966)).

View File

@ -4,13 +4,11 @@
"samples": [
{
"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",
"extension": "mpd"
"uri": "https://s3-eu-west-1.amazonaws.com/worm-bucket-prod/videos/5b6c35c32aee110004d40559/3DA76C26-A84A-4A53-8AD1-55EBCF5A9A09-ffr.MP4"
},
{
"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",
"extension": "mpd"
"uri": "https://s3.amazonaws.com/worm-streaming/test/playlist.m3u8"
},
{
"name": "Google Glass (WebM,VP9)",

View File

@ -26,10 +26,15 @@ import com.google.android.exoplayer2.upstream.DataSpec;
public abstract class BaseMediaChunk extends MediaChunk {
/**
* The media time from which output will begin, or {@link C#TIME_UNSET} if the whole chunk should
* be output.
* The time from which output will begin, or {@link C#TIME_UNSET} if output will begin from the
* 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 int[] firstSampleIndices;
@ -42,8 +47,10 @@ public abstract class BaseMediaChunk extends MediaChunk {
* @param trackSelectionData See {@link #trackSelectionData}.
* @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 seekTimeUs The media time from which output will begin, or {@link C#TIME_UNSET} if the
* whole chunk should be output.
* @param clippedStartTimeUs The time in the chunk from which output will begin, or {@link
* 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.
*/
public BaseMediaChunk(
@ -54,11 +61,13 @@ public abstract class BaseMediaChunk extends MediaChunk {
Object trackSelectionData,
long startTimeUs,
long endTimeUs,
long seekTimeUs,
long clippedStartTimeUs,
long clippedEndTimeUs,
long chunkIndex) {
super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs,
endTimeUs, chunkIndex);
this.seekTimeUs = seekTimeUs;
this.clippedStartTimeUs = clippedStartTimeUs;
this.clippedEndTimeUs = clippedEndTimeUs;
}
/**

View File

@ -64,6 +64,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput {
private boolean extractorInitialized;
private TrackOutputProvider trackOutputProvider;
private long endTimeUs;
private SeekMap seekMap;
private Format[] sampleFormats;
@ -101,21 +102,25 @@ public final class ChunkExtractorWrapper implements ExtractorOutput {
* 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 seekTimeUs The seek position within the new chunk, or {@link C#TIME_UNSET} to output the
* whole chunk.
* @param startTimeUs The start position in the new chunk, or {@link C#TIME_UNSET} to output
* 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.endTimeUs = endTimeUs;
if (!extractorInitialized) {
extractor.init(this);
if (seekTimeUs != C.TIME_UNSET) {
extractor.seek(/* position= */ 0, seekTimeUs);
if (startTimeUs != C.TIME_UNSET) {
extractor.seek(/* position= */ 0, startTimeUs);
}
extractorInitialized = true;
} 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++) {
bindingTrackOutputs.valueAt(i).bind(trackOutputProvider);
bindingTrackOutputs.valueAt(i).bind(trackOutputProvider, endTimeUs);
}
}
}
@ -131,7 +136,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput {
// TODO: Manifest formats for embedded tracks should also be passed here.
bindingTrackOutput = new BindingTrackOutput(id, type,
type == primaryTrackType ? primaryTrackManifestFormat : null);
bindingTrackOutput.bind(trackOutputProvider);
bindingTrackOutput.bind(trackOutputProvider, endTimeUs);
bindingTrackOutputs.put(id, bindingTrackOutput);
}
return bindingTrackOutput;
@ -158,21 +163,25 @@ public final class ChunkExtractorWrapper implements ExtractorOutput {
private final int id;
private final int type;
private final Format manifestFormat;
private final DummyTrackOutput dummyTrackOutput;
public Format sampleFormat;
private TrackOutput trackOutput;
private long endTimeUs;
public BindingTrackOutput(int id, int type, Format manifestFormat) {
this.id = id;
this.type = type;
this.manifestFormat = manifestFormat;
dummyTrackOutput = new DummyTrackOutput();
}
public void bind(TrackOutputProvider trackOutputProvider) {
public void bind(TrackOutputProvider trackOutputProvider, long endTimeUs) {
if (trackOutputProvider == null) {
trackOutput = new DummyTrackOutput();
trackOutput = dummyTrackOutput;
return;
}
this.endTimeUs = endTimeUs;
trackOutput = trackOutputProvider.track(id, type);
if (sampleFormat != null) {
trackOutput.format(sampleFormat);
@ -200,6 +209,9 @@ public final class ChunkExtractorWrapper implements ExtractorOutput {
@Override
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
CryptoData cryptoData) {
if (endTimeUs != C.TIME_UNSET && timeUs >= endTimeUs) {
trackOutput = dummyTrackOutput;
}
trackOutput.sampleMetadata(timeUs, flags, size, offset, cryptoData);
}

View File

@ -290,7 +290,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
for (int i = 0; i < mediaChunks.size(); i++) {
BaseMediaChunk mediaChunk = mediaChunks.get(i);
long mediaChunkStartTimeUs = mediaChunk.startTimeUs;
if (mediaChunkStartTimeUs == positionUs && mediaChunk.seekTimeUs == C.TIME_UNSET) {
if (mediaChunkStartTimeUs == positionUs && mediaChunk.clippedStartTimeUs == C.TIME_UNSET) {
seekToMediaChunk = mediaChunk;
break;
} else if (mediaChunkStartTimeUs > positionUs) {

View File

@ -50,8 +50,10 @@ public class ContainerMediaChunk extends BaseMediaChunk {
* @param trackSelectionData See {@link #trackSelectionData}.
* @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 seekTimeUs The media time from which output will begin, or {@link C#TIME_UNSET} if the
* whole chunk should be output.
* @param clippedStartTimeUs The time in the chunk from which output will begin, or {@link
* 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 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
@ -67,7 +69,8 @@ public class ContainerMediaChunk extends BaseMediaChunk {
Object trackSelectionData,
long startTimeUs,
long endTimeUs,
long seekTimeUs,
long clippedStartTimeUs,
long clippedEndTimeUs,
long chunkIndex,
int chunkCount,
long sampleOffsetUs,
@ -80,7 +83,8 @@ public class ContainerMediaChunk extends BaseMediaChunk {
trackSelectionData,
startTimeUs,
endTimeUs,
seekTimeUs,
clippedStartTimeUs,
clippedEndTimeUs,
chunkIndex);
this.chunkCount = chunkCount;
this.sampleOffsetUs = sampleOffsetUs;
@ -117,7 +121,11 @@ public class ContainerMediaChunk extends BaseMediaChunk {
BaseMediaChunkOutput output = getOutput();
output.setSampleOffsetUs(sampleOffsetUs);
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.
try {

View File

@ -76,7 +76,10 @@ public final class InitializationChunk extends Chunk {
ExtractorInput input = new DefaultExtractorInput(dataSource,
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
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.
try {

View File

@ -68,7 +68,8 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
trackSelectionData,
startTimeUs,
endTimeUs,
C.TIME_UNSET,
/* clippedStartTimeUs= */ C.TIME_UNSET,
/* clippedEndTimeUs= */ C.TIME_UNSET,
chunkIndex);
this.trackType = trackType;
this.sampleFormat = sampleFormat;

View File

@ -345,13 +345,32 @@ public class DefaultDashChunkSource implements DashChunkSource {
}
if (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);
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) 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;
out.chunk =
newMediaChunk(
@ -523,6 +542,11 @@ public class DefaultDashChunkSource implements DashChunkSource {
segmentCount++;
}
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),
segmentUri.start, segmentUri.length, representation.getCacheKey());
long sampleOffsetUs = -representation.presentationTimeOffsetUs;
@ -535,6 +559,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
startTimeUs,
endTimeUs,
seekTimeUs,
clippedEndTimeUs,
firstSegmentNum,
segmentCount,
sampleOffsetUs,

View File

@ -288,6 +288,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
chunkStartTimeUs,
chunkEndTimeUs,
chunkSeekTimeUs,
/* clippedEndTimeUs= */ C.TIME_UNSET,
chunkIndex,
/* chunkCount= */ 1,
sampleOffsetUs,