mirror of
https://github.com/androidx/media.git
synced 2025-05-06 23:20:42 +08:00
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:
parent
35c230f3c6
commit
a5a7e988e3
@ -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)).
|
||||
|
@ -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)",
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -288,6 +288,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
||||
chunkStartTimeUs,
|
||||
chunkEndTimeUs,
|
||||
chunkSeekTimeUs,
|
||||
/* clippedEndTimeUs= */ C.TIME_UNSET,
|
||||
chunkIndex,
|
||||
/* chunkCount= */ 1,
|
||||
sampleOffsetUs,
|
||||
|
Loading…
x
Reference in New Issue
Block a user