Add support for SegmentTemplate and SegmentList mpds.

Misc Notes:
- Removed content type filters because some of third parties
  don't set content type.
This commit is contained in:
Oliver Woodman 2014-07-18 14:30:30 +01:00
parent 87461821fe
commit 62d17cabf0
14 changed files with 485 additions and 387 deletions

View File

@ -52,7 +52,7 @@ public class SampleChooserActivity extends Activity {
sampleAdapter.addAll((Object[]) Samples.SIMPLE); sampleAdapter.addAll((Object[]) Samples.SIMPLE);
sampleAdapter.add(new Header("YouTube DASH")); sampleAdapter.add(new Header("YouTube DASH"));
sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_MP4); sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_MP4);
sampleAdapter.add(new Header("Widevine DASH GTS")); sampleAdapter.add(new Header("Widevine GTS DASH"));
sampleAdapter.addAll((Object[]) Samples.WIDEVINE_GTS); sampleAdapter.addAll((Object[]) Samples.WIDEVINE_GTS);
sampleAdapter.add(new Header("SmoothStreaming")); sampleAdapter.add(new Header("SmoothStreaming"));
sampleAdapter.addAll((Object[]) Samples.SMOOTHSTREAMING); sampleAdapter.addAll((Object[]) Samples.SMOOTHSTREAMING);

View File

@ -129,10 +129,6 @@ 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[] {

View File

@ -160,8 +160,7 @@ public class DashVodRendererBuilder implements RendererBuilder,
} }
// Build the video renderer. // Build the video renderer.
DataSource videoDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES, DataSource videoDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
bandwidthMeter);
ChunkSource videoChunkSource; ChunkSource videoChunkSource;
String mimeType = videoRepresentations[0].format.mimeType; String mimeType = videoRepresentations[0].format.mimeType;
if (mimeType.equals(MimeTypes.VIDEO_MP4)) { if (mimeType.equals(MimeTypes.VIDEO_MP4)) {
@ -192,8 +191,7 @@ public class DashVodRendererBuilder implements RendererBuilder,
audioChunkSource = null; audioChunkSource = null;
audioRenderer = null; audioRenderer = null;
} else { } else {
DataSource audioDataSource = new HttpDataSource(userAgent, DataSource audioDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
HttpDataSource.REJECT_PAYWALL_TYPES, bandwidthMeter);
audioTrackNames = new String[audioRepresentationsList.size()]; audioTrackNames = new String[audioRepresentationsList.size()];
ChunkSource[] audioChunkSources = new ChunkSource[audioRepresentationsList.size()]; ChunkSource[] audioChunkSources = new ChunkSource[audioRepresentationsList.size()];
FormatEvaluator audioEvaluator = new FormatEvaluator.FixedEvaluator(); FormatEvaluator audioEvaluator = new FormatEvaluator.FixedEvaluator();

View File

@ -150,8 +150,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
} }
// Build the video renderer. // Build the video renderer.
DataSource videoDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES, DataSource videoDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
bandwidthMeter);
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(url, manifest, ChunkSource videoChunkSource = new SmoothStreamingChunkSource(url, manifest,
videoStreamElementIndex, videoTrackIndices, videoDataSource, videoStreamElementIndex, videoTrackIndices, videoDataSource,
new AdaptiveEvaluator(bandwidthMeter)); new AdaptiveEvaluator(bandwidthMeter));
@ -173,8 +172,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
} else { } else {
audioTrackNames = new String[audioStreamElementCount]; audioTrackNames = new String[audioStreamElementCount];
ChunkSource[] audioChunkSources = new ChunkSource[audioStreamElementCount]; ChunkSource[] audioChunkSources = new ChunkSource[audioStreamElementCount];
DataSource audioDataSource = new HttpDataSource(userAgent, DataSource audioDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
HttpDataSource.REJECT_PAYWALL_TYPES, bandwidthMeter);
FormatEvaluator audioFormatEvaluator = new FormatEvaluator.FixedEvaluator(); FormatEvaluator audioFormatEvaluator = new FormatEvaluator.FixedEvaluator();
audioStreamElementCount = 0; audioStreamElementCount = 0;
for (int i = 0; i < manifest.streamElements.length; i++) { for (int i = 0; i < manifest.streamElements.length; i++) {
@ -204,8 +202,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
} else { } else {
textTrackNames = new String[textStreamElementCount]; textTrackNames = new String[textStreamElementCount];
ChunkSource[] textChunkSources = new ChunkSource[textStreamElementCount]; ChunkSource[] textChunkSources = new ChunkSource[textStreamElementCount];
DataSource ttmlDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES, DataSource ttmlDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
bandwidthMeter);
FormatEvaluator ttmlFormatEvaluator = new FormatEvaluator.FixedEvaluator(); FormatEvaluator ttmlFormatEvaluator = new FormatEvaluator.FixedEvaluator();
textStreamElementCount = 0; textStreamElementCount = 0;
for (int i = 0; i < manifest.streamElements.length; i++) { for (int i = 0; i < manifest.streamElements.length; i++) {

View File

@ -115,8 +115,7 @@ import java.util.ArrayList;
videoRepresentationsList.toArray(videoRepresentations); videoRepresentationsList.toArray(videoRepresentations);
// Build the video renderer. // Build the video renderer.
DataSource videoDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES, DataSource videoDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
bandwidthMeter);
ChunkSource videoChunkSource = new DashMp4ChunkSource(videoDataSource, ChunkSource videoChunkSource = new DashMp4ChunkSource(videoDataSource,
new AdaptiveEvaluator(bandwidthMeter), videoRepresentations); new AdaptiveEvaluator(bandwidthMeter), videoRepresentations);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
@ -125,8 +124,7 @@ import java.util.ArrayList;
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50);
// Build the audio renderer. // Build the audio renderer.
DataSource audioDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES, DataSource audioDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
bandwidthMeter);
ChunkSource audioChunkSource = new DashMp4ChunkSource(audioDataSource, ChunkSource audioChunkSource = new DashMp4ChunkSource(audioDataSource,
new FormatEvaluator.FixedEvaluator(), audioRepresentation); new FormatEvaluator.FixedEvaluator(), audioRepresentation);
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,

View File

@ -115,8 +115,7 @@ import java.util.ArrayList;
} }
// Build the video renderer. // Build the video renderer.
DataSource videoDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES, DataSource videoDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
bandwidthMeter);
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(url, manifest, ChunkSource videoChunkSource = new SmoothStreamingChunkSource(url, manifest,
videoStreamElementIndex, videoTrackIndices, videoDataSource, videoStreamElementIndex, videoTrackIndices, videoDataSource,
new AdaptiveEvaluator(bandwidthMeter)); new AdaptiveEvaluator(bandwidthMeter));
@ -126,8 +125,7 @@ import java.util.ArrayList;
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50);
// Build the audio renderer. // Build the audio renderer.
DataSource audioDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES, DataSource audioDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
bandwidthMeter);
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(url, manifest, ChunkSource audioChunkSource = new SmoothStreamingChunkSource(url, manifest,
audioStreamElementIndex, new int[] {0}, audioDataSource, audioStreamElementIndex, new int[] {0}, audioDataSource,
new FormatEvaluator.FixedEvaluator()); new FormatEvaluator.FixedEvaluator());

