move baseUrl from segments to representations: V2

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=138136090
This commit is contained in:
zhihuichen 2016-07-18 16:51:25 +01:00 committed by Oliver Woodman
parent 6c7ead5d0c
commit e2081f40fb
10 changed files with 204 additions and 139 deletions

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:yt="http://youtube.com/yt/2012/10/10" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" minBufferTime="PT1.500S" profiles="urn:mpeg:dash:profile:isoff-main:2011" type="dynamic" availabilityStartTime="2016-10-14T17:00:17" timeShiftBufferDepth="PT7200.000S" minimumUpdatePeriod="PT2.000S" yt:earliestMediaSequence="0" yt:mpdRequestTime="2016-10-14T18:29:17.082" yt:mpdResponseTime="2016-10-14T18:29:17.194">
<Period start="PT0.000S" yt:segmentIngestTime="2016-10-14T17:00:14.257">
<SegmentTemplate startNumber="0" timescale="1000" media="sq/$Number$">
<SegmentTimeline>
<S d="2002" t="6009" r="2"/>
<S d="1985"/>
<S d="2000"/>
</SegmentTimeline>
</SegmentTemplate>
<AdaptationSet id="0" mimeType="audio/mp4" subsegmentAlignment="true">
<Role schemeIdUri="urn:mpeg:DASH:role:2011" value="main"/>
<Representation id="140" codecs="mp4a.40.2" audioSamplingRate="48000" startWithSAP="1" bandwidth="144000">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>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/</BaseURL>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" mimeType="video/mp4" subsegmentAlignment="true">
<Role schemeIdUri="urn:mpeg:DASH:role:2011" value="main"/>
<Representation id="133" codecs="avc1.4d4015" width="426" height="240" startWithSAP="1" maxPlayoutRate="1" bandwidth="258000" frameRate="30">
<BaseURL>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/</BaseURL>
</Representation>
<Representation id="134" codecs="avc1.4d401e" width="640" height="360" startWithSAP="1" maxPlayoutRate="1" bandwidth="646000" frameRate="30">
<BaseURL>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/</BaseURL>
</Representation>
<Representation id="135" codecs="avc1.4d401f" width="854" height="480" startWithSAP="1" maxPlayoutRate="1" bandwidth="1171000" frameRate="30">
<BaseURL>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/</BaseURL>
</Representation>
<Representation id="160" codecs="avc1.42c00b" width="256" height="144" startWithSAP="1" maxPlayoutRate="1" bandwidth="124000" frameRate="30">
<BaseURL>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/</BaseURL>
</Representation>
<Representation id="136" codecs="avc1.4d401f" width="1280" height="720" startWithSAP="1" maxPlayoutRate="1" bandwidth="2326000" frameRate="30">
<BaseURL>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/</BaseURL>
</Representation>
</AdaptationSet>
</Period>
</MPD>

View File

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

View File

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

View File

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

View File

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

View File

@ -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,

View File

@ -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<SegmentTimelineElement> 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<SegmentTimelineElement> 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<SchemeData> drmSchemeDatas;
public RepresentationInfo(Format format, SegmentBase segmentBase,
public RepresentationInfo(Format format, String baseUrl, SegmentBase segmentBase,
ArrayList<SchemeData> drmSchemeDatas) {
this.format = format;
this.baseUrl = baseUrl;
this.segmentBase = segmentBase;
this.drmSchemeDatas = drmSchemeDatas;
}

View File

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

View File

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

View File

@ -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<SegmentTimelineElement> 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