From f609fecf9b1f23bf7a449e65a70b8acab5e1624c Mon Sep 17 00:00:00 2001 From: bachinger Date: Mon, 21 Jun 2021 18:27:51 +0100 Subject: [PATCH] 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 --- .../android/exoplayer2/util/UriUtil.java | 11 ++- .../android/exoplayer2/util/UriUtilTest.java | 21 ++++++ .../exoplayer2/source/dash/DashUtil.java | 8 +- .../source/dash/DefaultDashChunkSource.java | 8 +- .../source/dash/manifest/BaseUrl.java | 73 +++++++++++++++++++ .../dash/manifest/DashManifestParser.java | 36 ++++++--- .../source/dash/manifest/Representation.java | 50 +++++++------ .../source/dash/offline/DashDownloader.java | 2 +- .../exoplayer2/source/dash/DashUtilTest.java | 8 +- .../dash/manifest/DashManifestParserTest.java | 37 +++++++++- .../dash/manifest/DashManifestTest.java | 7 +- .../sample_mpd_availabilityTimeOffset_baseUrl | 28 +++++-- 12 files changed, 233 insertions(+), 56 deletions(-) create mode 100644 library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/BaseUrl.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/UriUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/UriUtil.java index c95f6c7a6a..40bd4de36b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/UriUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/UriUtil.java @@ -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(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/UriUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/UriUtilTest.java index 6d1c27c518..02741eb16f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/UriUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/UriUtilTest.java @@ -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(); + } } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java index 17ee580036..cf5be4f1be 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java @@ -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; diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index a61b951dbb..19dc560e7d 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -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 = diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/BaseUrl.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/BaseUrl.java new file mode 100644 index 0000000000..0126213051 --- /dev/null +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/BaseUrl.java @@ -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); + } +} diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index 958d9359b3..ba47b3c5a2 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -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 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 drmSchemeDatas; @@ -1875,7 +1891,7 @@ public class DashManifestParser extends DefaultHandler public RepresentationInfo( Format format, - String baseUrl, + BaseUrl baseUrl, SegmentBase segmentBase, @Nullable String drmSchemeType, ArrayList drmSchemeDatas, diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java index e45972f262..bb363b926a 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java @@ -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 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 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 baseUrls, SegmentBase segmentBase, @Nullable List 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 baseUrls, SegmentBase segmentBase, @Nullable List 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 baseUrls, SegmentBase segmentBase, @Nullable List 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 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 baseUrls, SingleSegmentBase segmentBase, @Nullable List 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 baseUrls, MultiSegmentBase segmentBase, @Nullable List inbandEventStreams) { - super(revisionId, format, baseUrl, segmentBase, inbandEventStreams); + super(revisionId, format, baseUrls, segmentBase, inbandEventStreams); this.segmentBase = segmentBase; } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java index 253bcbb8a4..2029b370de 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java @@ -163,7 +163,7 @@ public final class DashDownloader extends SegmentDownloader { 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); diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java index 7a30062ad0..1eb422d27c 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java @@ -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() { diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java index 9dc7c9ea5a..91198fdd95 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java @@ -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 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(); diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java index 74c1db4da5..0b3a3efbbb 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java @@ -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( diff --git a/testdata/src/test/assets/media/mpd/sample_mpd_availabilityTimeOffset_baseUrl b/testdata/src/test/assets/media/mpd/sample_mpd_availabilityTimeOffset_baseUrl index 188b2778a0..365416825c 100644 --- a/testdata/src/test/assets/media/mpd/sample_mpd_availabilityTimeOffset_baseUrl +++ b/testdata/src/test/assets/media/mpd/sample_mpd_availabilityTimeOffset_baseUrl @@ -2,31 +2,49 @@ + http://video.com/baseUrl - http://video.com/baseUrl + http://video.com/baseUrl/period - http://video.com/baseUrl + http://video.com/baseUrl/adaptationSet1 - http://video.com/baseUrl + http://video.com/baseUrl/representation2 - http://video.com/baseUrl + http://video-foo.com/baseUrl/adaptationSet3 - http://video.com/baseUrl + /baseUrl/representation3