View File

@ -309,7 +309,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
} }
MediaFormat mediaFormat = mediaChunk.getMediaFormat(); MediaFormat mediaFormat = mediaChunk.getMediaFormat();
if (downstreamMediaFormat == null || !downstreamMediaFormat.equals(mediaFormat)) { if (mediaFormat != null && !mediaFormat.equals(downstreamMediaFormat)) {
chunkSource.getMaxVideoDimensions(mediaFormat); chunkSource.getMaxVideoDimensions(mediaFormat);
formatHolder.format = mediaFormat; formatHolder.format = mediaFormat;
formatHolder.drmInitData = mediaChunk.getPsshInfo(); formatHolder.drmInitData = mediaChunk.getPsshInfo();

View File

@ -74,7 +74,7 @@ public class DashMp4ChunkSource implements ChunkSource {
this.segmentIndexes = new HashMap<String, DashSegmentIndex>(); 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].periodDurationMs * 1000);
this.evaluation = new Evaluation(); this.evaluation = new Evaluation();
int maxWidth = 0; int maxWidth = 0;
int maxHeight = 0; int maxHeight = 0;
@ -198,7 +198,7 @@ public class DashMp4ChunkSource implements ChunkSource {
RangedUri requestUri; RangedUri requestUri;
if (initializationUri != null) { if (initializationUri != null) {
// It's common for initialization and index data to be stored adjacently. Attempt to merge // It's common for initialization and index data to be stored adjacently. Attempt to merge
// the two requests together to request at once. // the two requests together to request both at once.
expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_MOOV; expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_MOOV;
requestUri = initializationUri.attemptMerge(indexUri); requestUri = initializationUri.attemptMerge(indexUri);
if (requestUri != null) { if (requestUri != null) {

View File

@ -70,7 +70,7 @@ public class DashWebmChunkSource implements ChunkSource {
this.segmentIndexes = new HashMap<String, DashSegmentIndex>(); 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].periodDurationMs * 1000);
this.evaluation = new Evaluation(); this.evaluation = new Evaluation();
int maxWidth = 0; int maxWidth = 0;
int maxHeight = 0; int maxHeight = 0;

View File

@ -17,7 +17,10 @@ package com.google.android.exoplayer.dash.mpd;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentList;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
@ -39,11 +42,6 @@ import java.util.regex.Pattern;
/** /**
* A parser of media presentation description files. * A parser of media presentation description files.
*/ */
/*
* TODO: Parse representation base attributes at multiple levels, and normalize the resulting
* datastructure.
* TODO: Decide how best to represent missing integer/double/long attributes.
*/
public class MediaPresentationDescriptionParser extends DefaultHandler { public class MediaPresentationDescriptionParser extends DefaultHandler {
// Note: Does not support the date part of ISO 8601 // Note: Does not support the date part of ISO 8601
@ -60,6 +58,8 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
} }
} }
// MPD parsing.
/** /**
* Parses a manifest from the provided {@link InputStream}. * Parses a manifest from the provided {@link InputStream}.
* *
@ -86,96 +86,69 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
} }
private MediaPresentationDescription parseMediaPresentationDescription(XmlPullParser xpp, private MediaPresentationDescription parseMediaPresentationDescription(XmlPullParser xpp,
String contentId, Uri parentBaseUrl) throws XmlPullParserException, IOException { String contentId, Uri baseUrl) throws XmlPullParserException, IOException {
Uri baseUrl = parentBaseUrl; long durationMs = parseDurationMs(xpp, "mediaPresentationDuration");
long duration = parseDurationMs(xpp, "mediaPresentationDuration"); long minBufferTimeMs = parseDurationMs(xpp, "minBufferTime");
long minBufferTime = parseDurationMs(xpp, "minBufferTime");
String typeString = xpp.getAttributeValue(null, "type"); String typeString = xpp.getAttributeValue(null, "type");
boolean dynamic = (typeString != null) ? typeString.equals("dynamic") : false; boolean dynamic = (typeString != null) ? typeString.equals("dynamic") : false;
long minUpdateTime = (dynamic) ? parseDurationMs(xpp, "minimumUpdatePeriod", -1) : -1; long minUpdateTimeMs = (dynamic) ? parseDurationMs(xpp, "minimumUpdatePeriod", -1) : -1;
List<Period> periods = new ArrayList<Period>(); List<Period> periods = new ArrayList<Period>();
do { do {
xpp.next(); xpp.next();
if (isStartTag(xpp, "BaseURL")) { if (isStartTag(xpp, "BaseURL")) {
baseUrl = parseBaseUrl(xpp, parentBaseUrl); baseUrl = parseBaseUrl(xpp, baseUrl);
} else if (isStartTag(xpp, "Period")) { } else if (isStartTag(xpp, "Period")) {
periods.add(parsePeriod(xpp, contentId, baseUrl, duration)); periods.add(parsePeriod(xpp, contentId, baseUrl, durationMs));
} }
} while (!isEndTag(xpp, "MPD")); } while (!isEndTag(xpp, "MPD"));
return new MediaPresentationDescription(duration, minBufferTime, dynamic, minUpdateTime, return new MediaPresentationDescription(durationMs, minBufferTimeMs, dynamic, minUpdateTimeMs,
periods); periods);
} }
private Period parsePeriod(XmlPullParser xpp, String contentId, Uri parentBaseUrl, private Period parsePeriod(XmlPullParser xpp, String contentId, Uri baseUrl, long mpdDurationMs)
long mediaPresentationDuration) throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
Uri baseUrl = parentBaseUrl;
String id = xpp.getAttributeValue(null, "id"); String id = xpp.getAttributeValue(null, "id");
long start = parseDurationMs(xpp, "start", 0); long startMs = parseDurationMs(xpp, "start", 0);
long duration = parseDurationMs(xpp, "duration", mediaPresentationDuration); long durationMs = parseDurationMs(xpp, "duration", mpdDurationMs);
SegmentBase segmentBase = null;
List<AdaptationSet> adaptationSets = new ArrayList<AdaptationSet>(); List<AdaptationSet> adaptationSets = new ArrayList<AdaptationSet>();
List<Segment.Timeline> segmentTimelineList = null;
int segmentStartNumber = 0;
int segmentTimescale = 0;
long presentationTimeOffset = 0;
do { do {
xpp.next(); xpp.next();
if (isStartTag(xpp, "BaseURL")) { if (isStartTag(xpp, "BaseURL")) {
baseUrl = parseBaseUrl(xpp, parentBaseUrl); baseUrl = parseBaseUrl(xpp, baseUrl);
} else if (isStartTag(xpp, "AdaptationSet")) { } else if (isStartTag(xpp, "AdaptationSet")) {
adaptationSets.add(parseAdaptationSet(xpp, contentId, baseUrl, start, duration, adaptationSets.add(parseAdaptationSet(xpp, contentId, baseUrl, startMs, durationMs,
segmentTimelineList)); segmentBase));
} else if (isStartTag(xpp, "SegmentBase")) {
segmentBase = parseSegmentBase(xpp, baseUrl, null);
} else if (isStartTag(xpp, "SegmentList")) { } else if (isStartTag(xpp, "SegmentList")) {
segmentStartNumber = parseInt(xpp, "startNumber"); segmentBase = parseSegmentList(xpp, baseUrl, null, durationMs);
segmentTimescale = parseInt(xpp, "timescale"); } else if (isStartTag(xpp, "SegmentTemplate")) {
presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", 0); segmentBase = parseSegmentTemplate(xpp, baseUrl, null, durationMs);
segmentTimelineList = parsePeriodSegmentList(xpp, segmentStartNumber);
} }
} while (!isEndTag(xpp, "Period")); } while (!isEndTag(xpp, "Period"));
return new Period(id, start, duration, adaptationSets, segmentTimelineList, return new Period(id, startMs, durationMs, adaptationSets);
segmentStartNumber, segmentTimescale, presentationTimeOffset);
} }
private List<Segment.Timeline> parsePeriodSegmentList( // AdaptationSet parsing.
XmlPullParser xpp, long segmentStartNumber) throws XmlPullParserException, IOException {
List<Segment.Timeline> segmentTimelineList = new ArrayList<Segment.Timeline>();
do { private AdaptationSet parseAdaptationSet(XmlPullParser xpp, String contentId, Uri baseUrl,
xpp.next(); long periodStartMs, long periodDurationMs, SegmentBase segmentBase)
if (isStartTag(xpp, "SegmentTimeline")) {
do {
xpp.next();
if (isStartTag(xpp, "S")) {
long duration = parseLong(xpp, "d");
segmentTimelineList.add(new Segment.Timeline(segmentStartNumber, duration));
segmentStartNumber++;
}
} while (!isEndTag(xpp, "SegmentTimeline"));
}
} while (!isEndTag(xpp, "SegmentList"));
return segmentTimelineList;
}
private AdaptationSet parseAdaptationSet(XmlPullParser xpp, String contentId, Uri parentBaseUrl,
long periodStart, long periodDuration, List<Segment.Timeline> segmentTimelineList)
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
Uri baseUrl = parentBaseUrl;
int id = -1;
// TODO: Correctly handle other common attributes and elements. See 23009-1 Table 9.
String mimeType = xpp.getAttributeValue(null, "mimeType"); String mimeType = xpp.getAttributeValue(null, "mimeType");
int contentType = parseAdaptationSetTypeFromMimeType(mimeType); int contentType = parseAdaptationSetTypeFromMimeType(mimeType);
int id = -1;
List<ContentProtection> contentProtections = null; List<ContentProtection> contentProtections = null;
List<Representation> representations = new ArrayList<Representation>(); List<Representation> representations = new ArrayList<Representation>();
do { do {
xpp.next(); xpp.next();
if (isStartTag(xpp, "BaseURL")) { if (isStartTag(xpp, "BaseURL")) {
baseUrl = parseBaseUrl(xpp, parentBaseUrl); baseUrl = parseBaseUrl(xpp, baseUrl);
} else if (isStartTag(xpp, "ContentProtection")) { } else if (isStartTag(xpp, "ContentProtection")) {
if (contentProtections == null) { if (contentProtections == null) {
contentProtections = new ArrayList<ContentProtection>(); contentProtections = new ArrayList<ContentProtection>();
@ -186,17 +159,62 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
contentType = checkAdaptationSetTypeConsistency(contentType, contentType = checkAdaptationSetTypeConsistency(contentType,
parseAdaptationSetType(xpp.getAttributeValue(null, "contentType"))); parseAdaptationSetType(xpp.getAttributeValue(null, "contentType")));
} else if (isStartTag(xpp, "Representation")) { } else if (isStartTag(xpp, "Representation")) {
Representation representation = parseRepresentation(xpp, contentId, baseUrl, periodStart, Representation representation = parseRepresentation(xpp, contentId, baseUrl, periodStartMs,
periodDuration, mimeType, segmentTimelineList); periodDurationMs, mimeType, segmentBase);
contentType = checkAdaptationSetTypeConsistency(contentType, contentType = checkAdaptationSetTypeConsistency(contentType,
parseAdaptationSetTypeFromMimeType(representation.format.mimeType)); parseAdaptationSetTypeFromMimeType(representation.format.mimeType));
representations.add(representation); representations.add(representation);
} else if (isStartTag(xpp, "SegmentBase")) {
segmentBase = parseSegmentBase(xpp, baseUrl, (SingleSegmentBase) segmentBase);
} else if (isStartTag(xpp, "SegmentList")) {
segmentBase = parseSegmentList(xpp, baseUrl, (SegmentList) segmentBase, periodDurationMs);
} else if (isStartTag(xpp, "SegmentTemplate")) {
segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase,
periodDurationMs);
} }
} while (!isEndTag(xpp, "AdaptationSet")); } while (!isEndTag(xpp, "AdaptationSet"));
return new AdaptationSet(id, contentType, representations, contentProtections); return new AdaptationSet(id, contentType, representations, contentProtections);
} }
private int parseAdaptationSetType(String contentType) {
return TextUtils.isEmpty(contentType) ? AdaptationSet.TYPE_UNKNOWN
: MimeTypes.BASE_TYPE_AUDIO.equals(contentType) ? AdaptationSet.TYPE_AUDIO
: MimeTypes.BASE_TYPE_VIDEO.equals(contentType) ? AdaptationSet.TYPE_VIDEO
: MimeTypes.BASE_TYPE_TEXT.equals(contentType) ? AdaptationSet.TYPE_TEXT
: AdaptationSet.TYPE_UNKNOWN;
}
private int parseAdaptationSetTypeFromMimeType(String mimeType) {
return TextUtils.isEmpty(mimeType) ? AdaptationSet.TYPE_UNKNOWN
: MimeTypes.isAudio(mimeType) ? AdaptationSet.TYPE_AUDIO
: MimeTypes.isVideo(mimeType) ? AdaptationSet.TYPE_VIDEO
: MimeTypes.isText(mimeType) || MimeTypes.isTtml(mimeType) ? AdaptationSet.TYPE_TEXT
: AdaptationSet.TYPE_UNKNOWN;
}
/**
* Checks two adaptation set types for consistency, returning the consistent type, or throwing an
* {@link IllegalStateException} if the types are inconsistent.
* <p>
* Two types are consistent if they are equal, or if one is {@link AdaptationSet#TYPE_UNKNOWN}.
* Where one of the types is {@link AdaptationSet#TYPE_UNKNOWN}, the other is returned.
*
* @param firstType The first type.
* @param secondType The second type.
* @return The consistent type.
*/
private int checkAdaptationSetTypeConsistency(int firstType, int secondType) {
if (firstType == AdaptationSet.TYPE_UNKNOWN) {
return secondType;
} else if (secondType == AdaptationSet.TYPE_UNKNOWN) {
return firstType;
} else {
Assertions.checkState(firstType == secondType);
return firstType;
}
}
/** /**
* Parses a ContentProtection element. * Parses a ContentProtection element.
* *
@ -209,90 +227,194 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
return new ContentProtection(schemeUriId, null); return new ContentProtection(schemeUriId, null);
} }
private Representation parseRepresentation(XmlPullParser xpp, String contentId, Uri parentBaseUrl, // Representation parsing.
long periodStart, long periodDuration, String parentMimeType,
List<Segment.Timeline> segmentTimelineList) throws XmlPullParserException, IOException { private Representation parseRepresentation(XmlPullParser xpp, String contentId, Uri baseUrl,
Uri baseUrl = parentBaseUrl; long periodStartMs, long periodDurationMs, String mimeType, SegmentBase segmentBase)
throws XmlPullParserException, IOException {
String id = xpp.getAttributeValue(null, "id"); String id = xpp.getAttributeValue(null, "id");
int bandwidth = parseInt(xpp, "bandwidth"); int bandwidth = parseInt(xpp, "bandwidth");
int audioSamplingRate = parseInt(xpp, "audioSamplingRate"); int audioSamplingRate = parseInt(xpp, "audioSamplingRate");
int width = parseInt(xpp, "width"); int width = parseInt(xpp, "width");
int height = parseInt(xpp, "height"); int height = parseInt(xpp, "height");
mimeType = parseString(xpp, "mimeType", mimeType);
String mimeType = xpp.getAttributeValue(null, "mimeType");
if (mimeType == null) {
mimeType = parentMimeType;
}
long indexStart = -1;
long indexEnd = -1;
long initializationStart = -1;
long initializationEnd = -1;
int numChannels = -1; int numChannels = -1;
List<Segment> segmentList = null;
do { do {
xpp.next(); xpp.next();
if (isStartTag(xpp, "BaseURL")) { if (isStartTag(xpp, "BaseURL")) {
baseUrl = parseBaseUrl(xpp, parentBaseUrl); baseUrl = parseBaseUrl(xpp, baseUrl);
} else if (isStartTag(xpp, "AudioChannelConfiguration")) { } else if (isStartTag(xpp, "AudioChannelConfiguration")) {
numChannels = Integer.parseInt(xpp.getAttributeValue(null, "value")); numChannels = Integer.parseInt(xpp.getAttributeValue(null, "value"));
} else if (isStartTag(xpp, "SegmentBase")) { } else if (isStartTag(xpp, "SegmentBase")) {
String[] indexRange = xpp.getAttributeValue(null, "indexRange").split("-"); segmentBase = parseSegmentBase(xpp, baseUrl, (SingleSegmentBase) segmentBase);
indexStart = Long.parseLong(indexRange[0]);
indexEnd = Long.parseLong(indexRange[1]);
} else if (isStartTag(xpp, "SegmentList")) { } else if (isStartTag(xpp, "SegmentList")) {
segmentList = parseRepresentationSegmentList(xpp, segmentTimelineList); segmentBase = parseSegmentList(xpp, baseUrl, (SegmentList) segmentBase, periodDurationMs);
} else if (isStartTag(xpp, "Initialization")) { } else if (isStartTag(xpp, "SegmentTemplate")) {
String[] indexRange = xpp.getAttributeValue(null, "range").split("-"); segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase,
initializationStart = Long.parseLong(indexRange[0]); periodDurationMs);
initializationEnd = Long.parseLong(indexRange[1]);
} }
} while (!isEndTag(xpp, "Representation")); } while (!isEndTag(xpp, "Representation"));
Format format = new Format(id, mimeType, width, height, numChannels, audioSamplingRate, Format format = new Format(id, mimeType, width, height, numChannels, audioSamplingRate,
bandwidth); bandwidth);
if (segmentList == null) { return Representation.newInstance(periodStartMs, periodDurationMs, contentId, -1, format,
return new Representation(contentId, -1, format, baseUrl, DataSpec.LENGTH_UNBOUNDED, segmentBase);
initializationStart, initializationEnd, indexStart, indexEnd, periodStart,
periodDuration);
} else {
return new SegmentedRepresentation(contentId, format, baseUrl, initializationStart,
initializationEnd, indexStart, indexEnd, periodStart, periodDuration, segmentList);
}
} }
private List<Segment> parseRepresentationSegmentList(XmlPullParser xpp, // SegmentBase, SegmentList and SegmentTemplate parsing.
List<Segment.Timeline> segmentTimelineList) throws XmlPullParserException, IOException {
List<Segment> segmentList = new ArrayList<Segment>(); private SingleSegmentBase parseSegmentBase(XmlPullParser xpp, Uri baseUrl,
int i = 0; SingleSegmentBase parent) throws XmlPullParserException, IOException {
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset",
parent != null ? parent.presentationTimeOffset : 0);
long indexStart = parent != null ? parent.indexStart : 0;
long indexLength = parent != null ? parent.indexLength : -1;
String indexRangeText = xpp.getAttributeValue(null, "indexRange");
if (indexRangeText != null) {
String[] indexRange = indexRangeText.split("-");
indexStart = Long.parseLong(indexRange[0]);
indexLength = Long.parseLong(indexRange[1]) - indexStart + 1;
}
RangedUri initialization = parent != null ? parent.initialization : null;
do {
xpp.next();
if (isStartTag(xpp, "Initialization")) {
initialization = parseInitialization(xpp, baseUrl);
}
} while (!isEndTag(xpp, "SegmentBase"));
return new SingleSegmentBase(initialization, timescale, presentationTimeOffset, baseUrl,
indexStart, indexLength);
}
private SegmentList parseSegmentList(XmlPullParser xpp, Uri baseUrl, SegmentList parent,
long periodDuration) throws XmlPullParserException, IOException {
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset",
parent != null ? parent.presentationTimeOffset : 0);
long duration = parseLong(xpp, "duration", parent != null ? parent.duration : -1);
int startNumber = parseInt(xpp, "startNumber", parent != null ? parent.startNumber : 0);
RangedUri initialization = null;
List<SegmentTimelineElement> timeline = null;
List<RangedUri> segments = null;
do { do {
xpp.next(); xpp.next();
if (isStartTag(xpp, "Initialization")) { if (isStartTag(xpp, "Initialization")) {
String url = xpp.getAttributeValue(null, "sourceURL"); initialization = parseInitialization(xpp, baseUrl);
String[] indexRange = xpp.getAttributeValue(null, "range").split("-"); } else if (isStartTag(xpp, "SegmentTimeline")) {
long initializationStart = Long.parseLong(indexRange[0]); timeline = parseSegmentTimeline(xpp);
long initializationEnd = Long.parseLong(indexRange[1]);
segmentList.add(new Segment.Initialization(url, initializationStart, initializationEnd));
} else if (isStartTag(xpp, "SegmentURL")) { } else if (isStartTag(xpp, "SegmentURL")) {
String url = xpp.getAttributeValue(null, "media"); if (segments == null) {
String mediaRange = xpp.getAttributeValue(null, "mediaRange"); segments = new ArrayList<RangedUri>();
long sequenceNumber = segmentTimelineList.get(i).sequenceNumber;
long duration = segmentTimelineList.get(i).duration;
i++;
if (mediaRange != null) {
String[] mediaRangeArray = xpp.getAttributeValue(null, "mediaRange").split("-");
long mediaStart = Long.parseLong(mediaRangeArray[0]);
segmentList.add(new Segment.Media(url, mediaStart, sequenceNumber, duration));
} else {
segmentList.add(new Segment.Media(url, sequenceNumber, duration));
} }
segments.add(parseSegmentUrl(xpp, baseUrl));
} }
} while (!isEndTag(xpp, "SegmentList")); } while (!isEndTag(xpp, "SegmentList"));
return segmentList; if (parent != null) {
initialization = initialization != null ? initialization : parent.initialization;
timeline = timeline != null ? timeline : parent.segmentTimeline;
segments = segments != null ? segments : parent.mediaSegments;
}
return new SegmentList(initialization, timescale, presentationTimeOffset, periodDuration,
startNumber, duration, timeline, segments);
} }
private SegmentTemplate parseSegmentTemplate(XmlPullParser xpp, Uri baseUrl,
SegmentTemplate parent, long periodDuration) throws XmlPullParserException, IOException {
long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1);
long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset",
parent != null ? parent.presentationTimeOffset : 0);
long duration = parseLong(xpp, "duration", parent != null ? parent.duration : -1);
int startNumber = parseInt(xpp, "startNumber", parent != null ? parent.startNumber : 0);
UrlTemplate mediaTemplate = parseUrlTemplate(xpp, "media",
parent != null ? parent.mediaTemplate : null);
UrlTemplate initializationTemplate = parseUrlTemplate(xpp, "initialization",
parent != null ? parent.initializationTemplate : null);
RangedUri initialization = null;
List<SegmentTimelineElement> timeline = null;
do {
xpp.next();
if (isStartTag(xpp, "Initialization")) {
initialization = parseInitialization(xpp, baseUrl);
} else if (isStartTag(xpp, "SegmentTimeline")) {
timeline = parseSegmentTimeline(xpp);
}
} while (!isEndTag(xpp, "SegmentTemplate"));
if (parent != null) {
initialization = initialization != null ? initialization : parent.initialization;
timeline = timeline != null ? timeline : parent.segmentTimeline;
}
return new SegmentTemplate(initialization, timescale, presentationTimeOffset, periodDuration,
startNumber, duration, timeline, initializationTemplate, mediaTemplate, baseUrl);
}
private List<SegmentTimelineElement> parseSegmentTimeline(XmlPullParser xpp)
throws XmlPullParserException, IOException {
List<SegmentTimelineElement> segmentTimeline = new ArrayList<SegmentTimelineElement>();
long elapsedTime = 0;
do {
xpp.next();
if (isStartTag(xpp, "S")) {
elapsedTime = parseLong(xpp, "t", elapsedTime);
long duration = parseLong(xpp, "d");
int count = 1 + parseInt(xpp, "r", 0);
for (int i = 0; i < count; i++) {
segmentTimeline.add(new SegmentTimelineElement(elapsedTime, duration));
elapsedTime += duration;
}
}
} while (!isEndTag(xpp, "SegmentTimeline"));
return segmentTimeline;
}
private UrlTemplate parseUrlTemplate(XmlPullParser xpp, String name,
UrlTemplate defaultValue) {
String valueString = xpp.getAttributeValue(null, name);
if (valueString != null) {
return UrlTemplate.compile(valueString);
}
return defaultValue;
}
private RangedUri parseInitialization(XmlPullParser xpp, Uri baseUrl) {
return parseRangedUrl(xpp, baseUrl, "sourceURL", "range");
}
private RangedUri parseSegmentUrl(XmlPullParser xpp, Uri baseUrl) {
return parseRangedUrl(xpp, baseUrl, "media", "mediaRange");
}
private RangedUri parseRangedUrl(XmlPullParser xpp, Uri baseUrl, String urlAttribute,
String rangeAttribute) {
String urlText = xpp.getAttributeValue(null, urlAttribute);
long rangeStart = 0;
long rangeLength = -1;
String rangeText = xpp.getAttributeValue(null, rangeAttribute);
if (rangeText != null) {
String[] rangeTextArray = rangeText.split("-");
rangeStart = Long.parseLong(rangeTextArray[0]);
rangeLength = Long.parseLong(rangeTextArray[1]) - rangeStart + 1;
}
return new RangedUri(baseUrl, urlText, rangeStart, rangeLength);
}
// Utility methods.
protected static boolean isEndTag(XmlPullParser xpp, String name) throws XmlPullParserException { protected static boolean isEndTag(XmlPullParser xpp, String name) throws XmlPullParserException {
return xpp.getEventType() == XmlPullParser.END_TAG && name.equals(xpp.getName()); return xpp.getEventType() == XmlPullParser.END_TAG && name.equals(xpp.getName());
} }
@ -302,21 +424,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
return xpp.getEventType() == XmlPullParser.START_TAG && name.equals(xpp.getName()); return xpp.getEventType() == XmlPullParser.START_TAG && name.equals(xpp.getName());
} }
protected static int parseInt(XmlPullParser xpp, String name) { private static long parseDurationMs(XmlPullParser xpp, String name) {
String value = xpp.getAttributeValue(null, name);
return value == null ? -1 : Integer.parseInt(value);
}
protected static long parseLong(XmlPullParser xpp, String name) {
return parseLong(xpp, name, -1);
}
protected static long parseLong(XmlPullParser xpp, String name, long defaultValue) {
String value = xpp.getAttributeValue(null, name);
return value == null ? defaultValue : Long.parseLong(value);
}
private long parseDurationMs(XmlPullParser xpp, String name) {
return parseDurationMs(xpp, name, -1); return parseDurationMs(xpp, name, -1);
} }
@ -339,54 +447,38 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
return defaultValue; return defaultValue;
} }
private static Uri parseBaseUrl(XmlPullParser xpp, Uri parentBaseUrl) protected static Uri parseBaseUrl(XmlPullParser xpp, Uri parentBaseUrl)
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
xpp.next(); xpp.next();
String newBaseUrlText = xpp.getText(); String newBaseUrlText = xpp.getText();
Uri newBaseUri = Uri.parse(newBaseUrlText); Uri newBaseUri = Uri.parse(newBaseUrlText);
if (newBaseUri.isAbsolute()) { if (!newBaseUri.isAbsolute()) {
return newBaseUri; newBaseUri = Uri.withAppendedPath(parentBaseUrl, newBaseUrlText);
} else {
return parentBaseUrl.buildUpon().appendEncodedPath(newBaseUrlText).build();
} }
return newBaseUri;
} }
private static int parseAdaptationSetType(String contentType) { protected static int parseInt(XmlPullParser xpp, String name) {
return TextUtils.isEmpty(contentType) ? AdaptationSet.TYPE_UNKNOWN return parseInt(xpp, name, -1);
: MimeTypes.BASE_TYPE_AUDIO.equals(contentType) ? AdaptationSet.TYPE_AUDIO
: MimeTypes.BASE_TYPE_VIDEO.equals(contentType) ? AdaptationSet.TYPE_VIDEO
: MimeTypes.BASE_TYPE_TEXT.equals(contentType) ? AdaptationSet.TYPE_TEXT
: AdaptationSet.TYPE_UNKNOWN;
} }
private static int parseAdaptationSetTypeFromMimeType(String mimeType) { protected static int parseInt(XmlPullParser xpp, String name, int defaultValue) {
return TextUtils.isEmpty(mimeType) ? AdaptationSet.TYPE_UNKNOWN String value = xpp.getAttributeValue(null, name);
: MimeTypes.isAudio(mimeType) ? AdaptationSet.TYPE_AUDIO return value == null ? defaultValue : Integer.parseInt(value);
: MimeTypes.isVideo(mimeType) ? AdaptationSet.TYPE_VIDEO
: MimeTypes.isText(mimeType) || MimeTypes.isTtml(mimeType) ? AdaptationSet.TYPE_TEXT
: AdaptationSet.TYPE_UNKNOWN;
} }
/** protected static long parseLong(XmlPullParser xpp, String name) {
* Checks two adaptation set types for consistency, returning the consistent type, or throwing an return parseLong(xpp, name, -1);
* {@link IllegalStateException} if the types are inconsistent. }
* <p>
* Two types are consistent if they are equal, or if one is {@link AdaptationSet#TYPE_UNKNOWN}. protected static long parseLong(XmlPullParser xpp, String name, long defaultValue) {
* Where one of the types is {@link AdaptationSet#TYPE_UNKNOWN}, the other is returned. String value = xpp.getAttributeValue(null, name);
* return value == null ? defaultValue : Long.parseLong(value);
* @param firstType The first type. }
* @param secondType The second type.
* @return The consistent type. protected static String parseString(XmlPullParser xpp, String name, String defaultValue) {
*/ String value = xpp.getAttributeValue(null, name);
private static int checkAdaptationSetTypeConsistency(int firstType, int secondType) { return value == null ? defaultValue : value;
if (firstType == AdaptationSet.TYPE_UNKNOWN) {
return secondType;
} else if (secondType == AdaptationSet.TYPE_UNKNOWN) {
return firstType;
} else {
Assertions.checkState(firstType == secondType);
return firstType;
}
} }
} }

