Parse BaseURL element including DVB attributes in DASH manifest

This change parses the entire BaseURL element including DVB extension attributes, stores it in an instance of new BaseUrl class and puts it in a list of base URLs of the resulting Representation. The base url handling itself is still the same, which means that only the first base url is taken into account, just as before this change.

PiperOrigin-RevId: 380609495
This commit is contained in:
bachinger 2021-06-21 18:27:51 +01:00 committed by Oliver Woodman
parent 663082161a
commit f609fecf9b
12 changed files with 233 additions and 56 deletions

View File

@ -140,12 +140,17 @@ public final class UriUtil {
}
}
/** Returns true if the URI is starting with a scheme component, false otherwise. */
public static boolean isAbsolute(@Nullable String uri) {
return uri != null && getUriIndices(uri)[SCHEME_COLON] != -1;
}
/**
* Removes query parameter from an Uri, if present.
* Removes query parameter from a URI, if present.
*
* @param uri The uri.
* @param uri The URI.
* @param queryParameterName The name of the query parameter.
* @return The uri without the query parameter.
* @return The URI without the query parameter.
*/
public static Uri removeQueryParameter(Uri uri, String queryParameterName) {
Uri.Builder builder = uri.buildUpon();

View File

@ -42,6 +42,7 @@ public final class UriUtilTest {
assertThat(resolve(base, "g/")).isEqualTo("http://a/b/c/g/");
assertThat(resolve(base, "/g")).isEqualTo("http://a/g");
assertThat(resolve(base, "//g")).isEqualTo("http://g");
assertThat(resolve(base, "//g:80")).isEqualTo("http://g:80");
assertThat(resolve(base, "?y")).isEqualTo("http://a/b/c/d;p?y");
assertThat(resolve(base, "g?y")).isEqualTo("http://a/b/c/g?y");
assertThat(resolve(base, "#s")).isEqualTo("http://a/b/c/d;p?q#s");
@ -134,4 +135,24 @@ public final class UriUtilTest {
uri = Uri.parse("http://uri?query=value");
assertThat(removeQueryParameter(uri, "foo").toString()).isEqualTo("http://uri?query=value");
}
@Test
public void isAbsolute_absoluteUri_returnsTrue() {
assertThat(UriUtil.isAbsolute("fo://bar")).isTrue();
}
@Test
public void isAbsolute_emptyString_returnsFalse() {
assertThat(UriUtil.isAbsolute("")).isFalse();
assertThat(UriUtil.isAbsolute(" ")).isFalse();
assertThat(UriUtil.isAbsolute(null)).isFalse();
}
@Test
public void isAbsolute_relativeUri_returnsFalse() {
assertThat(UriUtil.isAbsolute("//www.google.com")).isFalse();
assertThat(UriUtil.isAbsolute("//www.google.com:80")).isFalse();
assertThat(UriUtil.isAbsolute("/path/to/file")).isFalse();
assertThat(UriUtil.isAbsolute("path/to/file")).isFalse();
}
}

View File

@ -55,7 +55,7 @@ public final class DashUtil {
public static DataSpec buildDataSpec(
Representation representation, RangedUri requestUri, int flags) {
return new DataSpec.Builder()
.setUri(requestUri.resolveUri(representation.baseUrl))
.setUri(requestUri.resolveUri(representation.baseUrls.get(0).url))
.setPosition(requestUri.start)
.setLength(requestUri.length)
.setKey(representation.getCacheKey())
@ -171,15 +171,15 @@ public final class DashUtil {
boolean loadIndex)
throws IOException {
RangedUri initializationUri = Assertions.checkNotNull(representation.getInitializationUri());
RangedUri requestUri;
@Nullable RangedUri requestUri;
if (loadIndex) {
RangedUri indexUri = representation.getIndexUri();
@Nullable RangedUri indexUri = representation.getIndexUri();
if (indexUri == null) {
return;
}
// 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, representation.baseUrl);
requestUri = initializationUri.attemptMerge(indexUri, representation.baseUrls.get(0).url);
if (requestUri == null) {
loadInitializationData(dataSource, representation, chunkExtractor, initializationUri);
requestUri = indexUri;

View File

@ -541,14 +541,14 @@ public class DefaultDashChunkSource implements DashChunkSource {
Format trackFormat,
int trackSelectionReason,
Object trackSelectionData,
RangedUri initializationUri,
@Nullable RangedUri initializationUri,
RangedUri indexUri) {
Representation representation = representationHolder.representation;
RangedUri requestUri;
@Nullable 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 both at once.
requestUri = initializationUri.attemptMerge(indexUri, representation.baseUrl);
requestUri = initializationUri.attemptMerge(indexUri, representation.baseUrls.get(0).url);
if (requestUri == null) {
requestUri = initializationUri;
}
@ -579,7 +579,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
Representation representation = representationHolder.representation;
long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum);
RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum);
String baseUrl = representation.baseUrl;
String baseUrl = representation.baseUrls.get(0).url;
if (representationHolder.chunkExtractor == null) {
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum);
int flags =

View File

@ -0,0 +1,73 @@
/*
* Copyright 2021 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.exoplayer2.source.dash.manifest;
import androidx.annotation.Nullable;
import com.google.common.base.Objects;
/** A base URL, as defined by ISO 23009-1, 2nd edition, 5.6. and ETSI TS 103 285 V1.2.1, 10.8.2.1 */
public final class BaseUrl {
/** The default priority. */
public static final int DEFAULT_PRIORITY = 1;
/** The default weight. */
public static final int DEFAULT_WEIGHT = 1;
/** The URL. */
public final String url;
/** The service location. */
@Nullable public final String serviceLocation;
/** The priority. */
public final int priority;
/** The weight. */
public final int weight;
/**
* Creates an instance with {@link #DEFAULT_PRIORITY default priority}, {@link #DEFAULT_WEIGHT
* default weight} and using the URL as the service location.
*/
public BaseUrl(String url) {
this(url, /* serviceLocation= */ url, DEFAULT_PRIORITY, DEFAULT_WEIGHT);
}
/** Creates an instance. */
public BaseUrl(String url, @Nullable String serviceLocation, int priority, int weight) {
this.url = url;
this.serviceLocation = serviceLocation;
this.priority = priority;
this.weight = weight;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof BaseUrl)) {
return false;
}
BaseUrl baseUrl = (BaseUrl) o;
return priority == baseUrl.priority
&& weight == baseUrl.weight
&& Objects.equal(url, baseUrl.url)
&& Objects.equal(serviceLocation, baseUrl.serviceLocation);
}
@Override
public int hashCode() {
return Objects.hashCode(url, serviceLocation, priority, weight);
}
}

View File

@ -102,13 +102,13 @@ public class DashManifestParser extends DefaultHandler
throw new ParserException(
"inputStream does not contain a valid media presentation description");
}
return parseMediaPresentationDescription(xpp, uri.toString());
return parseMediaPresentationDescription(xpp, new BaseUrl(uri.toString()));
} catch (XmlPullParserException e) {
throw ParserException.createForMalformedManifest(/* message= */ null, /* cause= */ e);
}
}
protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, String baseUrl)
protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, BaseUrl baseUrl)
throws XmlPullParserException, IOException {
long availabilityStartTime = parseDateTime(xpp, "availabilityStartTime", C.TIME_UNSET);
long durationMs = parseDuration(xpp, "mediaPresentationDuration", C.TIME_UNSET);
@ -271,7 +271,7 @@ public class DashManifestParser extends DefaultHandler
protected Pair<Period, Long> parsePeriod(
XmlPullParser xpp,
String baseUrl,
BaseUrl baseUrl,
long defaultStartMs,
long baseUrlAvailabilityTimeOffsetUs,
long availabilityStartTimeMs,
@ -361,7 +361,7 @@ public class DashManifestParser extends DefaultHandler
protected AdaptationSet parseAdaptationSet(
XmlPullParser xpp,
String baseUrl,
BaseUrl baseUrl,
@Nullable SegmentBase segmentBase,
long periodDurationMs,
long baseUrlAvailabilityTimeOffsetUs,
@ -625,7 +625,7 @@ public class DashManifestParser extends DefaultHandler
protected RepresentationInfo parseRepresentation(
XmlPullParser xpp,
String baseUrl,
BaseUrl baseUrl,
@Nullable String adaptationSetMimeType,
@Nullable String adaptationSetCodecs,
int adaptationSetWidth,
@ -829,7 +829,7 @@ public class DashManifestParser extends DefaultHandler
return Representation.newInstance(
representationInfo.revisionId,
formatBuilder.build(),
representationInfo.baseUrl,
ImmutableList.of(representationInfo.baseUrl),
representationInfo.segmentBase,
inbandEventStreams);
}
@ -1360,9 +1360,25 @@ public class DashManifestParser extends DefaultHandler
* @throws IOException If an error occurs reading the element.
* @return The parsed and resolved URL.
*/
protected String parseBaseUrl(XmlPullParser xpp, String parentBaseUrl)
protected BaseUrl parseBaseUrl(XmlPullParser xpp, BaseUrl parentBaseUrl)
throws XmlPullParserException, IOException {
return UriUtil.resolve(parentBaseUrl, parseText(xpp, "BaseURL"));
@Nullable String priorityValue = xpp.getAttributeValue(null, "dvb:priority");
int priority =
priorityValue != null ? Integer.parseInt(priorityValue) : BaseUrl.DEFAULT_PRIORITY;
@Nullable String weightValue = xpp.getAttributeValue(null, "dvb:weight");
int weight = weightValue != null ? Integer.parseInt(weightValue) : BaseUrl.DEFAULT_WEIGHT;
@Nullable String serviceLocation = xpp.getAttributeValue(null, "serviceLocation");
String baseUrl = parseText(xpp, "BaseURL");
if (serviceLocation == null) {
serviceLocation = baseUrl;
}
if (!UriUtil.isAbsolute(baseUrl)) {
baseUrl = UriUtil.resolve(parentBaseUrl.url, baseUrl);
priority = parentBaseUrl.priority;
weight = parentBaseUrl.weight;
serviceLocation = parentBaseUrl.serviceLocation;
}
return new BaseUrl(baseUrl, serviceLocation, priority, weight);
}
/**
@ -1866,7 +1882,7 @@ public class DashManifestParser extends DefaultHandler
protected static final class RepresentationInfo {
public final Format format;
public final String baseUrl;
public final BaseUrl baseUrl;
public final SegmentBase segmentBase;
@Nullable public final String drmSchemeType;
public final ArrayList<SchemeData> drmSchemeDatas;
@ -1875,7 +1891,7 @@ public class DashManifestParser extends DefaultHandler
public RepresentationInfo(
Format format,
String baseUrl,
BaseUrl baseUrl,
SegmentBase segmentBase,
@Nullable String drmSchemeType,
ArrayList<SchemeData> drmSchemeDatas,

View File

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.source.dash.manifest;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@ -23,6 +25,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.MultiSegmentBase;
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List;
@ -41,8 +44,8 @@ public abstract class Representation {
public final long revisionId;
/** The format of the representation. */
public final Format format;
/** The base URL of the representation. */
public final String baseUrl;
/** The base URLs of the representation. */
public final ImmutableList<BaseUrl> baseUrls;
/** The offset of the presentation timestamps in the media stream relative to media time. */
public final long presentationTimeOffsetUs;
/** The in-band event streams in the representation. May be empty. */
@ -55,13 +58,13 @@ public abstract class Representation {
*
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param baseUrl The base URL.
* @param baseUrls The list of base URLs of the representation.
* @param segmentBase A segment base element for the representation.
* @return The constructed instance.
*/
public static Representation newInstance(
long revisionId, Format format, String baseUrl, SegmentBase segmentBase) {
return newInstance(revisionId, format, baseUrl, segmentBase, /* inbandEventStreams= */ null);
long revisionId, Format format, List<BaseUrl> baseUrls, SegmentBase segmentBase) {
return newInstance(revisionId, format, baseUrls, segmentBase, /* inbandEventStreams= */ null);
}
/**
@ -69,7 +72,7 @@ public abstract class Representation {
*
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param baseUrl The base URL.
* @param baseUrls The list of base URLs of the representation.
* @param segmentBase A segment base element for the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
* @return The constructed instance.
@ -77,11 +80,11 @@ public abstract class Representation {
public static Representation newInstance(
long revisionId,
Format format,
String baseUrl,
List<BaseUrl> baseUrls,
SegmentBase segmentBase,
@Nullable List<Descriptor> inbandEventStreams) {
return newInstance(
revisionId, format, baseUrl, segmentBase, inbandEventStreams, /* cacheKey= */ null);
revisionId, format, baseUrls, segmentBase, inbandEventStreams, /* cacheKey= */ null);
}
/**
@ -89,7 +92,7 @@ public abstract class Representation {
*
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param baseUrl The base URL of the representation.
* @param baseUrls The list of base URLs of the representation.
* @param segmentBase A segment base element for the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
* @param cacheKey An optional key to be returned from {@link #getCacheKey()}, or null. This
@ -99,7 +102,7 @@ public abstract class Representation {
public static Representation newInstance(
long revisionId,
Format format,
String baseUrl,
List<BaseUrl> baseUrls,
SegmentBase segmentBase,
@Nullable List<Descriptor> inbandEventStreams,
@Nullable String cacheKey) {
@ -107,14 +110,14 @@ public abstract class Representation {
return new SingleSegmentRepresentation(
revisionId,
format,
baseUrl,
baseUrls,
(SingleSegmentBase) segmentBase,
inbandEventStreams,
cacheKey,
C.LENGTH_UNSET);
} else if (segmentBase instanceof MultiSegmentBase) {
return new MultiSegmentRepresentation(
revisionId, format, baseUrl, (MultiSegmentBase) segmentBase, inbandEventStreams);
revisionId, format, baseUrls, (MultiSegmentBase) segmentBase, inbandEventStreams);
} else {
throw new IllegalArgumentException(
"segmentBase must be of type SingleSegmentBase or " + "MultiSegmentBase");
@ -124,12 +127,13 @@ public abstract class Representation {
private Representation(
long revisionId,
Format format,
String baseUrl,
List<BaseUrl> baseUrls,
SegmentBase segmentBase,
@Nullable List<Descriptor> inbandEventStreams) {
checkArgument(!baseUrls.isEmpty());
this.revisionId = revisionId;
this.format = format;
this.baseUrl = baseUrl;
this.baseUrls = ImmutableList.copyOf(baseUrls);
this.inbandEventStreams =
inbandEventStreams == null
? Collections.emptyList()
@ -167,7 +171,6 @@ public abstract class Representation {
/** The uri of the single segment. */
public final Uri uri;
/** The content length, or {@link C#LENGTH_UNSET} if unknown. */
public final long contentLength;
@ -202,14 +205,15 @@ public abstract class Representation {
new RangedUri(null, initializationStart, initializationEnd - initializationStart + 1);
SingleSegmentBase segmentBase =
new SingleSegmentBase(rangedUri, 1, 0, indexStart, indexEnd - indexStart + 1);
List<BaseUrl> baseUrls = ImmutableList.of(new BaseUrl(uri));
return new SingleSegmentRepresentation(
revisionId, format, uri, segmentBase, inbandEventStreams, cacheKey, contentLength);
revisionId, format, baseUrls, segmentBase, inbandEventStreams, cacheKey, contentLength);
}
/**
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param baseUrl The base URL of the representation.
* @param baseUrls The base urls of the representation.
* @param segmentBase The segment base underlying the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
* @param cacheKey An optional key to be returned from {@link #getCacheKey()}, or null.
@ -218,13 +222,13 @@ public abstract class Representation {
public SingleSegmentRepresentation(
long revisionId,
Format format,
String baseUrl,
List<BaseUrl> baseUrls,
SingleSegmentBase segmentBase,
@Nullable List<Descriptor> inbandEventStreams,
@Nullable String cacheKey,
long contentLength) {
super(revisionId, format, baseUrl, segmentBase, inbandEventStreams);
this.uri = Uri.parse(baseUrl);
super(revisionId, format, baseUrls, segmentBase, inbandEventStreams);
this.uri = Uri.parse(baseUrls.get(0).url);
this.indexUri = segmentBase.getIndex();
this.cacheKey = cacheKey;
this.contentLength = contentLength;
@ -264,17 +268,17 @@ public abstract class Representation {
*
* @param revisionId Identifies the revision of the content.
* @param format The format of the representation.
* @param baseUrl The base URL of the representation.
* @param baseUrls The base URLs of the representation.
* @param segmentBase The segment base underlying the representation.
* @param inbandEventStreams The in-band event streams in the representation. May be null.
*/
public MultiSegmentRepresentation(
long revisionId,
Format format,
String baseUrl,
List<BaseUrl> baseUrls,
MultiSegmentBase segmentBase,
@Nullable List<Descriptor> inbandEventStreams) {
super(revisionId, format, baseUrl, segmentBase, inbandEventStreams);
super(revisionId, format, baseUrls, segmentBase, inbandEventStreams);
this.segmentBase = segmentBase;
}

