Define DashSegmentIndex wrapper.
This paves the way for SegmentTemplate and SegmentList based mpds, which will implement DashSegmentIndex directly rather than parsing an index from the media stream. - Define DashSegmentIndex. - Make use of DashSegmentIndex in chunk sources. - Define an implementation of DashSegmentIndex that wraps a SegmentIndex. - Add method that will allow Representations to return a DashSegmentIndex directly in the future. - Add support for non-contiguous index and initialization data in media streams. For the Webm case this isn't enabled yet due to extractor limitations. - Removed ability to fetch multiple chunks. This functionality does not extend properly to SegmentList and SegmentTemplate variants of DASH.
This commit is contained in:
parent
d7d14037b8
commit
87461821fe
@ -129,6 +129,10 @@ package com.google.android.exoplayer.demo;
|
||||
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
|
||||
+ "&expire=19000000000&signature=88DC53943385CED8CF9F37ADD9E9843E3BF621E6."
|
||||
+ "22727BB612D24AA4FACE4EF62726F9461A9BF57A&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true),
|
||||
new Sample("WV: 30s license duration", "f9a34cab7b05881a",
|
||||
"http://dash.edgesuite.net/digitalprimates/fraunhofer/480p_video/heaac_2_0_with_video/ElephantsDream/elephants_dream_480p_heaac2_0.mpd", DemoUtil.TYPE_DASH_VOD, false, true),
|
||||
|
||||
|
||||
};
|
||||
|
||||
public static final Sample[] MISC = new Sample[] {
|
||||
|
@ -100,6 +100,11 @@ public class Format {
|
||||
this.bandwidth = bitrate / 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements equality based on {@link #id} only.
|
||||
*/
|
||||
|
@ -27,15 +27,14 @@ import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation;
|
||||
import com.google.android.exoplayer.chunk.MediaChunk;
|
||||
import com.google.android.exoplayer.chunk.Mp4MediaChunk;
|
||||
import com.google.android.exoplayer.dash.mpd.RangedUri;
|
||||
import com.google.android.exoplayer.dash.mpd.Representation;
|
||||
import com.google.android.exoplayer.parser.SegmentIndex;
|
||||
import com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSpec;
|
||||
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.util.Log;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
@ -47,26 +46,17 @@ import java.util.List;
|
||||
*/
|
||||
public class DashMp4ChunkSource implements ChunkSource {
|
||||
|
||||
public static final int DEFAULT_NUM_SEGMENTS_PER_CHUNK = 1;
|
||||
|
||||
private static final int EXPECTED_INITIALIZATION_RESULT =
|
||||
FragmentedMp4Extractor.RESULT_END_OF_STREAM
|
||||
| FragmentedMp4Extractor.RESULT_READ_MOOV
|
||||
| FragmentedMp4Extractor.RESULT_READ_SIDX;
|
||||
|
||||
private static final String TAG = "DashMp4ChunkSource";
|
||||
|
||||
private final TrackInfo trackInfo;
|
||||
private final DataSource dataSource;
|
||||
private final FormatEvaluator evaluator;
|
||||
private final Evaluation evaluation;
|
||||
private final int maxWidth;
|
||||
private final int maxHeight;
|
||||
private final int numSegmentsPerChunk;
|
||||
|
||||
private final Format[] formats;
|
||||
private final HashMap<String, Representation> representations;
|
||||
private final HashMap<String, FragmentedMp4Extractor> extractors;
|
||||
private final HashMap<String, DashSegmentIndex> segmentIndexes;
|
||||
|
||||
private boolean lastChunkWasInitialization;
|
||||
|
||||
@ -77,23 +67,11 @@ public class DashMp4ChunkSource implements ChunkSource {
|
||||
*/
|
||||
public DashMp4ChunkSource(DataSource dataSource, FormatEvaluator evaluator,
|
||||
Representation... representations) {
|
||||
this(dataSource, evaluator, DEFAULT_NUM_SEGMENTS_PER_CHUNK, representations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
||||
* @param evaluator Selects from the available formats.
|
||||
* @param numSegmentsPerChunk The number of segments (as defined in the stream's segment index)
|
||||
* that should be grouped into a single chunk.
|
||||
* @param representations The representations to be considered by the source.
|
||||
*/
|
||||
public DashMp4ChunkSource(DataSource dataSource, FormatEvaluator evaluator,
|
||||
int numSegmentsPerChunk, Representation... representations) {
|
||||
this.dataSource = dataSource;
|
||||
this.evaluator = evaluator;
|
||||
this.numSegmentsPerChunk = numSegmentsPerChunk;
|
||||
this.formats = new Format[representations.length];
|
||||
this.extractors = new HashMap<String, FragmentedMp4Extractor>();
|
||||
this.segmentIndexes = new HashMap<String, DashSegmentIndex>();
|
||||
this.representations = new HashMap<String, Representation>();
|
||||
this.trackInfo = new TrackInfo(representations[0].format.mimeType,
|
||||
representations[0].periodDuration * 1000);
|
||||
@ -106,6 +84,10 @@ public class DashMp4ChunkSource implements ChunkSource {
|
||||
maxHeight = Math.max(formats[i].height, maxHeight);
|
||||
extractors.put(formats[i].id, new FragmentedMp4Extractor());
|
||||
this.representations.put(formats[i].id, representations[i]);
|
||||
DashSegmentIndex segmentIndex = representations[i].getIndex();
|
||||
if (segmentIndex != null) {
|
||||
segmentIndexes.put(formats[i].id, segmentIndex);
|
||||
}
|
||||
}
|
||||
this.maxWidth = maxWidth;
|
||||
this.maxHeight = maxHeight;
|
||||
@ -161,29 +143,39 @@ public class DashMp4ChunkSource implements ChunkSource {
|
||||
|
||||
Representation selectedRepresentation = representations.get(selectedFormat.id);
|
||||
FragmentedMp4Extractor extractor = extractors.get(selectedRepresentation.format.id);
|
||||
|
||||
RangedUri pendingInitializationUri = null;
|
||||
RangedUri pendingIndexUri = null;
|
||||
if (extractor.getTrack() == null) {
|
||||
Chunk initializationChunk = newInitializationChunk(selectedRepresentation, extractor,
|
||||
dataSource, evaluation.trigger);
|
||||
pendingInitializationUri = selectedRepresentation.getInitializationUri();
|
||||
}
|
||||
if (!segmentIndexes.containsKey(selectedRepresentation.format.id)) {
|
||||
pendingIndexUri = selectedRepresentation.getIndexUri();
|
||||
}
|
||||
if (pendingInitializationUri != null || pendingIndexUri != null) {
|
||||
// We have initialization and/or index requests to make.
|
||||
Chunk initializationChunk = newInitializationChunk(pendingInitializationUri, pendingIndexUri,
|
||||
selectedRepresentation, extractor, dataSource, evaluation.trigger);
|
||||
lastChunkWasInitialization = true;
|
||||
out.chunk = initializationChunk;
|
||||
return;
|
||||
}
|
||||
|
||||
int nextIndex;
|
||||
int nextSegmentNum;
|
||||
DashSegmentIndex segmentIndex = segmentIndexes.get(selectedRepresentation.format.id);
|
||||
if (queue.isEmpty()) {
|
||||
nextIndex = Util.binarySearchFloor(extractor.getSegmentIndex().timesUs, seekPositionUs,
|
||||
true, true);
|
||||
nextSegmentNum = segmentIndex.getSegmentNum(seekPositionUs);
|
||||
} else {
|
||||
nextIndex = queue.get(out.queueSize - 1).nextChunkIndex;
|
||||
nextSegmentNum = queue.get(out.queueSize - 1).nextChunkIndex;
|
||||
}
|
||||
|
||||
if (nextIndex == -1) {
|
||||
if (nextSegmentNum == -1) {
|
||||
out.chunk = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Chunk nextMediaChunk = newMediaChunk(selectedRepresentation, extractor, dataSource,
|
||||
extractor.getSegmentIndex(), nextIndex, evaluation.trigger, numSegmentsPerChunk);
|
||||
Chunk nextMediaChunk = newMediaChunk(selectedRepresentation, segmentIndex, extractor,
|
||||
dataSource, nextSegmentNum, evaluation.trigger);
|
||||
lastChunkWasInitialization = false;
|
||||
out.chunk = nextMediaChunk;
|
||||
}
|
||||
@ -198,75 +190,75 @@ public class DashMp4ChunkSource implements ChunkSource {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
private static Chunk newInitializationChunk(Representation representation,
|
||||
FragmentedMp4Extractor extractor, DataSource dataSource, int trigger) {
|
||||
DataSpec dataSpec = new DataSpec(representation.uri, 0, representation.indexEnd + 1,
|
||||
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
|
||||
Representation representation, FragmentedMp4Extractor extractor, DataSource dataSource,
|
||||
int trigger) {
|
||||
int expectedExtractorResult = FragmentedMp4Extractor.RESULT_END_OF_STREAM;
|
||||
long indexAnchor = 0;
|
||||
RangedUri requestUri;
|
||||
if (initializationUri != null) {
|
||||
// It's common for initialization and index data to be stored adjacently. Attempt to merge
|
||||
// the two requests together to request at once.
|
||||
expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_MOOV;
|
||||
requestUri = initializationUri.attemptMerge(indexUri);
|
||||
if (requestUri != null) {
|
||||
expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_SIDX;
|
||||
indexAnchor = indexUri.start + indexUri.length;
|
||||
} else {
|
||||
requestUri = initializationUri;
|
||||
}
|
||||
} else {
|
||||
requestUri = indexUri;
|
||||
indexAnchor = indexUri.start + indexUri.length;
|
||||
expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_SIDX;
|
||||
}
|
||||
DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length,
|
||||
representation.getCacheKey());
|
||||
return new InitializationMp4Loadable(dataSource, dataSpec, trigger, extractor, representation);
|
||||
return new InitializationMp4Loadable(dataSource, dataSpec, trigger, representation.format,
|
||||
extractor, expectedExtractorResult, indexAnchor);
|
||||
}
|
||||
|
||||
private static Chunk newMediaChunk(Representation representation,
|
||||
FragmentedMp4Extractor extractor, DataSource dataSource, SegmentIndex sidx, int index,
|
||||
int trigger, int numSegmentsPerChunk) {
|
||||
|
||||
// Computes the segments to included in the next fetch.
|
||||
int numSegmentsToFetch = Math.min(numSegmentsPerChunk, sidx.length - index);
|
||||
int lastSegmentInChunk = index + numSegmentsToFetch - 1;
|
||||
int nextIndex = lastSegmentInChunk == sidx.length - 1 ? -1 : lastSegmentInChunk + 1;
|
||||
|
||||
long startTimeUs = sidx.timesUs[index];
|
||||
|
||||
// Compute the end time, prefer to use next segment start time if there is a next segment.
|
||||
long endTimeUs = nextIndex == -1 ?
|
||||
sidx.timesUs[lastSegmentInChunk] + sidx.durationsUs[lastSegmentInChunk] :
|
||||
sidx.timesUs[nextIndex];
|
||||
|
||||
long offset = (int) representation.indexEnd + 1 + sidx.offsets[index];
|
||||
|
||||
// Compute combined segments byte length.
|
||||
long size = 0;
|
||||
for (int i = index; i <= lastSegmentInChunk; i++) {
|
||||
size += sidx.sizes[i];
|
||||
}
|
||||
|
||||
DataSpec dataSpec = new DataSpec(representation.uri, offset, size,
|
||||
private Chunk newMediaChunk(Representation representation, DashSegmentIndex segmentIndex,
|
||||
FragmentedMp4Extractor extractor, DataSource dataSource, int segmentNum, int trigger) {
|
||||
int lastSegmentNum = segmentIndex.getLastSegmentNum();
|
||||
int nextSegmentNum = segmentNum == lastSegmentNum ? -1 : segmentNum + 1;
|
||||
long startTimeUs = segmentIndex.getTimeUs(segmentNum);
|
||||
long endTimeUs = segmentNum < lastSegmentNum ? segmentIndex.getTimeUs(segmentNum + 1)
|
||||
: startTimeUs + segmentIndex.getDurationUs(segmentNum);
|
||||
RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum);
|
||||
DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
|
||||
representation.getCacheKey());
|
||||
return new Mp4MediaChunk(dataSource, dataSpec, representation.format, trigger, startTimeUs,
|
||||
endTimeUs, nextIndex, extractor, false, 0);
|
||||
endTimeUs, nextSegmentNum, extractor, false, 0);
|
||||
}
|
||||
|
||||
private static class InitializationMp4Loadable extends Chunk {
|
||||
private class InitializationMp4Loadable extends Chunk {
|
||||
|
||||
private final Representation representation;
|
||||
private final FragmentedMp4Extractor extractor;
|
||||
private final int expectedExtractorResult;
|
||||
private final long indexAnchor;
|
||||
private final Uri uri;
|
||||
|
||||
public InitializationMp4Loadable(DataSource dataSource, DataSpec dataSpec, int trigger,
|
||||
FragmentedMp4Extractor extractor, Representation representation) {
|
||||
super(dataSource, dataSpec, representation.format, trigger);
|
||||
Format format, FragmentedMp4Extractor extractor, int expectedExtractorResult,
|
||||
long indexAnchor) {
|
||||
super(dataSource, dataSpec, format, trigger);
|
||||
this.extractor = extractor;
|
||||
this.representation = representation;
|
||||
this.expectedExtractorResult = expectedExtractorResult;
|
||||
this.indexAnchor = indexAnchor;
|
||||
this.uri = dataSpec.uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void consumeStream(NonBlockingInputStream stream) throws IOException {
|
||||
int result = extractor.read(stream, null);
|
||||
if (result != EXPECTED_INITIALIZATION_RESULT) {
|
||||
throw new ParserException("Invalid initialization data");
|
||||
if (result != expectedExtractorResult) {
|
||||
throw new ParserException("Invalid extractor result. Expected "
|
||||
+ expectedExtractorResult + ", got " + result);
|
||||
}
|
||||
validateSegmentIndex(extractor.getSegmentIndex());
|
||||
}
|
||||
|
||||
private void validateSegmentIndex(SegmentIndex segmentIndex) {
|
||||
long expectedIndexLen = representation.indexEnd - representation.indexStart + 1;
|
||||
if (segmentIndex.sizeBytes != expectedIndexLen) {
|
||||
Log.w(TAG, "Sidx length mismatch: sidxLen = " + segmentIndex.sizeBytes +
|
||||
", ExpectedLen = " + expectedIndexLen);
|
||||
}
|
||||
long sidxContentLength = segmentIndex.offsets[segmentIndex.length - 1] +
|
||||
segmentIndex.sizes[segmentIndex.length - 1] + representation.indexEnd + 1;
|
||||
if (sidxContentLength != representation.contentLength) {
|
||||
Log.w(TAG, "ContentLength mismatch: Actual = " + sidxContentLength +
|
||||
", Expected = " + representation.contentLength);
|
||||
if ((result & FragmentedMp4Extractor.RESULT_READ_SIDX) != 0) {
|
||||
segmentIndexes.put(format.id,
|
||||
new DashWrappingSegmentIndex(extractor.getSegmentIndex(), uri, indexAnchor));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,16 +27,15 @@ import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation;
|
||||
import com.google.android.exoplayer.chunk.MediaChunk;
|
||||
import com.google.android.exoplayer.chunk.WebmMediaChunk;
|
||||
import com.google.android.exoplayer.dash.mpd.RangedUri;
|
||||
import com.google.android.exoplayer.dash.mpd.Representation;
|
||||
import com.google.android.exoplayer.parser.SegmentIndex;
|
||||
import com.google.android.exoplayer.parser.webm.DefaultWebmExtractor;
|
||||
import com.google.android.exoplayer.parser.webm.WebmExtractor;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSpec;
|
||||
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.util.Log;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
@ -48,34 +47,27 @@ import java.util.List;
|
||||
*/
|
||||
public class DashWebmChunkSource implements ChunkSource {
|
||||
|
||||
private static final String TAG = "DashWebmChunkSource";
|
||||
|
||||
private final TrackInfo trackInfo;
|
||||
private final DataSource dataSource;
|
||||
private final FormatEvaluator evaluator;
|
||||
private final Evaluation evaluation;
|
||||
private final int maxWidth;
|
||||
private final int maxHeight;
|
||||
private final int numSegmentsPerChunk;
|
||||
|
||||
private final Format[] formats;
|
||||
private final HashMap<String, Representation> representations;
|
||||
private final HashMap<String, WebmExtractor> extractors;
|
||||
private final HashMap<String, DashSegmentIndex> segmentIndexes;
|
||||
|
||||
private boolean lastChunkWasInitialization;
|
||||
|
||||
public DashWebmChunkSource(
|
||||
DataSource dataSource, FormatEvaluator evaluator, Representation... representations) {
|
||||
this(dataSource, evaluator, 1, representations);
|
||||
}
|
||||
|
||||
public DashWebmChunkSource(DataSource dataSource, FormatEvaluator evaluator,
|
||||
int numSegmentsPerChunk, Representation... representations) {
|
||||
Representation... representations) {
|
||||
this.dataSource = dataSource;
|
||||
this.evaluator = evaluator;
|
||||
this.numSegmentsPerChunk = numSegmentsPerChunk;
|
||||
this.formats = new Format[representations.length];
|
||||
this.extractors = new HashMap<String, WebmExtractor>();
|
||||
this.segmentIndexes = new HashMap<String, DashSegmentIndex>();
|
||||
this.representations = new HashMap<String, Representation>();
|
||||
this.trackInfo = new TrackInfo(
|
||||
representations[0].format.mimeType, representations[0].periodDuration * 1000);
|
||||
@ -88,6 +80,10 @@ public class DashWebmChunkSource implements ChunkSource {
|
||||
maxHeight = Math.max(formats[i].height, maxHeight);
|
||||
extractors.put(formats[i].id, new DefaultWebmExtractor());
|
||||
this.representations.put(formats[i].id, representations[i]);
|
||||
DashSegmentIndex segmentIndex = representations[i].getIndex();
|
||||
if (segmentIndex != null) {
|
||||
segmentIndexes.put(formats[i].id, segmentIndex);
|
||||
}
|
||||
}
|
||||
this.maxWidth = maxWidth;
|
||||
this.maxHeight = maxHeight;
|
||||
@ -143,28 +139,34 @@ public class DashWebmChunkSource implements ChunkSource {
|
||||
|
||||
Representation selectedRepresentation = representations.get(selectedFormat.id);
|
||||
WebmExtractor extractor = extractors.get(selectedRepresentation.format.id);
|
||||
|
||||
if (!extractor.isPrepared()) {
|
||||
Chunk initializationChunk = newInitializationChunk(selectedRepresentation, extractor,
|
||||
dataSource, evaluation.trigger);
|
||||
// TODO: This code forces cues to exist and to immediately follow the initialization
|
||||
// data. Webm extractor should be generalized to allow cues to be optional. See [redacted].
|
||||
RangedUri initializationUri = selectedRepresentation.getInitializationUri().attemptMerge(
|
||||
selectedRepresentation.getIndexUri());
|
||||
Chunk initializationChunk = newInitializationChunk(initializationUri, selectedRepresentation,
|
||||
extractor, dataSource, evaluation.trigger);
|
||||
lastChunkWasInitialization = true;
|
||||
out.chunk = initializationChunk;
|
||||
return;
|
||||
}
|
||||
|
||||
int nextIndex;
|
||||
int nextSegmentNum;
|
||||
DashSegmentIndex segmentIndex = segmentIndexes.get(selectedRepresentation.format.id);
|
||||
if (queue.isEmpty()) {
|
||||
nextIndex = Util.binarySearchFloor(extractor.getCues().timesUs, seekPositionUs, true, true);
|
||||
nextSegmentNum = segmentIndex.getSegmentNum(seekPositionUs);
|
||||
} else {
|
||||
nextIndex = queue.get(out.queueSize - 1).nextChunkIndex;
|
||||
nextSegmentNum = queue.get(out.queueSize - 1).nextChunkIndex;
|
||||
}
|
||||
|
||||
if (nextIndex == -1) {
|
||||
if (nextSegmentNum == -1) {
|
||||
out.chunk = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Chunk nextMediaChunk = newMediaChunk(selectedRepresentation, extractor, dataSource,
|
||||
extractor.getCues(), nextIndex, evaluation.trigger, numSegmentsPerChunk);
|
||||
Chunk nextMediaChunk = newMediaChunk(selectedRepresentation, segmentIndex, extractor,
|
||||
dataSource, nextSegmentNum, evaluation.trigger);
|
||||
lastChunkWasInitialization = false;
|
||||
out.chunk = nextMediaChunk;
|
||||
}
|
||||
@ -179,53 +181,38 @@ public class DashWebmChunkSource implements ChunkSource {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
private static Chunk newInitializationChunk(Representation representation,
|
||||
private Chunk newInitializationChunk(RangedUri initializationUri, Representation representation,
|
||||
WebmExtractor extractor, DataSource dataSource, int trigger) {
|
||||
DataSpec dataSpec = new DataSpec(representation.uri, 0, representation.indexEnd + 1,
|
||||
representation.getCacheKey());
|
||||
return new InitializationWebmLoadable(dataSource, dataSpec, trigger, extractor, representation);
|
||||
DataSpec dataSpec = new DataSpec(initializationUri.getUri(), initializationUri.start,
|
||||
initializationUri.length, representation.getCacheKey());
|
||||
return new InitializationWebmLoadable(dataSource, dataSpec, trigger, representation.format,
|
||||
extractor);
|
||||
}
|
||||
|
||||
private static Chunk newMediaChunk(Representation representation,
|
||||
WebmExtractor extractor, DataSource dataSource, SegmentIndex cues, int index,
|
||||
int trigger, int numSegmentsPerChunk) {
|
||||
|
||||
// Computes the segments to included in the next fetch.
|
||||
int numSegmentsToFetch = Math.min(numSegmentsPerChunk, cues.length - index);
|
||||
int lastSegmentInChunk = index + numSegmentsToFetch - 1;
|
||||
int nextIndex = lastSegmentInChunk == cues.length - 1 ? -1 : lastSegmentInChunk + 1;
|
||||
|
||||
long startTimeUs = cues.timesUs[index];
|
||||
|
||||
// Compute the end time, prefer to use next segment start time if there is a next segment.
|
||||
long endTimeUs = nextIndex == -1 ?
|
||||
cues.timesUs[lastSegmentInChunk] + cues.durationsUs[lastSegmentInChunk] :
|
||||
cues.timesUs[nextIndex];
|
||||
|
||||
long offset = cues.offsets[index];
|
||||
|
||||
// Compute combined segments byte length.
|
||||
long size = 0;
|
||||
for (int i = index; i <= lastSegmentInChunk; i++) {
|
||||
size += cues.sizes[i];
|
||||
}
|
||||
|
||||
DataSpec dataSpec = new DataSpec(representation.uri, offset, size,
|
||||
private Chunk newMediaChunk(Representation representation, DashSegmentIndex segmentIndex,
|
||||
WebmExtractor extractor, DataSource dataSource, int segmentNum, int trigger) {
|
||||
int lastSegmentNum = segmentIndex.getLastSegmentNum();
|
||||
int nextSegmentNum = segmentNum == lastSegmentNum ? -1 : segmentNum + 1;
|
||||
long startTimeUs = segmentIndex.getTimeUs(segmentNum);
|
||||
long endTimeUs = segmentNum < lastSegmentNum ? segmentIndex.getTimeUs(segmentNum + 1)
|
||||
: startTimeUs + segmentIndex.getDurationUs(segmentNum);
|
||||
RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum);
|
||||
DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
|
||||
representation.getCacheKey());
|
||||
return new WebmMediaChunk(dataSource, dataSpec, representation.format, trigger, extractor,
|
||||
startTimeUs, endTimeUs, nextIndex);
|
||||
startTimeUs, endTimeUs, nextSegmentNum);
|
||||
}
|
||||
|
||||
private static class InitializationWebmLoadable extends Chunk {
|
||||
private class InitializationWebmLoadable extends Chunk {
|
||||
|
||||
private final Representation representation;
|
||||
private final WebmExtractor extractor;
|
||||
private final Uri uri;
|
||||
|
||||
public InitializationWebmLoadable(DataSource dataSource, DataSpec dataSpec, int trigger,
|
||||
WebmExtractor extractor, Representation representation) {
|
||||
super(dataSource, dataSpec, representation.format, trigger);
|
||||
Format format, WebmExtractor extractor) {
|
||||
super(dataSource, dataSpec, format, trigger);
|
||||
this.extractor = extractor;
|
||||
this.representation = representation;
|
||||
this.uri = dataSpec.uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -234,22 +221,7 @@ public class DashWebmChunkSource implements ChunkSource {
|
||||
if (!extractor.isPrepared()) {
|
||||
throw new ParserException("Invalid initialization data");
|
||||
}
|
||||
validateCues(extractor.getCues());
|
||||
}
|
||||
|
||||
private void validateCues(SegmentIndex cues) {
|
||||
long expectedSizeBytes = representation.indexEnd - representation.indexStart + 1;
|
||||
if (cues.sizeBytes != expectedSizeBytes) {
|
||||
Log.w(TAG, "Cues length mismatch: got " + cues.sizeBytes +
|
||||
" but expected " + expectedSizeBytes);
|
||||
}
|
||||
long expectedContentLength = cues.offsets[cues.length - 1] +
|
||||
cues.sizes[cues.length - 1] + representation.indexEnd + 1;
|
||||
if (representation.contentLength > 0
|
||||
&& expectedContentLength != representation.contentLength) {
|
||||
Log.w(TAG, "ContentLength mismatch: got " + expectedContentLength +
|
||||
" but expected " + representation.contentLength);
|
||||
}
|
||||
segmentIndexes.put(format.id, new DashWrappingSegmentIndex(extractor.getCues(), uri, 0));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ public final class RangedUri {
|
||||
private final Uri baseUri;
|
||||
private final String stringUri;
|
||||
|
||||
private int hashCode;
|
||||
|
||||
/**
|
||||
* Constructs an ranged uri.
|
||||
* <p>
|
||||
@ -82,4 +84,55 @@ public final class RangedUri {
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to merge this {@link RangedUri} with another.
|
||||
* <p>
|
||||
* A merge is successful if both instances define the same {@link Uri}, and if one starte the
|
||||
* byte after the other ends, forming a contiguous region with no overlap.
|
||||
* <p>
|
||||
* If {@code other} is null then the merge is considered unsuccessful, and null is returned.
|
||||
*
|
||||
* @param other The {@link RangedUri} to merge.
|
||||
* @return The merged {@link RangedUri} if the merge was successful. Null otherwise.
|
||||
*/
|
||||
public RangedUri attemptMerge(RangedUri other) {
|
||||
if (other == null || !getUri().equals(other.getUri())) {
|
||||
return null;
|
||||
} else if (length != -1 && start + length == other.start) {
|
||||
return new RangedUri(baseUri, stringUri, start,
|
||||
other.length == -1 ? -1 : length + other.length);
|
||||
} else if (other.length != -1 && other.start + other.length == start) {
|
||||
return new RangedUri(baseUri, stringUri, other.start,
|
||||
length == -1 ? -1 : other.length + length);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (hashCode == 0) {
|
||||
int result = 17;
|
||||
result = 31 * result + (int) start;
|
||||
result = 31 * result + (int) length;
|
||||
result = 31 * result + getUri().hashCode();
|
||||
hashCode = result;
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
RangedUri other = (RangedUri) obj;
|
||||
return this.start == other.start
|
||||
&& this.length == other.length
|
||||
&& getUri().equals(other.getUri());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
package com.google.android.exoplayer.dash.mpd;
|
||||
|
||||
import com.google.android.exoplayer.chunk.Format;
|
||||
import com.google.android.exoplayer.dash.DashSegmentIndex;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
@ -79,6 +80,37 @@ public class Representation {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link RangedUri} defining the location of the representation's initialization data.
|
||||
* May be null if no initialization data exists.
|
||||
*
|
||||
* @return A {@link RangedUri} defining the location of the initialization data, or null.
|
||||
*/
|
||||
public RangedUri getInitializationUri() {
|
||||
return new RangedUri(uri, null, initializationStart,
|
||||
initializationEnd - initializationStart + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link RangedUri} defining the location of the representation's segment index. Null if
|
||||
* the representation provides an index directly.
|
||||
*
|
||||
* @return The location of the segment index, or null.
|
||||
*/
|
||||
public RangedUri getIndexUri() {
|
||||
return new RangedUri(uri, null, indexStart, indexEnd - indexStart + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a segment index, if the representation is able to provide one directly. Null if the
|
||||
* segment index is defined externally.
|
||||
*
|
||||
* @return The segment index, or null.
|
||||
*/
|
||||
public DashSegmentIndex getIndex() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a cache key for the {@link Representation}, in the format
|
||||
* {@code contentId + "." + format.id + "." + revisionId}.
|
||||
|
Loading…
x
Reference in New Issue
Block a user