View File

@ -23,46 +23,37 @@ import java.util.List;
*/ */
public final class Period { public final class Period {
/**
* The period identifier, if one exists.
*/
public final String id; public final String id;
public final long start; /**
* The start time of the period in milliseconds.
*/
public final long startMs;
public final long duration; /**
* The duration of the period in milliseconds, or -1 if the duration is unknown.
*/
public final long durationMs;
/**
* The adaptation sets belonging to the period.
*/
public final List<AdaptationSet> adaptationSets; public final List<AdaptationSet> adaptationSets;
public final List<Segment.Timeline> segmentList; /**
* @param id The period identifier. May be null.
public final int segmentStartNumber; * @param start The start time of the period in milliseconds.
* @param duration The duration of the period in milliseconds, or -1 if the duration is unknown.
public final int segmentTimescale; * @param adaptationSets The adaptation sets belonging to the period.
*/
public final long presentationTimeOffset;
public Period(String id, long start, long duration, List<AdaptationSet> adaptationSets) { public Period(String id, long start, long duration, List<AdaptationSet> adaptationSets) {
this(id, start, duration, adaptationSets, null, 0, 0, 0);
}
public Period(String id, long start, long duration, List<AdaptationSet> adaptationSets,
List<Segment.Timeline> segmentList, int segmentStartNumber, int segmentTimescale) {
this(id, start, duration, adaptationSets, segmentList, segmentStartNumber, segmentTimescale, 0);
}
public Period(String id, long start, long duration, List<AdaptationSet> adaptationSets,
List<Segment.Timeline> segmentList, int segmentStartNumber, int segmentTimescale,
long presentationTimeOffset) {
this.id = id; this.id = id;
this.start = start; this.startMs = start;
this.duration = duration; this.durationMs = duration;
this.adaptationSets = Collections.unmodifiableList(adaptationSets); this.adaptationSets = Collections.unmodifiableList(adaptationSets);
if (segmentList != null) {
this.segmentList = Collections.unmodifiableList(segmentList);
} else {
this.segmentList = null;
}
this.segmentStartNumber = segmentStartNumber;
this.segmentTimescale = segmentTimescale;
this.presentationTimeOffset = presentationTimeOffset;
} }
} }

