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