diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java index 519a252c79..adb28ef0dc 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java @@ -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); diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java index 01bf8dc527..c2dc0ff1c7 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java @@ -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[] { diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashVodRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashVodRendererBuilder.java index 4d50825205..221d2c6daa 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashVodRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashVodRendererBuilder.java @@ -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(); diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/SmoothStreamingRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/full/player/SmoothStreamingRendererBuilder.java index 50c7c964bf..5a4e9a58cb 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/SmoothStreamingRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/full/player/SmoothStreamingRendererBuilder.java @@ -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++) { diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/simple/DashVodRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/simple/DashVodRendererBuilder.java index ec5bde031f..e3ee3d46b3 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/simple/DashVodRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/simple/DashVodRendererBuilder.java @@ -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, diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/simple/SmoothStreamingRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/simple/SmoothStreamingRendererBuilder.java index 80e4c105de..0b92810073 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/simple/SmoothStreamingRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/simple/SmoothStreamingRendererBuilder.java @@ -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()); diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java index 33a4d08a7d..61ad0c93ee 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java @@ -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(); diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java index b6d5219825..e76152859f 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashMp4ChunkSource.java @@ -74,7 +74,7 @@ public class DashMp4ChunkSource implements ChunkSource { this.segmentIndexes = new HashMap(); this.representations = new HashMap(); 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) { diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java index 3c3718fcd5..133b4879ac 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java @@ -70,7 +70,7 @@ public class DashWebmChunkSource implements ChunkSource { this.segmentIndexes = new HashMap(); this.representations = new HashMap(); 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; diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java index 3d216d4839..3bf9666006 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java @@ -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 periods = new ArrayList(); 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 adaptationSets = new ArrayList(); - List 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 parsePeriodSegmentList( - XmlPullParser xpp, long segmentStartNumber) throws XmlPullParserException, IOException { - List segmentTimelineList = new ArrayList(); + // 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 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 contentProtections = null; List representations = new ArrayList(); 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(); @@ -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. + *

+ * 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 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 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 parseRepresentationSegmentList(XmlPullParser xpp, - List segmentTimelineList) throws XmlPullParserException, IOException { - List segmentList = new ArrayList(); - 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 timeline = null; + List 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(); } + 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 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 parseSegmentTimeline(XmlPullParser xpp) + throws XmlPullParserException, IOException { + List segmentTimeline = new ArrayList(); + 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. - *

- * 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; } } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Period.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/Period.java index 71294204e7..6fd3a71f4f 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Period.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/Period.java @@ -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 adaptationSets; - public final List 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 adaptationSets) { - this(id, start, duration, adaptationSets, null, 0, 0, 0); - } - - public Period(String id, long start, long duration, List adaptationSets, - List segmentList, int segmentStartNumber, int segmentTimescale) { - this(id, start, duration, adaptationSets, segmentList, segmentStartNumber, segmentTimescale, 0); - } - - public Period(String id, long start, long duration, List adaptationSets, - List 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; } } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java index e1d309266b..d089ba7f59 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java @@ -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. *

* 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(); + } + + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Segment.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/Segment.java deleted file mode 100644 index 681c7aae12..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Segment.java +++ /dev/null @@ -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; - } - - } -} diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentedRepresentation.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentedRepresentation.java deleted file mode 100644 index 53f14c3852..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentedRepresentation.java +++ /dev/null @@ -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 segmentList; - - public SegmentedRepresentation(String contentId, Format format, Uri uri, long initializationStart, - long initializationEnd, long indexStart, long indexEnd, long periodStart, long periodDuration, - List 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); - } - -}