View File

@ -17,13 +17,15 @@ 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 com.google.android.exoplayer.dash.DashSegmentIndex;
import com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
import android.net.Uri; import android.net.Uri;
/** /**
* A flat version of a DASH representation. * A DASH representation.
*/ */
public class Representation { public abstract class Representation {
/** /**
* Identifies the piece of content to which this {@link Representation} belongs. * Identifies the piece of content to which this {@link Representation} belongs.
@ -34,7 +36,7 @@ public class Representation {
public final String contentId; public final String contentId;
/** /**
* Identifies the revision of the {@link Representation}. * Identifies the revision of the content.
* <p> * <p>
* If the media for a given ({@link #contentId} can change over time without a change to the * If the media for a given ({@link #contentId} can change over time without a change to the
* {@link #format}'s {@link Format#id} (e.g. as a result of re-encoding the media with an * {@link #format}'s {@link Format#id} (e.g. as a result of re-encoding the media with an
@ -44,40 +46,62 @@ public class Representation {
public final long revisionId; public final long revisionId;
/** /**
* The format in which the {@link Representation} is encoded. * The format of the representation.
*/ */
public final Format format; public final Format format;
public final long contentLength; /**
* The start time of the enclosing period in milliseconds since the epoch.
*/
public final long periodStartMs;
public final long initializationStart; /**
* The duration of the enclosing period in milliseconds.
*/
public final long periodDurationMs;
public final long initializationEnd; /**
* The offset of the presentation timestamps in the media stream relative to media time.
*/
public final long presentationTimeOffsetMs;
public final long indexStart; private final RangedUri initializationUri;
public final long indexEnd; /**
* Constructs a new instance.
*
* @param periodStartMs The start time of the enclosing period in milliseconds.
* @param periodDurationMs The duration of the enclosing period in milliseconds, or -1 if the
* duration is unknown.
* @param contentId Identifies the piece of content to which this representation belongs.
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param segmentBase A segment base element for the representation.
* @return The constructed instance.
*/
public static Representation newInstance(long periodStartMs, long periodDurationMs,
String contentId, long revisionId, Format format, SegmentBase segmentBase) {
if (segmentBase instanceof SingleSegmentBase) {
return new SingleSegmentRepresentation(periodStartMs, periodDurationMs, contentId, revisionId,
format, (SingleSegmentBase) segmentBase, -1);
} else if (segmentBase instanceof MultiSegmentBase) {
return new MultiSegmentRepresentation(periodStartMs, periodDurationMs, contentId, revisionId,
format, (MultiSegmentBase) segmentBase);
} else {
throw new IllegalArgumentException("segmentBase must be of type SingleSegmentBase or "
+ "MultiSegmentBase");
}
}
public final long periodStart; private Representation(long periodStartMs, long periodDurationMs, String contentId,
long revisionId, Format format, SegmentBase segmentBase) {
public final long periodDuration; this.periodStartMs = periodStartMs;
this.periodDurationMs = periodDurationMs;
public final Uri uri;
public Representation(String contentId, long revisionId, Format format, Uri uri,
long contentLength, long initializationStart, long initializationEnd, long indexStart,
long indexEnd, long periodStart, long periodDuration) {
this.contentId = contentId; this.contentId = contentId;
this.revisionId = revisionId; this.revisionId = revisionId;
this.format = format; this.format = format;
this.contentLength = contentLength; initializationUri = segmentBase.getInitialization(this);
this.initializationStart = initializationStart; presentationTimeOffsetMs = (segmentBase.presentationTimeOffset * 1000) / segmentBase.timescale;
this.initializationEnd = initializationEnd;
this.indexStart = indexStart;
this.indexEnd = indexEnd;
this.periodStart = periodStart;
this.periodDuration = periodDuration;
this.uri = uri;
} }
/** /**
@ -87,8 +111,7 @@ public class Representation {
* @return A {@link RangedUri} defining the location of the initialization data, or null. * @return A {@link RangedUri} defining the location of the initialization data, or null.
*/ */
public RangedUri getInitializationUri() { public RangedUri getInitializationUri() {
return new RangedUri(uri, null, initializationStart, return initializationUri;
initializationEnd - initializationStart + 1);
} }
/** /**
@ -97,9 +120,7 @@ public class Representation {
* *
* @return The location of the segment index, or null. * @return The location of the segment index, or null.
*/ */
public RangedUri getIndexUri() { public abstract 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 * Gets a segment index, if the representation is able to provide one directly. Null if the
@ -107,9 +128,7 @@ public class Representation {
* *
* @return The segment index, or null. * @return The segment index, or null.
*/ */
public DashSegmentIndex getIndex() { public abstract 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
@ -121,4 +140,143 @@ public class Representation {
return contentId + "." + format.id + "." + revisionId; return contentId + "." + format.id + "." + revisionId;
} }
/**
* A DASH representation consisting of a single segment.
*/
public static class SingleSegmentRepresentation extends Representation {
/**
* The {@link Uri} of the single segment.
*/
public final Uri uri;
/**
* The content length, or -1 if unknown.
*/
public final long contentLength;
private final RangedUri indexUri;
/**
* @param periodStartMs The start time of the enclosing period in milliseconds.
* @param periodDurationMs The duration of the enclosing period in milliseconds, or -1 if the
* duration is unknown.
* @param contentId Identifies the piece of content to which this representation belongs.
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param uri The uri of the media.
* @param initializationStart The offset of the first byte of initialization data.
* @param initializationEnd The offset of the last byte of initialization data.
* @param indexStart The offset of the first byte of index data.
* @param indexEnd The offset of the last byte of index data.
* @param contentLength The content length, or -1 if unknown.
*/
public static SingleSegmentRepresentation newInstance(long periodStartMs, long periodDurationMs,
String contentId, long revisionId, Format format, Uri uri, long initializationStart,
long initializationEnd, long indexStart, long indexEnd, long contentLength) {
RangedUri rangedUri = new RangedUri(uri, null, initializationStart,
initializationEnd - initializationStart + 1);
SingleSegmentBase segmentBase = new SingleSegmentBase(rangedUri, 1, 0, uri, indexStart,
indexEnd - indexStart + 1);
return new SingleSegmentRepresentation(periodStartMs, periodDurationMs, contentId, revisionId,
format, segmentBase, contentLength);
}
/**
* @param periodStartMs The start time of the enclosing period in milliseconds.
* @param periodDurationMs The duration of the enclosing period in milliseconds, or -1 if the
* duration is unknown.
* @param contentId Identifies the piece of content to which this representation belongs.
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param segmentBase The segment base underlying the representation.
* @param contentLength The content length, or -1 if unknown.
*/
public SingleSegmentRepresentation(long periodStartMs, long periodDurationMs, String contentId,
long revisionId, Format format, SingleSegmentBase segmentBase, long contentLength) {
super(periodStartMs, periodDurationMs, contentId, revisionId, format, segmentBase);
this.uri = segmentBase.uri;
this.indexUri = segmentBase.getIndex();
this.contentLength = contentLength;
}
@Override
public RangedUri getIndexUri() {
return indexUri;
}
@Override
public DashSegmentIndex getIndex() {
return null;
}
}
/**
* A DASH representation consisting of multiple segments.
*/
public static class MultiSegmentRepresentation extends Representation
implements DashSegmentIndex {
private final MultiSegmentBase segmentBase;
/**
* @param periodStartMs The start time of the enclosing period in milliseconds.
* @param periodDurationMs The duration of the enclosing period in milliseconds, or -1 if the
* duration is unknown.
* @param contentId Identifies the piece of content to which this representation belongs.
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param segmentBase The segment base underlying the representation.
*/
public MultiSegmentRepresentation(long periodStartMs, long periodDurationMs, String contentId,
long revisionId, Format format, MultiSegmentBase segmentBase) {
super(periodStartMs, periodDurationMs, contentId, revisionId, format, segmentBase);
this.segmentBase = segmentBase;
}
@Override
public RangedUri getIndexUri() {
return null;
}
@Override
public DashSegmentIndex getIndex() {
return this;
}
// DashSegmentIndex implementation.
@Override
public RangedUri getSegmentUrl(int segmentIndex) {
return segmentBase.getSegmentUrl(this, segmentIndex);
}
@Override
public int getSegmentNum(long timeUs) {
return segmentBase.getSegmentNum(timeUs);
}
@Override
public long getTimeUs(int segmentIndex) {
return segmentBase.getSegmentTimeUs(segmentIndex);
}
@Override
public long getDurationUs(int segmentIndex) {
return segmentBase.getSegmentDurationUs(segmentIndex);
}
@Override
public int getFirstSegmentNum() {
return segmentBase.getFirstSegmentNum();
}
@Override
public int getLastSegmentNum() {
return segmentBase.getLastSegmentNum();
}
}
} }

View File

@ -1,81 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.dash.mpd;
/**
* Represents a particular segment in a Representation.
*
*/
public abstract class Segment {
public final String relativeUri;
public final long sequenceNumber;
public final long duration;
public Segment(String relativeUri, long sequenceNumber, long duration) {
this.relativeUri = relativeUri;
this.sequenceNumber = sequenceNumber;
this.duration = duration;
}
/**
* Represents a timeline segment from the MPD's SegmentTimeline list.
*/
public static class Timeline extends Segment {
public Timeline(long sequenceNumber, long duration) {
super(null, sequenceNumber, duration);
}
}
/**
* Represents an initialization segment.
*/
public static class Initialization extends Segment {
public final long initializationStart;
public final long initializationEnd;
public Initialization(String relativeUri, long initializationStart,
long initializationEnd) {
super(relativeUri, -1, -1);
this.initializationStart = initializationStart;
this.initializationEnd = initializationEnd;
}
}
/**
* Represents a media segment.
*/
public static class Media extends Segment {
public final long mediaStart;
public Media(String relativeUri, long sequenceNumber, long duration) {
this(relativeUri, 0, sequenceNumber, duration);
}
public Media(String uri, long mediaStart, long sequenceNumber, long duration) {
super(uri, sequenceNumber, duration);
this.mediaStart = mediaStart;
}
}
}

View File

@ -1,49 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.dash.mpd;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.upstream.DataSpec;
import android.net.Uri;
import java.util.List;
/**
* Represents a DASH Representation which uses the SegmentList structure (i.e. it has a list of
* Segment URLs instead of a single URL).
*/
public class SegmentedRepresentation extends Representation {
private List<Segment> segmentList;
public SegmentedRepresentation(String contentId, Format format, Uri uri, long initializationStart,
long initializationEnd, long indexStart, long indexEnd, long periodStart, long periodDuration,
List<Segment> segmentList) {
super(contentId, -1, format, uri, DataSpec.LENGTH_UNBOUNDED, initializationStart,
initializationEnd, indexStart, indexEnd, periodStart, periodDuration);
this.segmentList = segmentList;
}
public int getNumSegments() {
return segmentList.size();
}
public Segment getSegment(int i) {
return segmentList.get(i);
}
}