View File

@ -163,7 +163,7 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> {
throw new DownloadException("Unbounded segment index");
}
String baseUrl = representation.baseUrl;
String baseUrl = representation.baseUrls.get(0).url;
RangedUri initializationUri = representation.getInitializationUri();
if (initializationUri != null) {
addSegment(periodStartUs, baseUrl, initializationUri, out);

View File

@ -23,11 +23,13 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
import com.google.android.exoplayer2.source.dash.manifest.BaseUrl;
import com.google.android.exoplayer2.source.dash.manifest.Period;
import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
import com.google.android.exoplayer2.upstream.DummyDataSource;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
@ -88,7 +90,11 @@ public final class DashUtilTest {
.setSampleMimeType(MimeTypes.VIDEO_H264)
.setDrmInitData(drmInitData)
.build();
return Representation.newInstance(0, format, "", new SingleSegmentBase());
return Representation.newInstance(
/* revisionId= */ 0,
format,
/* baseUrls= */ ImmutableList.of(new BaseUrl("")),
new SingleSegmentBase());
}
private static DrmInitData newDrmInitData() {

View File

@ -105,10 +105,8 @@ public class DashManifestParserTest {
(Representation.MultiSegmentRepresentation) representation;
long firstSegmentIndex = multiSegmentRepresentation.getFirstSegmentNum();
RangedUri uri = multiSegmentRepresentation.getSegmentUrl(firstSegmentIndex);
assertThat(
uri.resolveUriString(representation.baseUrl)
.contains("redirector.googlevideo.com"))
.isTrue();
assertThat(uri.resolveUriString(representation.baseUrls.get(0).url))
.contains("redirector.googlevideo.com");
}
}
}
@ -572,6 +570,37 @@ public class DashManifestParserTest {
assertThat(getAvailabilityTimeOffsetUs(adaptationSets1.get(0))).isEqualTo(9_999_000);
}
@Test
public void baseUrl_absoluteBaseUrls_usesClosestBaseUrl() throws IOException {
DashManifestParser parser = new DashManifestParser();
DashManifest manifest =
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(),
SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_BASE_URL));
List<AdaptationSet> adaptationSets0 = manifest.getPeriod(0).adaptationSets;
assertThat(adaptationSets0.get(0).representations.get(0).baseUrls.get(0).serviceLocation)
.isEqualTo("period0");
assertThat(adaptationSets0.get(0).representations.get(0).baseUrls.get(0).priority).isEqualTo(2);
assertThat(adaptationSets0.get(0).representations.get(0).baseUrls.get(0).weight).isEqualTo(20);
assertThat(adaptationSets0.get(1).representations.get(0).baseUrls.get(0).serviceLocation)
.isEqualTo("adaptationSet1");
assertThat(adaptationSets0.get(1).representations.get(0).baseUrls.get(0).priority).isEqualTo(3);
assertThat(adaptationSets0.get(1).representations.get(0).baseUrls.get(0).weight).isEqualTo(30);
assertThat(adaptationSets0.get(2).representations.get(0).baseUrls.get(0).serviceLocation)
.isEqualTo("representation2");
assertThat(adaptationSets0.get(2).representations.get(0).baseUrls.get(0).priority).isEqualTo(4);
assertThat(adaptationSets0.get(2).representations.get(0).baseUrls.get(0).weight).isEqualTo(40);
assertThat(adaptationSets0.get(3).representations.get(0).baseUrls.get(0).serviceLocation)
.isEqualTo("http://video-foo.com/baseUrl/adaptationSet3");
assertThat(adaptationSets0.get(3).representations.get(0).baseUrls.get(0).priority).isEqualTo(1);
assertThat(adaptationSets0.get(3).representations.get(0).baseUrls.get(0).weight).isEqualTo(1);
assertThat(adaptationSets0.get(3).representations.get(0).baseUrls.get(0).url)
.isEqualTo("http://video-foo.com/baseUrl/representation3");
}
@Test
public void serviceDescriptionElement_allValuesSet() throws IOException {
DashManifestParser parser = new DashManifestParser();

View File

@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -231,7 +232,11 @@ public class DashManifestTest {
}
private static Representation newRepresentation() {
return Representation.newInstance(/* revisionId= */ 0, FORMAT, /* baseUrl= */ "", SEGMENT_BASE);
return Representation.newInstance(
/* revisionId= */ 0,
FORMAT,
/* baseUrls= */ ImmutableList.of(new BaseUrl("")),
SEGMENT_BASE);
}
private static DashManifest newDashManifest(

View File

@ -2,31 +2,49 @@
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:mpeg:DASH:schema:MPD:2011"
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"
xmlns:dvb="urn:dvb:dash:dash-extensions:2014-1"
profiles="urn:mpeg:dash:profile:isoff-main:2011"
type="dynamic"
availabilityStartTime="2016-10-14T17:00:17">
<BaseURL>http://video.com/baseUrl</BaseURL>
<Period start="PT0.000S">
<BaseURL availabilityTimeOffset="5">http://video.com/baseUrl</BaseURL>
<BaseURL
dvb:priority="2"
dvb:weight="20"
serviceLocation="period0"
availabilityTimeOffset="5">http://video.com/baseUrl/period</BaseURL>
<SegmentTemplate/>
<AdaptationSet>
<Representation/>
</AdaptationSet>
<AdaptationSet>
<BaseURL availabilityTimeOffset="4.321">http://video.com/baseUrl</BaseURL>
<BaseURL
dvb:priority="3"
dvb:weight="30"
serviceLocation="adaptationSet1"
availabilityTimeOffset="4.321">http://video.com/baseUrl/adaptationSet1</BaseURL>
<SegmentTemplate/>
<Representation/>
</AdaptationSet>
<AdaptationSet>
<SegmentTemplate/>
<Representation>
<BaseURL availabilityTimeOffset="9.876543210">http://video.com/baseUrl</BaseURL>
<BaseURL
dvb:priority="4"
dvb:weight="40"
serviceLocation="representation2"
availabilityTimeOffset="9.876543210">http://video.com/baseUrl/representation2</BaseURL>
<SegmentTemplate/>
</Representation>
</AdaptationSet>
<AdaptationSet>
<BaseURL availabilityTimeOffset="0.5">http://video.com/baseUrl</BaseURL>
<BaseURL availabilityTimeOffset="0.5">http://video-foo.com/baseUrl/adaptationSet3</BaseURL>
<Representation>
<BaseURL availabilityTimeOffset="INF">http://video.com/baseUrl</BaseURL>
<BaseURL
dvb:priority="5"
dvb:weight="50"
serviceLocation="representation3"
availabilityTimeOffset="INF">/baseUrl/representation3</BaseURL>
<SegmentTemplate/>
</Representation>
</AdaptationSet>