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:
parent
87461821fe
commit
62d17cabf0
@ -52,7 +52,7 @@ public class SampleChooserActivity extends Activity {
|
||||
sampleAdapter.addAll((Object[]) Samples.SIMPLE);
|
||||
sampleAdapter.add(new Header("YouTube DASH"));
|
||||
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.add(new Header("SmoothStreaming"));
|
||||
sampleAdapter.addAll((Object[]) Samples.SMOOTHSTREAMING);
|
||||
|
@ -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"
|
||||
+ "&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[] {
|
||||
|
@ -160,8 +160,7 @@ public class DashVodRendererBuilder implements RendererBuilder,
|
||||
}
|
||||
|
||||
// Build the video renderer.
|
||||
DataSource videoDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES,
|
||||
bandwidthMeter);
|
||||
DataSource videoDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
|
||||
ChunkSource videoChunkSource;
|
||||
String mimeType = videoRepresentations[0].format.mimeType;
|
||||
if (mimeType.equals(MimeTypes.VIDEO_MP4)) {
|
||||
@ -192,8 +191,7 @@ public class DashVodRendererBuilder implements RendererBuilder,
|
||||
audioChunkSource = null;
|
||||
audioRenderer = null;
|
||||
} else {
|
||||
DataSource audioDataSource = new HttpDataSource(userAgent,
|
||||
HttpDataSource.REJECT_PAYWALL_TYPES, bandwidthMeter);
|
||||
DataSource audioDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
|
||||
audioTrackNames = new String[audioRepresentationsList.size()];
|
||||
ChunkSource[] audioChunkSources = new ChunkSource[audioRepresentationsList.size()];
|
||||
FormatEvaluator audioEvaluator = new FormatEvaluator.FixedEvaluator();
|
||||
|
@ -150,8 +150,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
|
||||
}
|
||||
|
||||
// Build the video renderer.
|
||||
DataSource videoDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES,
|
||||
bandwidthMeter);
|
||||
DataSource videoDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
|
||||
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(url, manifest,
|
||||
videoStreamElementIndex, videoTrackIndices, videoDataSource,
|
||||
new AdaptiveEvaluator(bandwidthMeter));
|
||||
@ -173,8 +172,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
|
||||
} else {
|
||||
audioTrackNames = new String[audioStreamElementCount];
|
||||
ChunkSource[] audioChunkSources = new ChunkSource[audioStreamElementCount];
|
||||
DataSource audioDataSource = new HttpDataSource(userAgent,
|
||||
HttpDataSource.REJECT_PAYWALL_TYPES, bandwidthMeter);
|
||||
DataSource audioDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
|
||||
FormatEvaluator audioFormatEvaluator = new FormatEvaluator.FixedEvaluator();
|
||||
audioStreamElementCount = 0;
|
||||
for (int i = 0; i < manifest.streamElements.length; i++) {
|
||||
@ -204,8 +202,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
|
||||
} else {
|
||||
textTrackNames = new String[textStreamElementCount];
|
||||
ChunkSource[] textChunkSources = new ChunkSource[textStreamElementCount];
|
||||
DataSource ttmlDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES,
|
||||
bandwidthMeter);
|
||||
DataSource ttmlDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
|
||||
FormatEvaluator ttmlFormatEvaluator = new FormatEvaluator.FixedEvaluator();
|
||||
textStreamElementCount = 0;
|
||||
for (int i = 0; i < manifest.streamElements.length; i++) {
|
||||
|
@ -115,8 +115,7 @@ import java.util.ArrayList;
|
||||
videoRepresentationsList.toArray(videoRepresentations);
|
||||
|
||||
// Build the video renderer.
|
||||
DataSource videoDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES,
|
||||
bandwidthMeter);
|
||||
DataSource videoDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
|
||||
ChunkSource videoChunkSource = new DashMp4ChunkSource(videoDataSource,
|
||||
new AdaptiveEvaluator(bandwidthMeter), videoRepresentations);
|
||||
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);
|
||||
|
||||
// Build the audio renderer.
|
||||
DataSource audioDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES,
|
||||
bandwidthMeter);
|
||||
DataSource audioDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
|
||||
ChunkSource audioChunkSource = new DashMp4ChunkSource(audioDataSource,
|
||||
new FormatEvaluator.FixedEvaluator(), audioRepresentation);
|
||||
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||
|
@ -115,8 +115,7 @@ import java.util.ArrayList;
|
||||
}
|
||||
|
||||
// Build the video renderer.
|
||||
DataSource videoDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES,
|
||||
bandwidthMeter);
|
||||
DataSource videoDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
|
||||
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(url, manifest,
|
||||
videoStreamElementIndex, videoTrackIndices, videoDataSource,
|
||||
new AdaptiveEvaluator(bandwidthMeter));
|
||||
@ -126,8 +125,7 @@ import java.util.ArrayList;
|
||||
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50);
|
||||
|
||||
// Build the audio renderer.
|
||||
DataSource audioDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES,
|
||||
bandwidthMeter);
|
||||
DataSource audioDataSource = new HttpDataSource(userAgent, null, bandwidthMeter);
|
||||
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(url, manifest,
|
||||
audioStreamElementIndex, new int[] {0}, audioDataSource,
|
||||
new FormatEvaluator.FixedEvaluator());
|
||||
|
@ -309,7 +309,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
|
||||
}
|
||||
|
||||
MediaFormat mediaFormat = mediaChunk.getMediaFormat();
|
||||
if (downstreamMediaFormat == null || !downstreamMediaFormat.equals(mediaFormat)) {
|
||||
if (mediaFormat != null && !mediaFormat.equals(downstreamMediaFormat)) {
|
||||
chunkSource.getMaxVideoDimensions(mediaFormat);
|
||||
formatHolder.format = mediaFormat;
|
||||
formatHolder.drmInitData = mediaChunk.getPsshInfo();
|
||||
|
@ -74,7 +74,7 @@ public class DashMp4ChunkSource implements ChunkSource {
|
||||
this.segmentIndexes = new HashMap<String, DashSegmentIndex>();
|
||||
this.representations = new HashMap<String, Representation>();
|
||||
this.trackInfo = new TrackInfo(representations[0].format.mimeType,
|
||||
representations[0].periodDuration * 1000);
|
||||
representations[0].periodDurationMs * 1000);
|
||||
this.evaluation = new Evaluation();
|
||||
int maxWidth = 0;
|
||||
int maxHeight = 0;
|
||||
@ -198,7 +198,7 @@ public class DashMp4ChunkSource implements ChunkSource {
|
||||
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.
|
||||
// the two requests together to request both at once.
|
||||
expectedExtractorResult |= FragmentedMp4Extractor.RESULT_READ_MOOV;
|
||||
requestUri = initializationUri.attemptMerge(indexUri);
|
||||
if (requestUri != null) {
|
||||
|
@ -70,7 +70,7 @@ public class DashWebmChunkSource implements ChunkSource {
|
||||
this.segmentIndexes = new HashMap<String, DashSegmentIndex>();
|
||||
this.representations = new HashMap<String, Representation>();
|
||||
this.trackInfo = new TrackInfo(
|
||||
representations[0].format.mimeType, representations[0].periodDuration * 1000);
|
||||
representations[0].format.mimeType, representations[0].periodDurationMs * 1000);
|
||||
this.evaluation = new Evaluation();
|
||||
int maxWidth = 0;
|
||||
int maxHeight = 0;
|
||||
|
@ -17,7 +17,10 @@ package com.google.android.exoplayer.dash.mpd;
|
||||
|
||||
import com.google.android.exoplayer.ParserException;
|
||||
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.MimeTypes;
|
||||
|
||||
@ -39,11 +42,6 @@ import java.util.regex.Pattern;
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
// 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}.
|
||||
*
|
||||
@ -86,96 +86,69 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
|
||||
}
|
||||
|
||||
private MediaPresentationDescription parseMediaPresentationDescription(XmlPullParser xpp,
|
||||
String contentId, Uri parentBaseUrl) throws XmlPullParserException, IOException {
|
||||
Uri baseUrl = parentBaseUrl;
|
||||
long duration = parseDurationMs(xpp, "mediaPresentationDuration");
|
||||
long minBufferTime = parseDurationMs(xpp, "minBufferTime");
|
||||
String contentId, Uri baseUrl) throws XmlPullParserException, IOException {
|
||||
long durationMs = parseDurationMs(xpp, "mediaPresentationDuration");
|
||||
long minBufferTimeMs = parseDurationMs(xpp, "minBufferTime");
|
||||
String typeString = xpp.getAttributeValue(null, "type");
|
||||
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>();
|
||||
do {
|
||||
xpp.next();
|
||||
if (isStartTag(xpp, "BaseURL")) {
|
||||
baseUrl = parseBaseUrl(xpp, parentBaseUrl);
|
||||
baseUrl = parseBaseUrl(xpp, baseUrl);
|
||||
} else if (isStartTag(xpp, "Period")) {
|
||||
periods.add(parsePeriod(xpp, contentId, baseUrl, duration));
|
||||
periods.add(parsePeriod(xpp, contentId, baseUrl, durationMs));
|
||||
}
|
||||
} while (!isEndTag(xpp, "MPD"));
|
||||
|
||||
return new MediaPresentationDescription(duration, minBufferTime, dynamic, minUpdateTime,
|
||||
return new MediaPresentationDescription(durationMs, minBufferTimeMs, dynamic, minUpdateTimeMs,
|
||||
periods);
|
||||
}
|
||||
|
||||
private Period parsePeriod(XmlPullParser xpp, String contentId, Uri parentBaseUrl,
|
||||
long mediaPresentationDuration) throws XmlPullParserException, IOException {
|
||||
Uri baseUrl = parentBaseUrl;
|
||||
private Period parsePeriod(XmlPullParser xpp, String contentId, Uri baseUrl, long mpdDurationMs)
|
||||
throws XmlPullParserException, IOException {
|
||||
String id = xpp.getAttributeValue(null, "id");
|
||||
long start = parseDurationMs(xpp, "start", 0);
|
||||
long duration = parseDurationMs(xpp, "duration", mediaPresentationDuration);
|
||||
|
||||
long startMs = parseDurationMs(xpp, "start", 0);
|
||||
long durationMs = parseDurationMs(xpp, "duration", mpdDurationMs);
|
||||
SegmentBase segmentBase = null;
|
||||
List<AdaptationSet> adaptationSets = new ArrayList<AdaptationSet>();
|
||||
List<Segment.Timeline> segmentTimelineList = null;
|
||||
int segmentStartNumber = 0;
|
||||
int segmentTimescale = 0;
|
||||
long presentationTimeOffset = 0;
|
||||
do {
|
||||
xpp.next();
|
||||
if (isStartTag(xpp, "BaseURL")) {
|
||||
baseUrl = parseBaseUrl(xpp, parentBaseUrl);
|
||||
baseUrl = parseBaseUrl(xpp, baseUrl);
|
||||
} else if (isStartTag(xpp, "AdaptationSet")) {
|
||||
adaptationSets.add(parseAdaptationSet(xpp, contentId, baseUrl, start, duration,
|
||||
segmentTimelineList));
|
||||
adaptationSets.add(parseAdaptationSet(xpp, contentId, baseUrl, startMs, durationMs,
|
||||
segmentBase));
|
||||
} else if (isStartTag(xpp, "SegmentBase")) {
|
||||
segmentBase = parseSegmentBase(xpp, baseUrl, null);
|
||||
} else if (isStartTag(xpp, "SegmentList")) {
|
||||
segmentStartNumber = parseInt(xpp, "startNumber");
|
||||
segmentTimescale = parseInt(xpp, "timescale");
|
||||
presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", 0);
|
||||
segmentTimelineList = parsePeriodSegmentList(xpp, segmentStartNumber);
|
||||
segmentBase = parseSegmentList(xpp, baseUrl, null, durationMs);
|
||||
} else if (isStartTag(xpp, "SegmentTemplate")) {
|
||||
segmentBase = parseSegmentTemplate(xpp, baseUrl, null, durationMs);
|
||||
}
|
||||
} while (!isEndTag(xpp, "Period"));
|
||||
|
||||
return new Period(id, start, duration, adaptationSets, segmentTimelineList,
|
||||
segmentStartNumber, segmentTimescale, presentationTimeOffset);
|
||||
return new Period(id, startMs, durationMs, adaptationSets);
|
||||
}
|
||||
|
||||
private List<Segment.Timeline> parsePeriodSegmentList(
|
||||
XmlPullParser xpp, long segmentStartNumber) throws XmlPullParserException, IOException {
|
||||
List<Segment.Timeline> segmentTimelineList = new ArrayList<Segment.Timeline>();
|
||||
// AdaptationSet parsing.
|
||||
|
||||
do {
|
||||
xpp.next();
|
||||
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)
|
||||
private AdaptationSet parseAdaptationSet(XmlPullParser xpp, String contentId, Uri baseUrl,
|
||||
long periodStartMs, long periodDurationMs, SegmentBase segmentBase)
|
||||
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");
|
||||
int contentType = parseAdaptationSetTypeFromMimeType(mimeType);
|
||||
|
||||
int id = -1;
|
||||
List<ContentProtection> contentProtections = null;
|
||||
List<Representation> representations = new ArrayList<Representation>();
|
||||
do {
|
||||
xpp.next();
|
||||
if (isStartTag(xpp, "BaseURL")) {
|
||||
baseUrl = parseBaseUrl(xpp, parentBaseUrl);
|
||||
baseUrl = parseBaseUrl(xpp, baseUrl);
|
||||
} else if (isStartTag(xpp, "ContentProtection")) {
|
||||
if (contentProtections == null) {
|
||||
contentProtections = new ArrayList<ContentProtection>();
|
||||
@ -186,17 +159,62 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
|
||||
contentType = checkAdaptationSetTypeConsistency(contentType,
|
||||
parseAdaptationSetType(xpp.getAttributeValue(null, "contentType")));
|
||||
} else if (isStartTag(xpp, "Representation")) {
|
||||
Representation representation = parseRepresentation(xpp, contentId, baseUrl, periodStart,
|
||||
periodDuration, mimeType, segmentTimelineList);
|
||||
Representation representation = parseRepresentation(xpp, contentId, baseUrl, periodStartMs,
|
||||
periodDurationMs, mimeType, segmentBase);
|
||||
contentType = checkAdaptationSetTypeConsistency(contentType,
|
||||
parseAdaptationSetTypeFromMimeType(representation.format.mimeType));
|
||||
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"));
|
||||
|
||||
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.
|
||||
*
|
||||
@ -209,90 +227,194 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
|
||||
return new ContentProtection(schemeUriId, null);
|
||||
}
|
||||
|
||||
private Representation parseRepresentation(XmlPullParser xpp, String contentId, Uri parentBaseUrl,
|
||||
long periodStart, long periodDuration, String parentMimeType,
|
||||
List<Segment.Timeline> segmentTimelineList) throws XmlPullParserException, IOException {
|
||||
Uri baseUrl = parentBaseUrl;
|
||||
// Representation parsing.
|
||||
|
||||
private Representation parseRepresentation(XmlPullParser xpp, String contentId, Uri baseUrl,
|
||||
long periodStartMs, long periodDurationMs, String mimeType, SegmentBase segmentBase)
|
||||
throws XmlPullParserException, IOException {
|
||||
String id = xpp.getAttributeValue(null, "id");
|
||||
int bandwidth = parseInt(xpp, "bandwidth");
|
||||
int audioSamplingRate = parseInt(xpp, "audioSamplingRate");
|
||||
int width = parseInt(xpp, "width");
|
||||
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;
|
||||
List<Segment> segmentList = null;
|
||||
do {
|
||||
xpp.next();
|
||||
if (isStartTag(xpp, "BaseURL")) {
|
||||
baseUrl = parseBaseUrl(xpp, parentBaseUrl);
|
||||
baseUrl = parseBaseUrl(xpp, baseUrl);
|
||||
} else if (isStartTag(xpp, "AudioChannelConfiguration")) {
|
||||
numChannels = Integer.parseInt(xpp.getAttributeValue(null, "value"));
|
||||
} else if (isStartTag(xpp, "SegmentBase")) {
|
||||
String[] indexRange = xpp.getAttributeValue(null, "indexRange").split("-");
|
||||
indexStart = Long.parseLong(indexRange[0]);
|
||||
indexEnd = Long.parseLong(indexRange[1]);
|
||||
segmentBase = parseSegmentBase(xpp, baseUrl, (SingleSegmentBase) segmentBase);
|
||||
} else if (isStartTag(xpp, "SegmentList")) {
|
||||
segmentList = parseRepresentationSegmentList(xpp, segmentTimelineList);
|
||||
} else if (isStartTag(xpp, "Initialization")) {
|
||||
String[] indexRange = xpp.getAttributeValue(null, "range").split("-");
|
||||
initializationStart = Long.parseLong(indexRange[0]);
|
||||
initializationEnd = Long.parseLong(indexRange[1]);
|
||||
segmentBase = parseSegmentList(xpp, baseUrl, (SegmentList) segmentBase, periodDurationMs);
|
||||
} else if (isStartTag(xpp, "SegmentTemplate")) {
|
||||
segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase,
|
||||
periodDurationMs);
|
||||
}
|
||||
} while (!isEndTag(xpp, "Representation"));
|
||||
|
||||
Format format = new Format(id, mimeType, width, height, numChannels, audioSamplingRate,
|
||||
bandwidth);
|
||||
if (segmentList == null) {
|
||||
return new Representation(contentId, -1, format, baseUrl, DataSpec.LENGTH_UNBOUNDED,
|
||||
initializationStart, initializationEnd, indexStart, indexEnd, periodStart,
|
||||
periodDuration);
|
||||
} else {
|
||||
return new SegmentedRepresentation(contentId, format, baseUrl, initializationStart,
|
||||
initializationEnd, indexStart, indexEnd, periodStart, periodDuration, segmentList);
|
||||
}
|
||||
return Representation.newInstance(periodStartMs, periodDurationMs, contentId, -1, format,
|
||||
segmentBase);
|
||||
}
|
||||
|
||||
private List<Segment> parseRepresentationSegmentList(XmlPullParser xpp,
|
||||
List<Segment.Timeline> segmentTimelineList) throws XmlPullParserException, IOException {
|
||||
List<Segment> segmentList = new ArrayList<Segment>();
|
||||
int i = 0;
|
||||
// SegmentBase, SegmentList and SegmentTemplate parsing.
|
||||
|
||||
private SingleSegmentBase parseSegmentBase(XmlPullParser xpp, Uri baseUrl,
|
||||
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 {
|
||||
xpp.next();
|
||||
if (isStartTag(xpp, "Initialization")) {
|
||||
String url = xpp.getAttributeValue(null, "sourceURL");
|
||||
String[] indexRange = xpp.getAttributeValue(null, "range").split("-");
|
||||
long initializationStart = Long.parseLong(indexRange[0]);
|
||||
long initializationEnd = Long.parseLong(indexRange[1]);
|
||||
segmentList.add(new Segment.Initialization(url, initializationStart, initializationEnd));
|
||||
initialization = parseInitialization(xpp, baseUrl);
|
||||
} else if (isStartTag(xpp, "SegmentTimeline")) {
|
||||
timeline = parseSegmentTimeline(xpp);
|
||||
} else if (isStartTag(xpp, "SegmentURL")) {
|
||||
String url = xpp.getAttributeValue(null, "media");
|
||||
String mediaRange = xpp.getAttributeValue(null, "mediaRange");
|
||||
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));
|
||||
if (segments == null) {
|
||||
segments = new ArrayList<RangedUri>();
|
||||
}
|
||||
segments.add(parseSegmentUrl(xpp, baseUrl));
|
||||
}
|
||||
} 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 {
|
||||
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());
|
||||
}
|
||||
|
||||
protected static int parseInt(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) {
|
||||
private static long parseDurationMs(XmlPullParser xpp, String name) {
|
||||
return parseDurationMs(xpp, name, -1);
|
||||
}
|
||||
|
||||
@ -339,54 +447,38 @@ public class MediaPresentationDescriptionParser extends DefaultHandler {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static Uri parseBaseUrl(XmlPullParser xpp, Uri parentBaseUrl)
|
||||
protected static Uri parseBaseUrl(XmlPullParser xpp, Uri parentBaseUrl)
|
||||
throws XmlPullParserException, IOException {
|
||||
xpp.next();
|
||||
String newBaseUrlText = xpp.getText();
|
||||
Uri newBaseUri = Uri.parse(newBaseUrlText);
|
||||
if (newBaseUri.isAbsolute()) {
|
||||
return newBaseUri;
|
||||
} else {
|
||||
return parentBaseUrl.buildUpon().appendEncodedPath(newBaseUrlText).build();
|
||||
if (!newBaseUri.isAbsolute()) {
|
||||
newBaseUri = Uri.withAppendedPath(parentBaseUrl, newBaseUrlText);
|
||||
}
|
||||
return newBaseUri;
|
||||
}
|
||||
|
||||
private static 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;
|
||||
protected static int parseInt(XmlPullParser xpp, String name) {
|
||||
return parseInt(xpp, name, -1);
|
||||
}
|
||||
|
||||
private static 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;
|
||||
protected static int parseInt(XmlPullParser xpp, String name, int defaultValue) {
|
||||
String value = xpp.getAttributeValue(null, name);
|
||||
return value == null ? defaultValue : Integer.parseInt(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 static 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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
protected static String parseString(XmlPullParser xpp, String name, String defaultValue) {
|
||||
String value = xpp.getAttributeValue(null, name);
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,46 +23,37 @@ import java.util.List;
|
||||
*/
|
||||
public final class Period {
|
||||
|
||||
/**
|
||||
* The period identifier, if one exists.
|
||||
*/
|
||||
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<Segment.Timeline> segmentList;
|
||||
|
||||
public final int segmentStartNumber;
|
||||
|
||||
public final int segmentTimescale;
|
||||
|
||||
public final long presentationTimeOffset;
|
||||
|
||||
/**
|
||||
* @param id The period identifier. May be null.
|
||||
* @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.
|
||||
* @param adaptationSets The adaptation sets belonging to the period.
|
||||
*/
|
||||
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.start = start;
|
||||
this.duration = duration;
|
||||
this.startMs = start;
|
||||
this.durationMs = duration;
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,13 +17,15 @@ package com.google.android.exoplayer.dash.mpd;
|
||||
|
||||
import com.google.android.exoplayer.chunk.Format;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -34,7 +36,7 @@ public class Representation {
|
||||
public final String contentId;
|
||||
|
||||
/**
|
||||
* Identifies the revision of the {@link Representation}.
|
||||
* Identifies the revision of the content.
|
||||
* <p>
|
||||
* 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
|
||||
@ -44,40 +46,62 @@ public class Representation {
|
||||
public final long revisionId;
|
||||
|
||||
/**
|
||||
* The format in which the {@link Representation} is encoded.
|
||||
* The format of the representation.
|
||||
*/
|
||||
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;
|
||||
|
||||
public final long periodDuration;
|
||||
|
||||
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) {
|
||||
private Representation(long periodStartMs, long periodDurationMs, String contentId,
|
||||
long revisionId, Format format, SegmentBase segmentBase) {
|
||||
this.periodStartMs = periodStartMs;
|
||||
this.periodDurationMs = periodDurationMs;
|
||||
this.contentId = contentId;
|
||||
this.revisionId = revisionId;
|
||||
this.format = format;
|
||||
this.contentLength = contentLength;
|
||||
this.initializationStart = initializationStart;
|
||||
this.initializationEnd = initializationEnd;
|
||||
this.indexStart = indexStart;
|
||||
this.indexEnd = indexEnd;
|
||||
this.periodStart = periodStart;
|
||||
this.periodDuration = periodDuration;
|
||||
this.uri = uri;
|
||||
initializationUri = segmentBase.getInitialization(this);
|
||||
presentationTimeOffsetMs = (segmentBase.presentationTimeOffset * 1000) / segmentBase.timescale;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,8 +111,7 @@ public class Representation {
|
||||
* @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);
|
||||
return initializationUri;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,9 +120,7 @@ public class Representation {
|
||||
*
|
||||
* @return The location of the segment index, or null.
|
||||
*/
|
||||
public RangedUri getIndexUri() {
|
||||
return new RangedUri(uri, null, indexStart, indexEnd - indexStart + 1);
|
||||
}
|
||||
public abstract RangedUri getIndexUri();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public DashSegmentIndex getIndex() {
|
||||
return null;
|
||||
}
|
||||
public abstract DashSegmentIndex getIndex();
|
||||
|
||||
/**
|
||||
* Generates a cache key for the {@link Representation}, in the format
|
||||
@ -121,4 +140,143 @@ public class Representation {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user