Support DASH multi-segment fetches
Note that multi-segment fetching is only possible in the case that segments in a representation are defined to have the same Uri and adjacent ranges (this is very rarely true for live streams, but is quite often true for on-demand). In the case that merging is requested but not possible, the implementation will request one at a time. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=140012443
This commit is contained in:
parent
b29ff0cf51
commit
b3726cf761
@ -32,8 +32,9 @@ import java.io.IOException;
|
||||
*/
|
||||
public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMetadataOutput {
|
||||
|
||||
private final ChunkExtractorWrapper extractorWrapper;
|
||||
private final int chunkCount;
|
||||
private final long sampleOffsetUs;
|
||||
private final ChunkExtractorWrapper extractorWrapper;
|
||||
private final Format sampleFormat;
|
||||
|
||||
private volatile int bytesLoaded;
|
||||
@ -49,6 +50,9 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
|
||||
* @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 chunkIndex The index of the chunk.
|
||||
* @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
|
||||
* underlying media are being merged into a single load.
|
||||
* @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor.
|
||||
* @param extractorWrapper A wrapped extractor to use for parsing the data.
|
||||
* @param sampleFormat The {@link Format} of the samples in the chunk, if known. May be null if
|
||||
@ -56,15 +60,21 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
|
||||
*/
|
||||
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat,
|
||||
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
|
||||
int chunkIndex, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper,
|
||||
int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper,
|
||||
Format sampleFormat) {
|
||||
super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs,
|
||||
endTimeUs, chunkIndex);
|
||||
this.extractorWrapper = extractorWrapper;
|
||||
this.chunkCount = chunkCount;
|
||||
this.sampleOffsetUs = sampleOffsetUs;
|
||||
this.extractorWrapper = extractorWrapper;
|
||||
this.sampleFormat = sampleFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextChunkIndex() {
|
||||
return chunkIndex + chunkCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoadCompleted() {
|
||||
return loadCompleted;
|
||||
|
@ -53,7 +53,7 @@ public abstract class MediaChunk extends Chunk {
|
||||
/**
|
||||
* Returns the next chunk index.
|
||||
*/
|
||||
public final int getNextChunkIndex() {
|
||||
public int getNextChunkIndex() {
|
||||
return chunkIndex + 1;
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.source.dash;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
@ -54,9 +55,15 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
public static final class Factory implements DashChunkSource.Factory {
|
||||
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final int maxSegmentsPerLoad;
|
||||
|
||||
public Factory(DataSource.Factory dataSourceFactory) {
|
||||
this(dataSourceFactory, 1);
|
||||
}
|
||||
|
||||
public Factory(DataSource.Factory dataSourceFactory, int maxSegmentsPerLoad) {
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.maxSegmentsPerLoad = maxSegmentsPerLoad;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -65,7 +72,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
TrackSelection trackSelection, long elapsedRealtimeOffsetMs) {
|
||||
DataSource dataSource = dataSourceFactory.createDataSource();
|
||||
return new DefaultDashChunkSource(manifestLoaderErrorThrower, manifest, periodIndex,
|
||||
adaptationSetIndex, trackSelection, dataSource, elapsedRealtimeOffsetMs);
|
||||
adaptationSetIndex, trackSelection, dataSource, elapsedRealtimeOffsetMs,
|
||||
maxSegmentsPerLoad);
|
||||
}
|
||||
|
||||
}
|
||||
@ -76,6 +84,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
private final RepresentationHolder[] representationHolders;
|
||||
private final DataSource dataSource;
|
||||
private final long elapsedRealtimeOffsetMs;
|
||||
private final int maxSegmentsPerLoad;
|
||||
|
||||
private DashManifest manifest;
|
||||
private int periodIndex;
|
||||
@ -93,10 +102,13 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
* @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. If unknown, set to 0.
|
||||
* @param maxSegmentsPerLoad The maximum number of segments to combine into a single request.
|
||||
* Note that segments will only be combined if their {@link Uri}s are the same and if their
|
||||
* data ranges are adjacent.
|
||||
*/
|
||||
public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
|
||||
DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection,
|
||||
DataSource dataSource, long elapsedRealtimeOffsetMs) {
|
||||
DataSource dataSource, long elapsedRealtimeOffsetMs, int maxSegmentsPerLoad) {
|
||||
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
|
||||
this.manifest = manifest;
|
||||
this.adaptationSetIndex = adaptationSetIndex;
|
||||
@ -104,6 +116,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
this.dataSource = dataSource;
|
||||
this.periodIndex = periodIndex;
|
||||
this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs;
|
||||
this.maxSegmentsPerLoad = maxSegmentsPerLoad;
|
||||
|
||||
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
|
||||
List<Representation> representations = getRepresentations();
|
||||
@ -219,9 +232,10 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
return;
|
||||
}
|
||||
|
||||
int maxSegmentCount = Math.min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1);
|
||||
Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource,
|
||||
trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(),
|
||||
trackSelection.getSelectionData(), sampleFormat, segmentNum);
|
||||
trackSelection.getSelectionData(), sampleFormat, segmentNum, maxSegmentCount);
|
||||
out.chunk = nextMediaChunk;
|
||||
}
|
||||
|
||||
@ -260,7 +274,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
RepresentationHolder representationHolder =
|
||||
representationHolders[trackSelection.indexOf(chunk.trackFormat)];
|
||||
int lastAvailableSegmentNum = representationHolder.getLastSegmentNum();
|
||||
if (((MediaChunk) chunk).chunkIndex >= lastAvailableSegmentNum) {
|
||||
if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) {
|
||||
missingLastSegment = true;
|
||||
return true;
|
||||
}
|
||||
@ -284,7 +298,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
}
|
||||
}
|
||||
|
||||
private Chunk newInitializationChunk(RepresentationHolder representationHolder,
|
||||
private static Chunk newInitializationChunk(RepresentationHolder representationHolder,
|
||||
DataSource dataSource, Format trackFormat, int trackSelectionReason,
|
||||
Object trackSelectionData, RangedUri initializationUri, RangedUri indexUri) {
|
||||
RangedUri requestUri;
|
||||
@ -305,24 +319,38 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
||||
trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper);
|
||||
}
|
||||
|
||||
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource,
|
||||
Format trackFormat, int trackSelectionReason,
|
||||
Object trackSelectionData, Format sampleFormat, int segmentNum) {
|
||||
private static Chunk newMediaChunk(RepresentationHolder representationHolder,
|
||||
DataSource dataSource, Format trackFormat, int trackSelectionReason,
|
||||
Object trackSelectionData, Format sampleFormat, int firstSegmentNum, int maxSegmentCount) {
|
||||
Representation representation = representationHolder.representation;
|
||||
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum);
|
||||
long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum);
|
||||
RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum);
|
||||
DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(representation.baseUrl),
|
||||
segmentUri.start, segmentUri.length, representation.getCacheKey());
|
||||
|
||||
long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum);
|
||||
RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum);
|
||||
String baseUrl = representation.baseUrl;
|
||||
if (representationHolder.extractorWrapper == null) {
|
||||
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum);
|
||||
DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl),
|
||||
segmentUri.start, segmentUri.length, representation.getCacheKey());
|
||||
return new SingleSampleMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason,
|
||||
trackSelectionData, startTimeUs, endTimeUs, segmentNum, trackFormat);
|
||||
trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, trackFormat);
|
||||
} else {
|
||||
int segmentCount = 1;
|
||||
for (int i = 1; i < maxSegmentCount; i++) {
|
||||
RangedUri nextSegmentUri = representationHolder.getSegmentUrl(firstSegmentNum + i);
|
||||
RangedUri mergedSegmentUri = segmentUri.attemptMerge(nextSegmentUri, baseUrl);
|
||||
if (mergedSegmentUri == null) {
|
||||
// Unable to merge segment fetches because the URIs do not merge.
|
||||
break;
|
||||
}
|
||||
segmentUri = mergedSegmentUri;
|
||||
segmentCount++;
|
||||
}
|
||||
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum + segmentCount - 1);
|
||||
DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl),
|
||||
segmentUri.start, segmentUri.length, representation.getCacheKey());
|
||||
long sampleOffsetUs = -representation.presentationTimeOffsetUs;
|
||||
return new ContainerMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason,
|
||||
trackSelectionData, startTimeUs, endTimeUs, segmentNum, sampleOffsetUs,
|
||||
representationHolder.extractorWrapper, sampleFormat);
|
||||
trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, segmentCount,
|
||||
sampleOffsetUs, representationHolder.extractorWrapper, sampleFormat);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,7 +218,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
||||
// To convert them the absolute timestamps, we need to set sampleOffsetUs to chunkStartTimeUs.
|
||||
long sampleOffsetUs = chunkStartTimeUs;
|
||||
return new ContainerMediaChunk(dataSource, dataSpec, format, trackSelectionReason,
|
||||
trackSelectionData, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, sampleOffsetUs,
|
||||
trackSelectionData, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, 1, sampleOffsetUs,
|
||||
extractorWrapper, format);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user