Commit 87461821 by Oliver Woodman

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.
parent d7d14037
...@@ -129,6 +129,10 @@ package com.google.android.exoplayer.demo; ...@@ -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" + "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=88DC53943385CED8CF9F37ADD9E9843E3BF621E6." + "&expire=19000000000&signature=88DC53943385CED8CF9F37ADD9E9843E3BF621E6."
+ "22727BB612D24AA4FACE4EF62726F9461A9BF57A&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true), + "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[] { public static final Sample[] MISC = new Sample[] {
......
...@@ -100,6 +100,11 @@ public class Format { ...@@ -100,6 +100,11 @@ public class Format {
this.bandwidth = bitrate / 8; this.bandwidth = bitrate / 8;
} }
@Override
public int hashCode() {
return id.hashCode();
}
/** /**
* Implements equality based on {@link #id} only. * Implements equality based on {@link #id} only.
*/ */
......
...@@ -27,15 +27,14 @@ import com.google.android.exoplayer.chunk.FormatEvaluator; ...@@ -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.FormatEvaluator.Evaluation;
import com.google.android.exoplayer.chunk.MediaChunk; import com.google.android.exoplayer.chunk.MediaChunk;
import com.google.android.exoplayer.chunk.Mp4MediaChunk; 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.dash.mpd.Representation;
import com.google.android.exoplayer.parser.SegmentIndex;
import com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer.parser.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.NonBlockingInputStream; 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
...@@ -47,26 +46,17 @@ import java.util.List; ...@@ -47,26 +46,17 @@ import java.util.List;
*/ */
public class DashMp4ChunkSource implements ChunkSource { 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 TrackInfo trackInfo;
private final DataSource dataSource; private final DataSource dataSource;
private final FormatEvaluator evaluator; private final FormatEvaluator evaluator;
private final Evaluation evaluation; private final Evaluation evaluation;
private final int maxWidth; private final int maxWidth;
private final int maxHeight; private final int maxHeight;
private final int numSegmentsPerChunk;
private final Format[] formats; private final Format[] formats;
private final HashMap<String, Representation> representations; private final HashMap<String, Representation> representations;
private final HashMap<String, FragmentedMp4Extractor> extractors; private final HashMap<String, FragmentedMp4Extractor> extractors;
private final HashMap<String, DashSegmentIndex> segmentIndexes;
private boolean lastChunkWasInitialization; private boolean lastChunkWasInitialization;
...@@ -77,23 +67,11 @@ public class DashMp4ChunkSource implements ChunkSource { ...@@ -77,23 +67,11 @@ public class DashMp4ChunkSource implements ChunkSource {
*/ */
public DashMp4ChunkSource(DataSource dataSource, FormatEvaluator evaluator, public DashMp4ChunkSource(DataSource dataSource, FormatEvaluator evaluator,
Representation... representations) { 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.dataSource = dataSource;
this.evaluator = evaluator; this.evaluator = evaluator;
this.numSegmentsPerChunk = numSegmentsPerChunk;
this.formats = new Format[representations.length]; this.formats = new Format[representations.length];
this.extractors = new HashMap<String, FragmentedMp4Extractor>(); this.extractors = new HashMap<String, FragmentedMp4Extractor>();
this.segmentIndexes = new HashMap<String, DashSegmentIndex>();
this.representations = new HashMap<String, Representation>(); this.representations = new HashMap<String, Representation>();
this.trackInfo = new TrackInfo(representations[0].format.mimeType, this.trackInfo = new TrackInfo(representations[0].format.mimeType,
representations[0].periodDuration * 1000); representations[0].periodDuration * 1000);
...@@ -106,6 +84,10 @@ public class DashMp4ChunkSource implements ChunkSource { ...@@ -106,6 +84,10 @@ public class DashMp4ChunkSource implements ChunkSource {
maxHeight = Math.max(formats[i].height, maxHeight); maxHeight = Math.max(formats[i].height, maxHeight);
extractors.put(formats[i].id, new FragmentedMp4Extractor()); extractors.put(formats[i].id, new FragmentedMp4Extractor());
this.representations.put(formats[i].id, representations[i]); 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.maxWidth = maxWidth;
this.maxHeight = maxHeight; this.maxHeight = maxHeight;
...@@ -161,29 +143,39 @@ public class DashMp4ChunkSource implements ChunkSource { ...@@ -161,29 +143,39 @@ public class DashMp4ChunkSource implements ChunkSource {
Representation selectedRepresentation = representations.get(selectedFormat.id); Representation selectedRepresentation = representations.get(selectedFormat.id);
FragmentedMp4Extractor extractor = extractors.get(selectedRepresentation.format.id); FragmentedMp4Extractor extractor = extractors.get(selectedRepresentation.format.id);
RangedUri pendingInitializationUri = null;
RangedUri pendingIndexUri = null;
if (extractor.getTrack() == null) { if (extractor.getTrack() == null) {
Chunk initializationChunk = newInitializationChunk(selectedRepresentation, extractor, pendingInitializationUri = selectedRepresentation.getInitializationUri();
dataSource, evaluation.trigger); }
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; lastChunkWasInitialization = true;
out.chunk = initializationChunk; out.chunk = initializationChunk;
return; return;
} }
int nextIndex; int nextSegmentNum;
DashSegmentIndex segmentIndex = segmentIndexes.get(selectedRepresentation.format.id);
if (queue.isEmpty()) { if (queue.isEmpty()) {
nextIndex = Util.binarySearchFloor(extractor.getSegmentIndex().timesUs, seekPositionUs, nextSegmentNum = segmentIndex.getSegmentNum(seekPositionUs);
true, true);
} else { } else {
nextIndex = queue.get(out.queueSize - 1).nextChunkIndex; nextSegmentNum = queue.get(out.queueSize - 1).nextChunkIndex;
} }
if (nextIndex == -1) { if (nextSegmentNum == -1) {
out.chunk = null; out.chunk = null;
return; return;
} }
Chunk nextMediaChunk = newMediaChunk(selectedRepresentation, extractor, dataSource, Chunk nextMediaChunk = newMediaChunk(selectedRepresentation, segmentIndex, extractor,
extractor.getSegmentIndex(), nextIndex, evaluation.trigger, numSegmentsPerChunk); dataSource, nextSegmentNum, evaluation.trigger);
lastChunkWasInitialization = false; lastChunkWasInitialization = false;
out.chunk = nextMediaChunk; out.chunk = nextMediaChunk;
} }
...@@ -198,75 +190,75 @@ public class DashMp4ChunkSource implements ChunkSource { ...@@ -198,75 +190,75 @@ public class DashMp4ChunkSource implements ChunkSource {
// Do nothing. // Do nothing.
} }
private static Chunk newInitializationChunk(Representation representation, private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
FragmentedMp4Extractor extractor, DataSource dataSource, int trigger) { Representation representation, FragmentedMp4Extractor extractor, DataSource dataSource,
DataSpec dataSpec = new DataSpec(representation.uri, 0, representation.indexEnd + 1, 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()); 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, private Chunk newMediaChunk(Representation representation, DashSegmentIndex segmentIndex,
FragmentedMp4Extractor extractor, DataSource dataSource, SegmentIndex sidx, int index, FragmentedMp4Extractor extractor, DataSource dataSource, int segmentNum, int trigger) {
int trigger, int numSegmentsPerChunk) { int lastSegmentNum = segmentIndex.getLastSegmentNum();
int nextSegmentNum = segmentNum == lastSegmentNum ? -1 : segmentNum + 1;
// Computes the segments to included in the next fetch. long startTimeUs = segmentIndex.getTimeUs(segmentNum);
int numSegmentsToFetch = Math.min(numSegmentsPerChunk, sidx.length - index); long endTimeUs = segmentNum < lastSegmentNum ? segmentIndex.getTimeUs(segmentNum + 1)
int lastSegmentInChunk = index + numSegmentsToFetch - 1; : startTimeUs + segmentIndex.getDurationUs(segmentNum);
int nextIndex = lastSegmentInChunk == sidx.length - 1 ? -1 : lastSegmentInChunk + 1; RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum);
DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
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,
representation.getCacheKey()); representation.getCacheKey());
return new Mp4MediaChunk(dataSource, dataSpec, representation.format, trigger, startTimeUs, 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 FragmentedMp4Extractor extractor;
private final int expectedExtractorResult;
private final long indexAnchor;
private final Uri uri;
public InitializationMp4Loadable(DataSource dataSource, DataSpec dataSpec, int trigger, public InitializationMp4Loadable(DataSource dataSource, DataSpec dataSpec, int trigger,
FragmentedMp4Extractor extractor, Representation representation) { Format format, FragmentedMp4Extractor extractor, int expectedExtractorResult,
super(dataSource, dataSpec, representation.format, trigger); long indexAnchor) {
super(dataSource, dataSpec, format, trigger);
this.extractor = extractor; this.extractor = extractor;
this.representation = representation; this.expectedExtractorResult = expectedExtractorResult;
this.indexAnchor = indexAnchor;
this.uri = dataSpec.uri;
} }
@Override @Override
protected void consumeStream(NonBlockingInputStream stream) throws IOException { protected void consumeStream(NonBlockingInputStream stream) throws IOException {
int result = extractor.read(stream, null); int result = extractor.read(stream, null);
if (result != EXPECTED_INITIALIZATION_RESULT) { if (result != expectedExtractorResult) {
throw new ParserException("Invalid initialization data"); 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] + if ((result & FragmentedMp4Extractor.RESULT_READ_SIDX) != 0) {
segmentIndex.sizes[segmentIndex.length - 1] + representation.indexEnd + 1; segmentIndexes.put(format.id,
if (sidxContentLength != representation.contentLength) { new DashWrappingSegmentIndex(extractor.getSegmentIndex(), uri, indexAnchor));
Log.w(TAG, "ContentLength mismatch: Actual = " + sidxContentLength +
", Expected = " + representation.contentLength);
} }
} }
......
...@@ -27,16 +27,15 @@ import com.google.android.exoplayer.chunk.FormatEvaluator; ...@@ -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.FormatEvaluator.Evaluation;
import com.google.android.exoplayer.chunk.MediaChunk; import com.google.android.exoplayer.chunk.MediaChunk;
import com.google.android.exoplayer.chunk.WebmMediaChunk; 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.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.DefaultWebmExtractor;
import com.google.android.exoplayer.parser.webm.WebmExtractor; import com.google.android.exoplayer.parser.webm.WebmExtractor;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.NonBlockingInputStream; 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.io.IOException;
import java.util.Arrays; import java.util.Arrays;
...@@ -48,34 +47,27 @@ import java.util.List; ...@@ -48,34 +47,27 @@ import java.util.List;
*/ */
public class DashWebmChunkSource implements ChunkSource { public class DashWebmChunkSource implements ChunkSource {
private static final String TAG = "DashWebmChunkSource";
private final TrackInfo trackInfo; private final TrackInfo trackInfo;
private final DataSource dataSource; private final DataSource dataSource;
private final FormatEvaluator evaluator; private final FormatEvaluator evaluator;
private final Evaluation evaluation; private final Evaluation evaluation;
private final int maxWidth; private final int maxWidth;
private final int maxHeight; private final int maxHeight;
private final int numSegmentsPerChunk;
private final Format[] formats; private final Format[] formats;
private final HashMap<String, Representation> representations; private final HashMap<String, Representation> representations;
private final HashMap<String, WebmExtractor> extractors; private final HashMap<String, WebmExtractor> extractors;
private final HashMap<String, DashSegmentIndex> segmentIndexes;
private boolean lastChunkWasInitialization; private boolean lastChunkWasInitialization;
public DashWebmChunkSource(
DataSource dataSource, FormatEvaluator evaluator, Representation... representations) {
this(dataSource, evaluator, 1, representations);
}
public DashWebmChunkSource(DataSource dataSource, FormatEvaluator evaluator, public DashWebmChunkSource(DataSource dataSource, FormatEvaluator evaluator,
int numSegmentsPerChunk, Representation... representations) { Representation... representations) {
this.dataSource = dataSource; this.dataSource = dataSource;
this.evaluator = evaluator; this.evaluator = evaluator;
this.numSegmentsPerChunk = numSegmentsPerChunk;
this.formats = new Format[representations.length]; this.formats = new Format[representations.length];
this.extractors = new HashMap<String, WebmExtractor>(); this.extractors = new HashMap<String, WebmExtractor>();
this.segmentIndexes = new HashMap<String, DashSegmentIndex>();
this.representations = new HashMap<String, Representation>(); this.representations = new HashMap<String, Representation>();
this.trackInfo = new TrackInfo( this.trackInfo = new TrackInfo(
representations[0].format.mimeType, representations[0].periodDuration * 1000); representations[0].format.mimeType, representations[0].periodDuration * 1000);
...@@ -88,6 +80,10 @@ public class DashWebmChunkSource implements ChunkSource { ...@@ -88,6 +80,10 @@ public class DashWebmChunkSource implements ChunkSource {
maxHeight = Math.max(formats[i].height, maxHeight); maxHeight = Math.max(formats[i].height, maxHeight);
extractors.put(formats[i].id, new DefaultWebmExtractor()); extractors.put(formats[i].id, new DefaultWebmExtractor());
this.representations.put(formats[i].id, representations[i]); 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.maxWidth = maxWidth;
this.maxHeight = maxHeight; this.maxHeight = maxHeight;
...@@ -143,28 +139,34 @@ public class DashWebmChunkSource implements ChunkSource { ...@@ -143,28 +139,34 @@ public class DashWebmChunkSource implements ChunkSource {
Representation selectedRepresentation = representations.get(selectedFormat.id); Representation selectedRepresentation = representations.get(selectedFormat.id);
WebmExtractor extractor = extractors.get(selectedRepresentation.format.id); WebmExtractor extractor = extractors.get(selectedRepresentation.format.id);
if (!extractor.isPrepared()) { if (!extractor.isPrepared()) {
Chunk initializationChunk = newInitializationChunk(selectedRepresentation, extractor, // TODO: This code forces cues to exist and to immediately follow the initialization
dataSource, evaluation.trigger); // 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; lastChunkWasInitialization = true;
out.chunk = initializationChunk; out.chunk = initializationChunk;
return; return;
} }
int nextIndex; int nextSegmentNum;
DashSegmentIndex segmentIndex = segmentIndexes.get(selectedRepresentation.format.id);
if (queue.isEmpty()) { if (queue.isEmpty()) {
nextIndex = Util.binarySearchFloor(extractor.getCues().timesUs, seekPositionUs, true, true); nextSegmentNum = segmentIndex.getSegmentNum(seekPositionUs);
} else { } else {
nextIndex = queue.get(out.queueSize - 1).nextChunkIndex; nextSegmentNum = queue.get(out.queueSize - 1).nextChunkIndex;
} }
if (nextIndex == -1) { if (nextSegmentNum == -1) {
out.chunk = null; out.chunk = null;
return; return;
} }
Chunk nextMediaChunk = newMediaChunk(selectedRepresentation, extractor, dataSource, Chunk nextMediaChunk = newMediaChunk(selectedRepresentation, segmentIndex, extractor,
extractor.getCues(), nextIndex, evaluation.trigger, numSegmentsPerChunk); dataSource, nextSegmentNum, evaluation.trigger);
lastChunkWasInitialization = false; lastChunkWasInitialization = false;
out.chunk = nextMediaChunk; out.chunk = nextMediaChunk;
} }
...@@ -179,53 +181,38 @@ public class DashWebmChunkSource implements ChunkSource { ...@@ -179,53 +181,38 @@ public class DashWebmChunkSource implements ChunkSource {
// Do nothing. // Do nothing.
} }
private static Chunk newInitializationChunk(Representation representation, private Chunk newInitializationChunk(RangedUri initializationUri, Representation representation,
WebmExtractor extractor, DataSource dataSource, int trigger) { WebmExtractor extractor, DataSource dataSource, int trigger) {
DataSpec dataSpec = new DataSpec(representation.uri, 0, representation.indexEnd + 1, DataSpec dataSpec = new DataSpec(initializationUri.getUri(), initializationUri.start,
representation.getCacheKey()); initializationUri.length, representation.getCacheKey());
return new InitializationWebmLoadable(dataSource, dataSpec, trigger, extractor, representation); return new InitializationWebmLoadable(dataSource, dataSpec, trigger, representation.format,
extractor);
} }
private static Chunk newMediaChunk(Representation representation, private Chunk newMediaChunk(Representation representation, DashSegmentIndex segmentIndex,
WebmExtractor extractor, DataSource dataSource, SegmentIndex cues, int index, WebmExtractor extractor, DataSource dataSource, int segmentNum, int trigger) {
int trigger, int numSegmentsPerChunk) { int lastSegmentNum = segmentIndex.getLastSegmentNum();
int nextSegmentNum = segmentNum == lastSegmentNum ? -1 : segmentNum + 1;
// Computes the segments to included in the next fetch. long startTimeUs = segmentIndex.getTimeUs(segmentNum);
int numSegmentsToFetch = Math.min(numSegmentsPerChunk, cues.length - index); long endTimeUs = segmentNum < lastSegmentNum ? segmentIndex.getTimeUs(segmentNum + 1)
int lastSegmentInChunk = index + numSegmentsToFetch - 1; : startTimeUs + segmentIndex.getDurationUs(segmentNum);
int nextIndex = lastSegmentInChunk == cues.length - 1 ? -1 : lastSegmentInChunk + 1; RangedUri segmentUri = segmentIndex.getSegmentUrl(segmentNum);
DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
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,
representation.getCacheKey()); representation.getCacheKey());
return new WebmMediaChunk(dataSource, dataSpec, representation.format, trigger, extractor, 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 WebmExtractor extractor;
private final Uri uri;
public InitializationWebmLoadable(DataSource dataSource, DataSpec dataSpec, int trigger, public InitializationWebmLoadable(DataSource dataSource, DataSpec dataSpec, int trigger,
WebmExtractor extractor, Representation representation) { Format format, WebmExtractor extractor) {
super(dataSource, dataSpec, representation.format, trigger); super(dataSource, dataSpec, format, trigger);
this.extractor = extractor; this.extractor = extractor;
this.representation = representation; this.uri = dataSpec.uri;
} }
@Override @Override
...@@ -234,22 +221,7 @@ public class DashWebmChunkSource implements ChunkSource { ...@@ -234,22 +221,7 @@ public class DashWebmChunkSource implements ChunkSource {
if (!extractor.isPrepared()) { if (!extractor.isPrepared()) {
throw new ParserException("Invalid initialization data"); throw new ParserException("Invalid initialization data");
} }
validateCues(extractor.getCues()); segmentIndexes.put(format.id, new DashWrappingSegmentIndex(extractor.getCues(), uri, 0));
}
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);
}
} }
} }
......
...@@ -42,6 +42,8 @@ public final class RangedUri { ...@@ -42,6 +42,8 @@ public final class RangedUri {
private final Uri baseUri; private final Uri baseUri;
private final String stringUri; private final String stringUri;
private int hashCode;
/** /**
* Constructs an ranged uri. * Constructs an ranged uri.
* <p> * <p>
...@@ -82,4 +84,55 @@ public final class RangedUri { ...@@ -82,4 +84,55 @@ public final class RangedUri {
return uri; 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 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer.dash.mpd; package com.google.android.exoplayer.dash.mpd;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.dash.DashSegmentIndex;
import android.net.Uri; import android.net.Uri;
...@@ -80,6 +81,37 @@ public class Representation { ...@@ -80,6 +81,37 @@ public class Representation {
} }
/** /**
* 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 * Generates a cache key for the {@link Representation}, in the format
* {@code contentId + "." + format.id + "." + revisionId}. * {@code contentId + "." + format.id + "." + revisionId}.
* *
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment