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:
Oliver Woodman 2014-07-17 11:28:05 +01:00
parent d7d14037b8
commit 87461821fe
6 changed files with 216 additions and 158 deletions

View File

@ -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[] {

View File

@ -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.
*/

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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());
}
}

View File

@ -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}.