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 {
|
public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMetadataOutput {
|
||||||
|
|
||||||
private final ChunkExtractorWrapper extractorWrapper;
|
private final int chunkCount;
|
||||||
private final long sampleOffsetUs;
|
private final long sampleOffsetUs;
|
||||||
|
private final ChunkExtractorWrapper extractorWrapper;
|
||||||
private final Format sampleFormat;
|
private final Format sampleFormat;
|
||||||
|
|
||||||
private volatile int bytesLoaded;
|
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 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 chunkIndex The index of the chunk.
|
* @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 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 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
|
* @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,
|
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat,
|
||||||
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
|
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
|
||||||
int chunkIndex, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper,
|
int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper,
|
||||||
Format sampleFormat) {
|
Format sampleFormat) {
|
||||||
super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs,
|
super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs,
|
||||||
endTimeUs, chunkIndex);
|
endTimeUs, chunkIndex);
|
||||||
this.extractorWrapper = extractorWrapper;
|
this.chunkCount = chunkCount;
|
||||||
this.sampleOffsetUs = sampleOffsetUs;
|
this.sampleOffsetUs = sampleOffsetUs;
|
||||||
|
this.extractorWrapper = extractorWrapper;
|
||||||
this.sampleFormat = sampleFormat;
|
this.sampleFormat = sampleFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNextChunkIndex() {
|
||||||
|
return chunkIndex + chunkCount;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLoadCompleted() {
|
public boolean isLoadCompleted() {
|
||||||
return loadCompleted;
|
return loadCompleted;
|
||||||
|
@ -53,7 +53,7 @@ public abstract class MediaChunk extends Chunk {
|
|||||||
/**
|
/**
|
||||||
* Returns the next chunk index.
|
* Returns the next chunk index.
|
||||||
*/
|
*/
|
||||||
public final int getNextChunkIndex() {
|
public int getNextChunkIndex() {
|
||||||
return chunkIndex + 1;
|
return chunkIndex + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.dash;
|
package com.google.android.exoplayer2.source.dash;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
@ -54,9 +55,15 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
public static final class Factory implements DashChunkSource.Factory {
|
public static final class Factory implements DashChunkSource.Factory {
|
||||||
|
|
||||||
private final DataSource.Factory dataSourceFactory;
|
private final DataSource.Factory dataSourceFactory;
|
||||||
|
private final int maxSegmentsPerLoad;
|
||||||
|
|
||||||
public Factory(DataSource.Factory dataSourceFactory) {
|
public Factory(DataSource.Factory dataSourceFactory) {
|
||||||
|
this(dataSourceFactory, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Factory(DataSource.Factory dataSourceFactory, int maxSegmentsPerLoad) {
|
||||||
this.dataSourceFactory = dataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
|
this.maxSegmentsPerLoad = maxSegmentsPerLoad;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -65,7 +72,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
TrackSelection trackSelection, long elapsedRealtimeOffsetMs) {
|
TrackSelection trackSelection, long elapsedRealtimeOffsetMs) {
|
||||||
DataSource dataSource = dataSourceFactory.createDataSource();
|
DataSource dataSource = dataSourceFactory.createDataSource();
|
||||||
return new DefaultDashChunkSource(manifestLoaderErrorThrower, manifest, periodIndex,
|
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 RepresentationHolder[] representationHolders;
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
private final long elapsedRealtimeOffsetMs;
|
private final long elapsedRealtimeOffsetMs;
|
||||||
|
private final int maxSegmentsPerLoad;
|
||||||
|
|
||||||
private DashManifest manifest;
|
private DashManifest manifest;
|
||||||
private int periodIndex;
|
private int periodIndex;
|
||||||
@ -93,10 +102,13 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
|
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
|
||||||
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
|
* 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.
|
* 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,
|
public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
|
||||||
DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection,
|
DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection,
|
||||||
DataSource dataSource, long elapsedRealtimeOffsetMs) {
|
DataSource dataSource, long elapsedRealtimeOffsetMs, int maxSegmentsPerLoad) {
|
||||||
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
|
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
|
||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
this.adaptationSetIndex = adaptationSetIndex;
|
this.adaptationSetIndex = adaptationSetIndex;
|
||||||
@ -104,6 +116,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
this.periodIndex = periodIndex;
|
this.periodIndex = periodIndex;
|
||||||
this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs;
|
this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs;
|
||||||
|
this.maxSegmentsPerLoad = maxSegmentsPerLoad;
|
||||||
|
|
||||||
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
|
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
|
||||||
List<Representation> representations = getRepresentations();
|
List<Representation> representations = getRepresentations();
|
||||||
@ -219,9 +232,10 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int maxSegmentCount = Math.min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1);
|
||||||
Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource,
|
Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource,
|
||||||
trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(),
|
trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(),
|
||||||
trackSelection.getSelectionData(), sampleFormat, segmentNum);
|
trackSelection.getSelectionData(), sampleFormat, segmentNum, maxSegmentCount);
|
||||||
out.chunk = nextMediaChunk;
|
out.chunk = nextMediaChunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +274,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
RepresentationHolder representationHolder =
|
RepresentationHolder representationHolder =
|
||||||
representationHolders[trackSelection.indexOf(chunk.trackFormat)];
|
representationHolders[trackSelection.indexOf(chunk.trackFormat)];
|
||||||
int lastAvailableSegmentNum = representationHolder.getLastSegmentNum();
|
int lastAvailableSegmentNum = representationHolder.getLastSegmentNum();
|
||||||
if (((MediaChunk) chunk).chunkIndex >= lastAvailableSegmentNum) {
|
if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) {
|
||||||
missingLastSegment = true;
|
missingLastSegment = true;
|
||||||
return 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,
|
DataSource dataSource, Format trackFormat, int trackSelectionReason,
|
||||||
Object trackSelectionData, RangedUri initializationUri, RangedUri indexUri) {
|
Object trackSelectionData, RangedUri initializationUri, RangedUri indexUri) {
|
||||||
RangedUri requestUri;
|
RangedUri requestUri;
|
||||||
@ -305,24 +319,38 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper);
|
trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource,
|
private static Chunk newMediaChunk(RepresentationHolder representationHolder,
|
||||||
Format trackFormat, int trackSelectionReason,
|
DataSource dataSource, Format trackFormat, int trackSelectionReason,
|
||||||
Object trackSelectionData, Format sampleFormat, int segmentNum) {
|
Object trackSelectionData, Format sampleFormat, int firstSegmentNum, int maxSegmentCount) {
|
||||||
Representation representation = representationHolder.representation;
|
Representation representation = representationHolder.representation;
|
||||||
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum);
|
long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum);
|
||||||
long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum);
|
RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum);
|
||||||
RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum);
|
String baseUrl = representation.baseUrl;
|
||||||
DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(representation.baseUrl),
|
|
||||||
segmentUri.start, segmentUri.length, representation.getCacheKey());
|
|
||||||
|
|
||||||
if (representationHolder.extractorWrapper == null) {
|
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,
|
return new SingleSampleMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason,
|
||||||
trackSelectionData, startTimeUs, endTimeUs, segmentNum, trackFormat);
|
trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, trackFormat);
|
||||||
} else {
|
} 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;
|
long sampleOffsetUs = -representation.presentationTimeOffsetUs;
|
||||||
return new ContainerMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason,
|
return new ContainerMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason,
|
||||||
trackSelectionData, startTimeUs, endTimeUs, segmentNum, sampleOffsetUs,
|
trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, segmentCount,
|
||||||
representationHolder.extractorWrapper, sampleFormat);
|
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.
|
// To convert them the absolute timestamps, we need to set sampleOffsetUs to chunkStartTimeUs.
|
||||||
long sampleOffsetUs = chunkStartTimeUs;
|
long sampleOffsetUs = chunkStartTimeUs;
|
||||||
return new ContainerMediaChunk(dataSource, dataSpec, format, trackSelectionReason,
|
return new ContainerMediaChunk(dataSource, dataSpec, format, trackSelectionReason,
|
||||||
trackSelectionData, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, sampleOffsetUs,
|
trackSelectionData, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, 1, sampleOffsetUs,
|
||||||
extractorWrapper, format);
|
extractorWrapper, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user