From e2081f40fb01657ec32a023cae6dcfb8b29bcb00 Mon Sep 17 00:00:00 2001 From: zhihuichen Date: Mon, 18 Jul 2016 16:51:25 +0100 Subject: [PATCH] move baseUrl from segments to representations: V2 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=138136090 --- .../assets/dash/sample_mpd_3_segment_template | 38 +++++++++ .../dash/manifest/DashManifestParserTest.java | 28 +++++++ .../source/dash/manifest/RangedUriTest.java | 64 ++++++++------- .../dash/manifest/RepresentationTest.java | 7 +- .../source/dash/DashWrappingSegmentIndex.java | 4 +- .../source/dash/DefaultDashChunkSource.java | 11 +-- .../dash/manifest/DashManifestParser.java | 77 ++++++++++--------- .../source/dash/manifest/RangedUri.java | 49 ++++++------ .../source/dash/manifest/Representation.java | 39 ++++++---- .../source/dash/manifest/SegmentBase.java | 26 ++----- 10 files changed, 204 insertions(+), 139 deletions(-) create mode 100644 library/src/androidTest/assets/dash/sample_mpd_3_segment_template diff --git a/library/src/androidTest/assets/dash/sample_mpd_3_segment_template b/library/src/androidTest/assets/dash/sample_mpd_3_segment_template new file mode 100644 index 0000000000..a9147b54df --- /dev/null +++ b/library/src/androidTest/assets/dash/sample_mpd_3_segment_template @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + http://redirector.googlevideo.com/videoplayback/id/BktsoMO3OMs.0/itag/140/source/yt_live_broadcast/ratebypass/yes/cmbypass/yes/mime/audio%2Fmp4/live/1/gir/yes/noclen/1/signature/B5137EA0CC278C07DD056D204E863CC81EDEB39E.1AD5D242EBC94922EDA7165353A89A5E08A4103A/key/dg_test0/mpd_version/5/ip/0.0.0.0/ipbits/0/expire/1476490914/sparams/ip,ipbits,expire,id,itag,source,ratebypass,cmbypass,mime,live,gir,noclen/ + + + + + + http://redirector.googlevideo.com/videoplayback/id/BktsoMO3OMs.0/itag/133/source/yt_live_broadcast/ratebypass/yes/cmbypass/yes/mime/video%2Fmp4/live/1/gir/yes/noclen/1/signature/90154AE9C5C9D9D519CBF2E43AB0A1778375992D.40E2E855ADFB38FA7E95E168FEEEA6796B080BD7/key/dg_test0/mpd_version/5/ip/0.0.0.0/ipbits/0/expire/1476490914/sparams/ip,ipbits,expire,id,itag,source,ratebypass,cmbypass,mime,live,gir,noclen/ + + + http://redirector.googlevideo.com/videoplayback/id/BktsoMO3OMs.0/itag/134/source/yt_live_broadcast/ratebypass/yes/cmbypass/yes/mime/video%2Fmp4/live/1/gir/yes/noclen/1/signature/5C094AEFDCEB1A4D2F3C05F8BD095C336EF0E1C3.7AE6B9951B0237AAE6F031927AACAC4974BAFFAA/key/dg_test0/mpd_version/5/ip/0.0.0.0/ipbits/0/expire/1476490914/sparams/ip,ipbits,expire,id,itag,source,ratebypass,cmbypass,mime,live,gir,noclen/ + + + http://redirector.googlevideo.com/videoplayback/id/BktsoMO3OMs.0/itag/135/source/yt_live_broadcast/ratebypass/yes/cmbypass/yes/mime/video%2Fmp4/live/1/gir/yes/noclen/1/signature/1F7660CA4E5B4AE4D60E18795680E34CDD2EF3C9.800B0A1D5F490DE142CCF4C88C64FD21D42129/key/dg_test0/mpd_version/5/ip/0.0.0.0/ipbits/0/expire/1476490914/sparams/ip,ipbits,expire,id,itag,source,ratebypass,cmbypass,mime,live,gir,noclen/ + + + http://redirector.googlevideo.com/videoplayback/id/BktsoMO3OMs.0/itag/160/source/yt_live_broadcast/ratebypass/yes/cmbypass/yes/mime/video%2Fmp4/live/1/gir/yes/noclen/1/signature/94EB61673784DF0C4237A1A866F2E171C8A64ADB.AEC00AA06C2278FEA8702FB62693B70D8977F46C/key/dg_test0/mpd_version/5/ip/0.0.0.0/ipbits/0/expire/1476490914/sparams/ip,ipbits,expire,id,itag,source,ratebypass,cmbypass,mime,live,gir,noclen/ + + + http://redirector.googlevideo.com/videoplayback/id/BktsoMO3OMs.0/itag/136/source/yt_live_broadcast/ratebypass/yes/cmbypass/yes/mime/video%2Fmp4/live/1/gir/yes/noclen/1/signature/6D8C34FC30A1F1A4F700B61180D1C4CCF6274844.29EBCB4A837DE626C52C66CF650519E61C2FF0BF/key/dg_test0/mpd_version/5/ip/0.0.0.0/ipbits/0/expire/1476490914/sparams/ip,ipbits,expire,id,itag,source,ratebypass,cmbypass,mime,live,gir,noclen/ + + + + + diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java index 66ee298daf..460d3bb04e 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java @@ -28,6 +28,8 @@ public class DashManifestParserTest extends InstrumentationTestCase { private static final String SAMPLE_MPD_1 = "dash/sample_mpd_1"; private static final String SAMPLE_MPD_2_UNKNOWN_MIME_TYPE = "dash/sample_mpd_2_unknown_mime_type"; + private static final String SAMPLE_MPD_3_SEGMENT_TEMPLATE = + "dash/sample_mpd_3_segment_template"; /** * Simple test to ensure the sample manifests parse without any exceptions being thrown. @@ -40,4 +42,30 @@ public class DashManifestParserTest extends InstrumentationTestCase { TestUtil.getInputStream(getInstrumentation(), SAMPLE_MPD_2_UNKNOWN_MIME_TYPE)); } + public void testParseMediaPresentationDescriptionWithSegmentTemplate() throws IOException { + DashManifestParser parser = new DashManifestParser(); + DashManifest mpd = parser.parse(Uri.parse("https://example.com/test.mpd"), + TestUtil.getInputStream(getInstrumentation(), SAMPLE_MPD_3_SEGMENT_TEMPLATE)); + + assertEquals(1, mpd.getPeriodCount()); + + Period period = mpd.getPeriod(0); + assertNotNull(period); + assertEquals(2, period.adaptationSets.size()); + + for (AdaptationSet adaptationSet : period.adaptationSets) { + assertNotNull(adaptationSet); + for (Representation representation : adaptationSet.representations) { + if (representation instanceof Representation.MultiSegmentRepresentation) { + Representation.MultiSegmentRepresentation multiSegmentRepresentation = + (Representation.MultiSegmentRepresentation) representation; + int firstSegmentIndex = multiSegmentRepresentation.getFirstSegmentNum(); + RangedUri uri = multiSegmentRepresentation.getSegmentUrl(firstSegmentIndex); + assertTrue(uri.resolveUriString(representation.baseUrl).contains( + "redirector.googlevideo.com")); + } + } + } + } + } diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java index 59e1c14a33..fd559381fa 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java @@ -23,56 +23,64 @@ import junit.framework.TestCase; */ public class RangedUriTest extends TestCase { - private static final String FULL_URI = "http://www.test.com/path/file.ext"; + private static final String BASE_URI = "http://www.test.com/"; + private static final String PARTIAL_URI = "path/file.ext"; + private static final String FULL_URI = BASE_URI + PARTIAL_URI; public void testMerge() { - RangedUri rangeA = new RangedUri(null, FULL_URI, 0, 10); - RangedUri rangeB = new RangedUri(null, FULL_URI, 10, 10); - RangedUri expected = new RangedUri(null, FULL_URI, 0, 20); - assertMerge(rangeA, rangeB, expected); + RangedUri rangeA = new RangedUri(FULL_URI, 0, 10); + RangedUri rangeB = new RangedUri(FULL_URI, 10, 10); + RangedUri expected = new RangedUri(FULL_URI, 0, 20); + assertMerge(rangeA, rangeB, expected, null); } public void testMergeUnbounded() { - RangedUri rangeA = new RangedUri(null, FULL_URI, 0, 10); - RangedUri rangeB = new RangedUri(null, FULL_URI, 10, C.LENGTH_UNSET); - RangedUri expected = new RangedUri(null, FULL_URI, 0, C.LENGTH_UNSET); - assertMerge(rangeA, rangeB, expected); + RangedUri rangeA = new RangedUri(FULL_URI, 0, 10); + RangedUri rangeB = new RangedUri(FULL_URI, 10, C.LENGTH_UNSET); + RangedUri expected = new RangedUri(FULL_URI, 0, C.LENGTH_UNSET); + assertMerge(rangeA, rangeB, expected, null); } public void testNonMerge() { // A and B do not overlap, so should not merge - RangedUri rangeA = new RangedUri(null, FULL_URI, 0, 10); - RangedUri rangeB = new RangedUri(null, FULL_URI, 11, 10); - assertNonMerge(rangeA, rangeB); + RangedUri rangeA = new RangedUri(FULL_URI, 0, 10); + RangedUri rangeB = new RangedUri(FULL_URI, 11, 10); + assertNonMerge(rangeA, rangeB, null); // A and B do not overlap, so should not merge - rangeA = new RangedUri(null, FULL_URI, 0, 10); - rangeB = new RangedUri(null, FULL_URI, 11, C.LENGTH_UNSET); - assertNonMerge(rangeA, rangeB); + rangeA = new RangedUri(FULL_URI, 0, 10); + rangeB = new RangedUri(FULL_URI, 11, C.LENGTH_UNSET); + assertNonMerge(rangeA, rangeB, null); // A and B are bounded but overlap, so should not merge - rangeA = new RangedUri(null, FULL_URI, 0, 11); - rangeB = new RangedUri(null, FULL_URI, 10, 10); - assertNonMerge(rangeA, rangeB); + rangeA = new RangedUri(FULL_URI, 0, 11); + rangeB = new RangedUri(FULL_URI, 10, 10); + assertNonMerge(rangeA, rangeB, null); // A and B overlap due to unboundedness, so should not merge - rangeA = new RangedUri(null, FULL_URI, 0, C.LENGTH_UNSET); - rangeB = new RangedUri(null, FULL_URI, 10, C.LENGTH_UNSET); - assertNonMerge(rangeA, rangeB); - + rangeA = new RangedUri(FULL_URI, 0, C.LENGTH_UNSET); + rangeB = new RangedUri(FULL_URI, 10, C.LENGTH_UNSET); + assertNonMerge(rangeA, rangeB, null); } - private void assertMerge(RangedUri rangeA, RangedUri rangeB, RangedUri expected) { - RangedUri merged = rangeA.attemptMerge(rangeB); + public void testMergeWithBaseUri() { + RangedUri rangeA = new RangedUri(PARTIAL_URI, 0, 10); + RangedUri rangeB = new RangedUri(FULL_URI, 10, 10); + RangedUri expected = new RangedUri(FULL_URI, 0, 20); + assertMerge(rangeA, rangeB, expected, BASE_URI); + } + + private void assertMerge(RangedUri rangeA, RangedUri rangeB, RangedUri expected, String baseUrl) { + RangedUri merged = rangeA.attemptMerge(rangeB, baseUrl); assertEquals(expected, merged); - merged = rangeB.attemptMerge(rangeA); + merged = rangeB.attemptMerge(rangeA, baseUrl); assertEquals(expected, merged); } - private void assertNonMerge(RangedUri rangeA, RangedUri rangeB) { - RangedUri merged = rangeA.attemptMerge(rangeB); + private void assertNonMerge(RangedUri rangeA, RangedUri rangeB, String baseUrl) { + RangedUri merged = rangeA.attemptMerge(rangeB, baseUrl); assertNull(merged); - merged = rangeB.attemptMerge(rangeA); + merged = rangeB.attemptMerge(rangeA, baseUrl); assertNull(merged); } diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java index 681969ffa2..008cd0e556 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java @@ -27,16 +27,17 @@ public class RepresentationTest extends TestCase { public void testGetCacheKey() { String uri = "http://www.google.com"; - SegmentBase base = new SingleSegmentBase(new RangedUri(uri, null, 0, 1), 1, 0, uri, 1, 1); + SegmentBase base = new SingleSegmentBase(new RangedUri(null, 0, 1), 1, 0, 1, 1); Format format = Format.createVideoContainerFormat("0", MimeTypes.APPLICATION_MP4, null, MimeTypes.VIDEO_H264, 2500000, 1920, 1080, Format.NO_VALUE, null); - Representation representation = Representation.newInstance("test_stream_1", 3, format, base); + Representation representation = Representation.newInstance("test_stream_1", 3, format, uri, + base); assertEquals("test_stream_1.0.3", representation.getCacheKey()); format = Format.createVideoContainerFormat("150", MimeTypes.APPLICATION_MP4, null, MimeTypes.VIDEO_H264, 2500000, 1920, 1080, Format.NO_VALUE, null); representation = Representation.newInstance("test_stream_1", Representation.REVISION_ID_DEFAULT, - format, base); + format, uri, base); assertEquals("test_stream_1.150.-1", representation.getCacheKey()); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java index 716c9ad844..9e48bc2c79 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java @@ -25,7 +25,6 @@ import com.google.android.exoplayer2.source.dash.manifest.RangedUri; /* package */ final class DashWrappingSegmentIndex implements DashSegmentIndex { private final ChunkIndex chunkIndex; - private final String uri; /** * @param chunkIndex The {@link ChunkIndex} to wrap. @@ -33,7 +32,6 @@ import com.google.android.exoplayer2.source.dash.manifest.RangedUri; */ public DashWrappingSegmentIndex(ChunkIndex chunkIndex, String uri) { this.chunkIndex = chunkIndex; - this.uri = uri; } @Override @@ -58,7 +56,7 @@ import com.google.android.exoplayer2.source.dash.manifest.RangedUri; @Override public RangedUri getSegmentUrl(int segmentNum) { - return new RangedUri(uri, null, chunkIndex.offsets[segmentNum], chunkIndex.sizes[segmentNum]); + return new RangedUri(null, chunkIndex.offsets[segmentNum], chunkIndex.sizes[segmentNum]); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index a7c7389b2b..83b8724170 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -288,18 +288,19 @@ public class DefaultDashChunkSource implements DashChunkSource { DataSource dataSource, Format trackFormat, int trackSelectionReason, Object trackSelectionData, RangedUri initializationUri, RangedUri indexUri) { RangedUri requestUri; + String baseUrl = representationHolder.representation.baseUrl; if (initializationUri != null) { // It's common for initialization and index data to be stored adjacently. Attempt to merge // the two requests together to request both at once. - requestUri = initializationUri.attemptMerge(indexUri); + requestUri = initializationUri.attemptMerge(indexUri, baseUrl); if (requestUri == null) { requestUri = initializationUri; } } else { requestUri = indexUri; } - DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length, - representationHolder.representation.getCacheKey()); + DataSpec dataSpec = new DataSpec(requestUri.resolveUri(baseUrl), requestUri.start, + requestUri.length, representationHolder.representation.getCacheKey()); return new InitializationChunk(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper); } @@ -311,8 +312,8 @@ public class DefaultDashChunkSource implements DashChunkSource { long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum); long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum); RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum); - DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, - representation.getCacheKey()); + DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(representation.baseUrl), + segmentUri.start, segmentUri.length, representation.getCacheKey()); if (representationHolder.extractorWrapper == null) { return new SingleSampleMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason, diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index b2f0ae6f98..fdfe8cb4b3 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -205,11 +205,11 @@ public class DashManifestParser extends DefaultHandler } else if (XmlPullParserUtil.isStartTag(xpp, "AdaptationSet")) { adaptationSets.add(parseAdaptationSet(xpp, baseUrl, segmentBase)); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { - segmentBase = parseSegmentBase(xpp, baseUrl, null); + segmentBase = parseSegmentBase(xpp, null); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) { - segmentBase = parseSegmentList(xpp, baseUrl, null); + segmentBase = parseSegmentList(xpp, null); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { - segmentBase = parseSegmentTemplate(xpp, baseUrl, null); + segmentBase = parseSegmentTemplate(xpp, null); } } while (!XmlPullParserUtil.isEndTag(xpp, "Period")); @@ -263,11 +263,11 @@ public class DashManifestParser extends DefaultHandler } else if (XmlPullParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) { audioChannels = parseAudioChannelConfiguration(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { - segmentBase = parseSegmentBase(xpp, baseUrl, (SingleSegmentBase) segmentBase); + segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) { - segmentBase = parseSegmentList(xpp, baseUrl, (SegmentList) segmentBase); + segmentBase = parseSegmentList(xpp, (SegmentList) segmentBase); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { - segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase); + segmentBase = parseSegmentTemplate(xpp, (SegmentTemplate) segmentBase); } else if (XmlPullParserUtil.isStartTag(xpp)) { parseAdaptationSetChild(xpp); } @@ -390,11 +390,11 @@ public class DashManifestParser extends DefaultHandler } else if (XmlPullParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) { audioChannels = parseAudioChannelConfiguration(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { - segmentBase = parseSegmentBase(xpp, baseUrl, (SingleSegmentBase) segmentBase); + segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) { - segmentBase = parseSegmentList(xpp, baseUrl, (SegmentList) segmentBase); + segmentBase = parseSegmentList(xpp, (SegmentList) segmentBase); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { - segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase); + segmentBase = parseSegmentTemplate(xpp, (SegmentTemplate) segmentBase); } else if (XmlPullParserUtil.isStartTag(xpp, "ContentProtection")) { SchemeData contentProtection = parseContentProtection(xpp); if (contentProtection != null) { @@ -407,7 +407,7 @@ public class DashManifestParser extends DefaultHandler audioSamplingRate, bandwidth, adaptationSetLanguage, codecs); segmentBase = segmentBase != null ? segmentBase : new SingleSegmentBase(baseUrl); - return new RepresentationInfo(format, segmentBase, drmSchemeDatas); + return new RepresentationInfo(format, baseUrl, segmentBase, drmSchemeDatas); } protected Format buildFormat(String id, String containerMimeType, int width, int height, @@ -441,13 +441,13 @@ public class DashManifestParser extends DefaultHandler format = format.copyWithDrmInitData(new DrmInitData(drmSchemeDatas)); } return Representation.newInstance(contentId, Representation.REVISION_ID_DEFAULT, format, - representationInfo.segmentBase); + representationInfo.baseUrl, representationInfo.segmentBase); } // SegmentBase, SegmentList and SegmentTemplate parsing. - protected SingleSegmentBase parseSegmentBase(XmlPullParser xpp, String baseUrl, - SingleSegmentBase parent) throws XmlPullParserException, IOException { + protected SingleSegmentBase parseSegmentBase(XmlPullParser xpp, SingleSegmentBase parent) + throws XmlPullParserException, IOException { long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", @@ -466,21 +466,21 @@ public class DashManifestParser extends DefaultHandler do { xpp.next(); if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { - initialization = parseInitialization(xpp, baseUrl); + initialization = parseInitialization(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentBase")); - return buildSingleSegmentBase(initialization, timescale, presentationTimeOffset, baseUrl, - indexStart, indexLength); + return buildSingleSegmentBase(initialization, timescale, presentationTimeOffset, indexStart, + indexLength); } protected SingleSegmentBase buildSingleSegmentBase(RangedUri initialization, long timescale, - long presentationTimeOffset, String baseUrl, long indexStart, long indexLength) { - return new SingleSegmentBase(initialization, timescale, presentationTimeOffset, baseUrl, - indexStart, indexLength); + long presentationTimeOffset, long indexStart, long indexLength) { + return new SingleSegmentBase(initialization, timescale, presentationTimeOffset, indexStart, + indexLength); } - protected SegmentList parseSegmentList(XmlPullParser xpp, String baseUrl, SegmentList parent) + protected SegmentList parseSegmentList(XmlPullParser xpp, SegmentList parent) throws XmlPullParserException, IOException { long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); @@ -496,14 +496,14 @@ public class DashManifestParser extends DefaultHandler do { xpp.next(); if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { - initialization = parseInitialization(xpp, baseUrl); + initialization = parseInitialization(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) { timeline = parseSegmentTimeline(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentURL")) { if (segments == null) { segments = new ArrayList<>(); } - segments.add(parseSegmentUrl(xpp, baseUrl)); + segments.add(parseSegmentUrl(xpp)); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentList")); @@ -524,8 +524,8 @@ public class DashManifestParser extends DefaultHandler startNumber, duration, timeline, segments); } - protected SegmentTemplate parseSegmentTemplate(XmlPullParser xpp, String baseUrl, - SegmentTemplate parent) throws XmlPullParserException, IOException { + protected SegmentTemplate parseSegmentTemplate(XmlPullParser xpp, SegmentTemplate parent) + throws XmlPullParserException, IOException { long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", parent != null ? parent.presentationTimeOffset : 0); @@ -542,7 +542,7 @@ public class DashManifestParser extends DefaultHandler do { xpp.next(); if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { - initialization = parseInitialization(xpp, baseUrl); + initialization = parseInitialization(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) { timeline = parseSegmentTimeline(xpp); } @@ -554,15 +554,15 @@ public class DashManifestParser extends DefaultHandler } return buildSegmentTemplate(initialization, timescale, presentationTimeOffset, - startNumber, duration, timeline, initializationTemplate, mediaTemplate, baseUrl); + startNumber, duration, timeline, initializationTemplate, mediaTemplate); } protected SegmentTemplate buildSegmentTemplate(RangedUri initialization, long timescale, long presentationTimeOffset, int startNumber, long duration, List timeline, UrlTemplate initializationTemplate, - UrlTemplate mediaTemplate, String baseUrl) { + UrlTemplate mediaTemplate) { return new SegmentTemplate(initialization, timescale, presentationTimeOffset, - startNumber, duration, timeline, initializationTemplate, mediaTemplate, baseUrl); + startNumber, duration, timeline, initializationTemplate, mediaTemplate); } protected List parseSegmentTimeline(XmlPullParser xpp) @@ -597,15 +597,15 @@ public class DashManifestParser extends DefaultHandler return defaultValue; } - protected RangedUri parseInitialization(XmlPullParser xpp, String baseUrl) { - return parseRangedUrl(xpp, baseUrl, "sourceURL", "range"); + protected RangedUri parseInitialization(XmlPullParser xpp) { + return parseRangedUrl(xpp, "sourceURL", "range"); } - protected RangedUri parseSegmentUrl(XmlPullParser xpp, String baseUrl) { - return parseRangedUrl(xpp, baseUrl, "media", "mediaRange"); + protected RangedUri parseSegmentUrl(XmlPullParser xpp) { + return parseRangedUrl(xpp, "media", "mediaRange"); } - protected RangedUri parseRangedUrl(XmlPullParser xpp, String baseUrl, String urlAttribute, + protected RangedUri parseRangedUrl(XmlPullParser xpp, String urlAttribute, String rangeAttribute) { String urlText = xpp.getAttributeValue(null, urlAttribute); long rangeStart = 0; @@ -618,12 +618,11 @@ public class DashManifestParser extends DefaultHandler rangeLength = Long.parseLong(rangeTextArray[1]) - rangeStart + 1; } } - return buildRangedUri(baseUrl, urlText, rangeStart, rangeLength); + return buildRangedUri(urlText, rangeStart, rangeLength); } - protected RangedUri buildRangedUri(String baseUrl, String urlText, long rangeStart, - long rangeLength) { - return new RangedUri(baseUrl, urlText, rangeStart, rangeLength); + protected RangedUri buildRangedUri(String urlText, long rangeStart, long rangeLength) { + return new RangedUri(urlText, rangeStart, rangeLength); } // AudioChannelConfiguration parsing. @@ -788,12 +787,14 @@ public class DashManifestParser extends DefaultHandler private static final class RepresentationInfo { public final Format format; + public final String baseUrl; public final SegmentBase segmentBase; public final ArrayList drmSchemeDatas; - public RepresentationInfo(Format format, SegmentBase segmentBase, + public RepresentationInfo(Format format, String baseUrl, SegmentBase segmentBase, ArrayList drmSchemeDatas) { this.format = format; + this.baseUrl = baseUrl; this.segmentBase = segmentBase; this.drmSchemeDatas = drmSchemeDatas; } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java index 1668526b22..c2a64718df 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java @@ -17,11 +17,10 @@ package com.google.android.exoplayer2.source.dash.manifest; import android.net.Uri; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.UriUtil; /** - * Defines a range of data located at a {@link Uri}. + * Defines a range of data located at a reference uri. */ public final class RangedUri { @@ -35,12 +34,6 @@ public final class RangedUri { */ public final long length; - // The URI is stored internally in two parts: reference URI and a base URI to use when - // resolving it. This helps optimize memory usage in the same way that DASH manifests allow many - // URLs to be expressed concisely in the form of a single BaseURL and many relative paths. Note - // that this optimization relies on the same object being passed as the base URI to many - // instances of this class. - private final String baseUri; private final String referenceUri; private int hashCode; @@ -48,57 +41,59 @@ public final class RangedUri { /** * Constructs an ranged uri. * - * @param baseUri A uri that can form the base of the uri defined by the instance. - * @param referenceUri A reference uri that should be resolved with respect to {@code baseUri}. + * @param referenceUri The reference uri. * @param start The (zero based) index of the first byte of the range. * @param length The length of the range, or {@link C#LENGTH_UNSET} to indicate that the range is * unbounded. */ - public RangedUri(String baseUri, String referenceUri, long start, long length) { - Assertions.checkArgument(baseUri != null || referenceUri != null); - this.baseUri = baseUri; - this.referenceUri = referenceUri; + public RangedUri(String referenceUri, long start, long length) { + this.referenceUri = referenceUri == null ? "" : referenceUri; this.start = start; this.length = length; } /** - * Returns the {@link Uri} represented by the instance. + * Returns the resolved {@link Uri} represented by the instance. * + * @param baseUri The base Uri. * @return The {@link Uri} represented by the instance. */ - public Uri getUri() { + public Uri resolveUri(String baseUri) { return UriUtil.resolveToUri(baseUri, referenceUri); } /** - * Returns the uri represented by the instance as a string. + * Returns the resolved uri represented by the instance as a string. * + * @param baseUri The base Uri. * @return The uri represented by the instance. */ - public String getUriString() { + public String resolveUriString(String baseUri) { return UriUtil.resolve(baseUri, referenceUri); } /** - * Attempts to merge this {@link RangedUri} with another. + * Attempts to merge this {@link RangedUri} with another and an optional common base uri. *

- * A merge is successful if both instances define the same {@link Uri}, and if one starts the byte - * after the other ends, forming a contiguous region with no overlap. + * A merge is successful if both instances define the same {@link Uri} after resolution with the + * base uri, and if one starts the byte after the other ends, forming a contiguous region with + * no overlap. *

* If {@code other} is null then the merge is considered unsuccessful, and null is returned. * * @param other The {@link RangedUri} to merge. + * @param baseUri The optional base Uri. * @return The merged {@link RangedUri} if the merge was successful. Null otherwise. */ - public RangedUri attemptMerge(RangedUri other) { - if (other == null || !getUriString().equals(other.getUriString())) { + public RangedUri attemptMerge(RangedUri other, String baseUri) { + final String resolvedUri = resolveUriString(baseUri); + if (other == null || !resolvedUri.equals(other.resolveUriString(baseUri))) { return null; } else if (length != C.LENGTH_UNSET && start + length == other.start) { - return new RangedUri(baseUri, referenceUri, start, + return new RangedUri(resolvedUri, start, other.length == C.LENGTH_UNSET ? C.LENGTH_UNSET : length + other.length); } else if (other.length != C.LENGTH_UNSET && other.start + other.length == start) { - return new RangedUri(baseUri, referenceUri, other.start, + return new RangedUri(resolvedUri, other.start, length == C.LENGTH_UNSET ? C.LENGTH_UNSET : other.length + length); } else { return null; @@ -111,7 +106,7 @@ public final class RangedUri { int result = 17; result = 31 * result + (int) start; result = 31 * result + (int) length; - result = 31 * result + getUriString().hashCode(); + result = 31 * result + referenceUri.hashCode(); hashCode = result; } return hashCode; @@ -128,7 +123,7 @@ public final class RangedUri { RangedUri other = (RangedUri) obj; return this.start == other.start && this.length == other.length - && getUriString().equals(other.getUriString()); + && referenceUri.equals(other.referenceUri); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java index 6ebd69e29b..f52727c1a8 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java @@ -52,6 +52,10 @@ public abstract class Representation { * The format of the representation. */ public final Format format; + /** + * The base URL of the representation. + */ + public final String baseUrl; /** * The offset of the presentation timestamps in the media stream relative to media time. */ @@ -65,12 +69,13 @@ public abstract class Representation { * @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 baseUrl The base URL. * @param segmentBase A segment base element for the representation. * @return The constructed instance. */ public static Representation newInstance(String contentId, long revisionId, Format format, - SegmentBase segmentBase) { - return newInstance(contentId, revisionId, format, segmentBase, null); + String baseUrl, SegmentBase segmentBase) { + return newInstance(contentId, revisionId, format, baseUrl, segmentBase, null); } /** @@ -79,18 +84,19 @@ public abstract class Representation { * @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 baseUrl The base URL of the representation. * @param segmentBase A segment base element for the representation. * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. This * parameter is ignored if {@code segmentBase} consists of multiple segments. * @return The constructed instance. */ public static Representation newInstance(String contentId, long revisionId, Format format, - SegmentBase segmentBase, String customCacheKey) { + String baseUrl, SegmentBase segmentBase, String customCacheKey) { if (segmentBase instanceof SingleSegmentBase) { - return new SingleSegmentRepresentation(contentId, revisionId, format, + return new SingleSegmentRepresentation(contentId, revisionId, format, baseUrl, (SingleSegmentBase) segmentBase, customCacheKey, C.LENGTH_UNSET); } else if (segmentBase instanceof MultiSegmentBase) { - return new MultiSegmentRepresentation(contentId, revisionId, format, + return new MultiSegmentRepresentation(contentId, revisionId, format, baseUrl, (MultiSegmentBase) segmentBase); } else { throw new IllegalArgumentException("segmentBase must be of type SingleSegmentBase or " @@ -98,11 +104,12 @@ public abstract class Representation { } } - private Representation(String contentId, long revisionId, Format format, + private Representation(String contentId, long revisionId, Format format, String baseUrl, SegmentBase segmentBase) { this.contentId = contentId; this.revisionId = revisionId; this.format = format; + this.baseUrl = baseUrl; initializationUri = segmentBase.getInitialization(this); presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs(); } @@ -166,26 +173,27 @@ public abstract class Representation { public static SingleSegmentRepresentation newInstance(String contentId, long revisionId, Format format, String uri, long initializationStart, long initializationEnd, long indexStart, long indexEnd, String customCacheKey, long contentLength) { - RangedUri rangedUri = new RangedUri(uri, null, initializationStart, + RangedUri rangedUri = new RangedUri(null, initializationStart, initializationEnd - initializationStart + 1); - SingleSegmentBase segmentBase = new SingleSegmentBase(rangedUri, 1, 0, uri, indexStart, + SingleSegmentBase segmentBase = new SingleSegmentBase(rangedUri, 1, 0, indexStart, indexEnd - indexStart + 1); return new SingleSegmentRepresentation(contentId, revisionId, - format, segmentBase, customCacheKey, contentLength); + format, uri, segmentBase, customCacheKey, contentLength); } /** * @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 baseUrl The base URL of the representation. * @param segmentBase The segment base underlying the representation. * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. * @param contentLength The content length, or {@link C#LENGTH_UNSET} if unknown. */ public SingleSegmentRepresentation(String contentId, long revisionId, Format format, - SingleSegmentBase segmentBase, String customCacheKey, long contentLength) { - super(contentId, revisionId, format, segmentBase); - this.uri = Uri.parse(segmentBase.uri); + String baseUrl, SingleSegmentBase segmentBase, String customCacheKey, long contentLength) { + super(contentId, revisionId, format, baseUrl, segmentBase); + this.uri = Uri.parse(baseUrl); this.indexUri = segmentBase.getIndex(); this.cacheKey = customCacheKey != null ? customCacheKey : contentId != null ? contentId + "." + format.id + "." + revisionId : null; @@ -193,7 +201,7 @@ public abstract class Representation { // If we have an index uri then the index is defined externally, and we shouldn't return one // directly. If we don't, then we can't do better than an index defining a single segment. segmentIndex = indexUri != null ? null - : new SingleSegmentIndex(new RangedUri(segmentBase.uri, null, 0, contentLength)); + : new SingleSegmentIndex(new RangedUri(null, 0, contentLength)); } @Override @@ -225,11 +233,12 @@ public abstract class Representation { * @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 baseUrl The base URL of the representation. * @param segmentBase The segment base underlying the representation. */ public MultiSegmentRepresentation(String contentId, long revisionId, Format format, - MultiSegmentBase segmentBase) { - super(contentId, revisionId, format, segmentBase); + String baseUrl, MultiSegmentBase segmentBase) { + super(contentId, revisionId, format, baseUrl, segmentBase); this.segmentBase = segmentBase; } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java index dec626c326..966f88e5bc 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java @@ -65,11 +65,6 @@ public abstract class SegmentBase { */ public static class SingleSegmentBase extends SegmentBase { - /** - * The uri of the segment. - */ - public final String uri; - /* package */ final long indexStart; /* package */ final long indexLength; @@ -79,27 +74,22 @@ public abstract class SegmentBase { * @param timescale The timescale in units per second. * @param presentationTimeOffset The presentation time offset. The value in seconds is the * division of this value and {@code timescale}. - * @param uri The uri of the segment. * @param indexStart The byte offset of the index data in the segment. * @param indexLength The length of the index data in bytes. */ public SingleSegmentBase(RangedUri initialization, long timescale, long presentationTimeOffset, - String uri, long indexStart, long indexLength) { + long indexStart, long indexLength) { super(initialization, timescale, presentationTimeOffset); - this.uri = uri; this.indexStart = indexStart; this.indexLength = indexLength; } - /** - * @param uri The uri of the segment. - */ public SingleSegmentBase(String uri) { - this(null, 1, 0, uri, 0, 0); + this(null, 1, 0, 0, 0); } public RangedUri getIndex() { - return indexLength <= 0 ? null : new RangedUri(uri, null, indexStart, indexLength); + return indexLength <= 0 ? null : new RangedUri(null, indexStart, indexLength); } } @@ -279,8 +269,6 @@ public abstract class SegmentBase { /* package */ final UrlTemplate initializationTemplate; /* package */ final UrlTemplate mediaTemplate; - private final String baseUrl; - /** * @param initialization A {@link RangedUri} corresponding to initialization data, if such data * exists. The value of this parameter is ignored if {@code initializationTemplate} is @@ -299,16 +287,14 @@ public abstract class SegmentBase { * such data exists. If non-null then the {@code initialization} parameter is ignored. If * null then {@code initialization} will be used. * @param mediaTemplate A template defining the location of each media segment. - * @param baseUrl A url to use as the base for relative urls generated by the templates. */ public SegmentTemplate(RangedUri initialization, long timescale, long presentationTimeOffset, int startNumber, long duration, List segmentTimeline, - UrlTemplate initializationTemplate, UrlTemplate mediaTemplate, String baseUrl) { + UrlTemplate initializationTemplate, UrlTemplate mediaTemplate) { super(initialization, timescale, presentationTimeOffset, startNumber, duration, segmentTimeline); this.initializationTemplate = initializationTemplate; this.mediaTemplate = mediaTemplate; - this.baseUrl = baseUrl; } @Override @@ -316,7 +302,7 @@ public abstract class SegmentBase { if (initializationTemplate != null) { String urlString = initializationTemplate.buildUri(representation.format.id, 0, representation.format.bitrate, 0); - return new RangedUri(baseUrl, urlString, 0, C.LENGTH_UNSET); + return new RangedUri(urlString, 0, C.LENGTH_UNSET); } else { return super.getInitialization(representation); } @@ -332,7 +318,7 @@ public abstract class SegmentBase { } String uriString = mediaTemplate.buildUri(representation.format.id, sequenceNumber, representation.format.bitrate, time); - return new RangedUri(baseUrl, uriString, 0, C.LENGTH_UNSET); + return new RangedUri(uriString, 0, C.LENGTH_UNSET); } @Override