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:
parent
663082161a
commit
f609fecf9b
@ -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.
|
* @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) {
|
public static Uri removeQueryParameter(Uri uri, String queryParameterName) {
|
||||||
Uri.Builder builder = uri.buildUpon();
|
Uri.Builder builder = uri.buildUpon();
|
||||||
|
@ -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/b/c/g/");
|
||||||
assertThat(resolve(base, "/g")).isEqualTo("http://a/g");
|
assertThat(resolve(base, "/g")).isEqualTo("http://a/g");
|
||||||
assertThat(resolve(base, "//g")).isEqualTo("http://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, "?y")).isEqualTo("http://a/b/c/d;p?y");
|
||||||
assertThat(resolve(base, "g?y")).isEqualTo("http://a/b/c/g?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");
|
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");
|
uri = Uri.parse("http://uri?query=value");
|
||||||
assertThat(removeQueryParameter(uri, "foo").toString()).isEqualTo("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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ public final class DashUtil {
|
|||||||
public static DataSpec buildDataSpec(
|
public static DataSpec buildDataSpec(
|
||||||
Representation representation, RangedUri requestUri, int flags) {
|
Representation representation, RangedUri requestUri, int flags) {
|
||||||
return new DataSpec.Builder()
|
return new DataSpec.Builder()
|
||||||
.setUri(requestUri.resolveUri(representation.baseUrl))
|
.setUri(requestUri.resolveUri(representation.baseUrls.get(0).url))
|
||||||
.setPosition(requestUri.start)
|
.setPosition(requestUri.start)
|
||||||
.setLength(requestUri.length)
|
.setLength(requestUri.length)
|
||||||
.setKey(representation.getCacheKey())
|
.setKey(representation.getCacheKey())
|
||||||
@ -171,15 +171,15 @@ public final class DashUtil {
|
|||||||
boolean loadIndex)
|
boolean loadIndex)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
RangedUri initializationUri = Assertions.checkNotNull(representation.getInitializationUri());
|
RangedUri initializationUri = Assertions.checkNotNull(representation.getInitializationUri());
|
||||||
RangedUri requestUri;
|
@Nullable RangedUri requestUri;
|
||||||
if (loadIndex) {
|
if (loadIndex) {
|
||||||
RangedUri indexUri = representation.getIndexUri();
|
@Nullable RangedUri indexUri = representation.getIndexUri();
|
||||||
if (indexUri == null) {
|
if (indexUri == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// It's common for initialization and index data to be stored adjacently. Attempt to merge
|
// It's common for initialization and index data to be stored adjacently. Attempt to merge
|
||||||
// the two requests together to request both at once.
|
// 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) {
|
if (requestUri == null) {
|
||||||
loadInitializationData(dataSource, representation, chunkExtractor, initializationUri);
|
loadInitializationData(dataSource, representation, chunkExtractor, initializationUri);
|
||||||
requestUri = indexUri;
|
requestUri = indexUri;
|
||||||
|
@ -541,14 +541,14 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
Format trackFormat,
|
Format trackFormat,
|
||||||
int trackSelectionReason,
|
int trackSelectionReason,
|
||||||
Object trackSelectionData,
|
Object trackSelectionData,
|
||||||
RangedUri initializationUri,
|
@Nullable RangedUri initializationUri,
|
||||||
RangedUri indexUri) {
|
RangedUri indexUri) {
|
||||||
Representation representation = representationHolder.representation;
|
Representation representation = representationHolder.representation;
|
||||||
RangedUri requestUri;
|
@Nullable RangedUri requestUri;
|
||||||
if (initializationUri != null) {
|
if (initializationUri != null) {
|
||||||
// It's common for initialization and index data to be stored adjacently. Attempt to merge
|
// It's common for initialization and index data to be stored adjacently. Attempt to merge
|
||||||
// the two requests together to request both at once.
|
// 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) {
|
if (requestUri == null) {
|
||||||
requestUri = initializationUri;
|
requestUri = initializationUri;
|
||||||
}
|
}
|
||||||
@ -579,7 +579,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
Representation representation = representationHolder.representation;
|
Representation representation = representationHolder.representation;
|
||||||
long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum);
|
long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum);
|
||||||
RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum);
|
RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum);
|
||||||
String baseUrl = representation.baseUrl;
|
String baseUrl = representation.baseUrls.get(0).url;
|
||||||
if (representationHolder.chunkExtractor == null) {
|
if (representationHolder.chunkExtractor == null) {
|
||||||
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum);
|
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum);
|
||||||
int flags =
|
int flags =
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -102,13 +102,13 @@ public class DashManifestParser extends DefaultHandler
|
|||||||
throw new ParserException(
|
throw new ParserException(
|
||||||
"inputStream does not contain a valid media presentation description");
|
"inputStream does not contain a valid media presentation description");
|
||||||
}
|
}
|
||||||
return parseMediaPresentationDescription(xpp, uri.toString());
|
return parseMediaPresentationDescription(xpp, new BaseUrl(uri.toString()));
|
||||||
} catch (XmlPullParserException e) {
|
} catch (XmlPullParserException e) {
|
||||||
throw ParserException.createForMalformedManifest(/* message= */ null, /* cause= */ 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 {
|
throws XmlPullParserException, IOException {
|
||||||
long availabilityStartTime = parseDateTime(xpp, "availabilityStartTime", C.TIME_UNSET);
|
long availabilityStartTime = parseDateTime(xpp, "availabilityStartTime", C.TIME_UNSET);
|
||||||
long durationMs = parseDuration(xpp, "mediaPresentationDuration", 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(
|
protected Pair<Period, Long> parsePeriod(
|
||||||
XmlPullParser xpp,
|
XmlPullParser xpp,
|
||||||
String baseUrl,
|
BaseUrl baseUrl,
|
||||||
long defaultStartMs,
|
long defaultStartMs,
|
||||||
long baseUrlAvailabilityTimeOffsetUs,
|
long baseUrlAvailabilityTimeOffsetUs,
|
||||||
long availabilityStartTimeMs,
|
long availabilityStartTimeMs,
|
||||||
@ -361,7 +361,7 @@ public class DashManifestParser extends DefaultHandler
|
|||||||
|
|
||||||
protected AdaptationSet parseAdaptationSet(
|
protected AdaptationSet parseAdaptationSet(
|
||||||
XmlPullParser xpp,
|
XmlPullParser xpp,
|
||||||
String baseUrl,
|
BaseUrl baseUrl,
|
||||||
@Nullable SegmentBase segmentBase,
|
@Nullable SegmentBase segmentBase,
|
||||||
long periodDurationMs,
|
long periodDurationMs,
|
||||||
long baseUrlAvailabilityTimeOffsetUs,
|
long baseUrlAvailabilityTimeOffsetUs,
|
||||||
@ -625,7 +625,7 @@ public class DashManifestParser extends DefaultHandler
|
|||||||
|
|
||||||
protected RepresentationInfo parseRepresentation(
|
protected RepresentationInfo parseRepresentation(
|
||||||
XmlPullParser xpp,
|
XmlPullParser xpp,
|
||||||
String baseUrl,
|
BaseUrl baseUrl,
|
||||||
@Nullable String adaptationSetMimeType,
|
@Nullable String adaptationSetMimeType,
|
||||||
@Nullable String adaptationSetCodecs,
|
@Nullable String adaptationSetCodecs,
|
||||||
int adaptationSetWidth,
|
int adaptationSetWidth,
|
||||||
@ -829,7 +829,7 @@ public class DashManifestParser extends DefaultHandler
|
|||||||
return Representation.newInstance(
|
return Representation.newInstance(
|
||||||
representationInfo.revisionId,
|
representationInfo.revisionId,
|
||||||
formatBuilder.build(),
|
formatBuilder.build(),
|
||||||
representationInfo.baseUrl,
|
ImmutableList.of(representationInfo.baseUrl),
|
||||||
representationInfo.segmentBase,
|
representationInfo.segmentBase,
|
||||||
inbandEventStreams);
|
inbandEventStreams);
|
||||||
}
|
}
|
||||||
@ -1360,9 +1360,25 @@ public class DashManifestParser extends DefaultHandler
|
|||||||
* @throws IOException If an error occurs reading the element.
|
* @throws IOException If an error occurs reading the element.
|
||||||
* @return The parsed and resolved URL.
|
* @return The parsed and resolved URL.
|
||||||
*/
|
*/
|
||||||
protected String parseBaseUrl(XmlPullParser xpp, String parentBaseUrl)
|
protected BaseUrl parseBaseUrl(XmlPullParser xpp, BaseUrl parentBaseUrl)
|
||||||
throws XmlPullParserException, IOException {
|
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 {
|
protected static final class RepresentationInfo {
|
||||||
|
|
||||||
public final Format format;
|
public final Format format;
|
||||||
public final String baseUrl;
|
public final BaseUrl baseUrl;
|
||||||
public final SegmentBase segmentBase;
|
public final SegmentBase segmentBase;
|
||||||
@Nullable public final String drmSchemeType;
|
@Nullable public final String drmSchemeType;
|
||||||
public final ArrayList<SchemeData> drmSchemeDatas;
|
public final ArrayList<SchemeData> drmSchemeDatas;
|
||||||
@ -1875,7 +1891,7 @@ public class DashManifestParser extends DefaultHandler
|
|||||||
|
|
||||||
public RepresentationInfo(
|
public RepresentationInfo(
|
||||||
Format format,
|
Format format,
|
||||||
String baseUrl,
|
BaseUrl baseUrl,
|
||||||
SegmentBase segmentBase,
|
SegmentBase segmentBase,
|
||||||
@Nullable String drmSchemeType,
|
@Nullable String drmSchemeType,
|
||||||
ArrayList<SchemeData> drmSchemeDatas,
|
ArrayList<SchemeData> drmSchemeDatas,
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.dash.manifest;
|
package com.google.android.exoplayer2.source.dash.manifest;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
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.DashSegmentIndex;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.MultiSegmentBase;
|
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.MultiSegmentBase;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
|
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -41,8 +44,8 @@ public abstract class Representation {
|
|||||||
public final long revisionId;
|
public final long revisionId;
|
||||||
/** The format of the representation. */
|
/** The format of the representation. */
|
||||||
public final Format format;
|
public final Format format;
|
||||||
/** The base URL of the representation. */
|
/** The base URLs of the representation. */
|
||||||
public final String baseUrl;
|
public final ImmutableList<BaseUrl> baseUrls;
|
||||||
/** The offset of the presentation timestamps in the media stream relative to media time. */
|
/** The offset of the presentation timestamps in the media stream relative to media time. */
|
||||||
public final long presentationTimeOffsetUs;
|
public final long presentationTimeOffsetUs;
|
||||||
/** The in-band event streams in the representation. May be empty. */
|
/** 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 revisionId Identifies the revision of the content.
|
||||||
* @param format The format of the representation.
|
* @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 segmentBase A segment base element for the representation.
|
||||||
* @return The constructed instance.
|
* @return The constructed instance.
|
||||||
*/
|
*/
|
||||||
public static Representation newInstance(
|
public static Representation newInstance(
|
||||||
long revisionId, Format format, String baseUrl, SegmentBase segmentBase) {
|
long revisionId, Format format, List<BaseUrl> baseUrls, SegmentBase segmentBase) {
|
||||||
return newInstance(revisionId, format, baseUrl, segmentBase, /* inbandEventStreams= */ null);
|
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 revisionId Identifies the revision of the content.
|
||||||
* @param format The format of the representation.
|
* @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 segmentBase A segment base element for the representation.
|
||||||
* @param inbandEventStreams The in-band event streams in the representation. May be null.
|
* @param inbandEventStreams The in-band event streams in the representation. May be null.
|
||||||
* @return The constructed instance.
|
* @return The constructed instance.
|
||||||
@ -77,11 +80,11 @@ public abstract class Representation {
|
|||||||
public static Representation newInstance(
|
public static Representation newInstance(
|
||||||
long revisionId,
|
long revisionId,
|
||||||
Format format,
|
Format format,
|
||||||
String baseUrl,
|
List<BaseUrl> baseUrls,
|
||||||
SegmentBase segmentBase,
|
SegmentBase segmentBase,
|
||||||
@Nullable List<Descriptor> inbandEventStreams) {
|
@Nullable List<Descriptor> inbandEventStreams) {
|
||||||
return newInstance(
|
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 revisionId Identifies the revision of the content.
|
||||||
* @param format The format of the representation.
|
* @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 segmentBase A segment base element for the representation.
|
||||||
* @param inbandEventStreams The in-band event streams in the representation. May be null.
|
* @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
|
* @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(
|
public static Representation newInstance(
|
||||||
long revisionId,
|
long revisionId,
|
||||||
Format format,
|
Format format,
|
||||||
String baseUrl,
|
List<BaseUrl> baseUrls,
|
||||||
SegmentBase segmentBase,
|
SegmentBase segmentBase,
|
||||||
@Nullable List<Descriptor> inbandEventStreams,
|
@Nullable List<Descriptor> inbandEventStreams,
|
||||||
@Nullable String cacheKey) {
|
@Nullable String cacheKey) {
|
||||||
@ -107,14 +110,14 @@ public abstract class Representation {
|
|||||||
return new SingleSegmentRepresentation(
|
return new SingleSegmentRepresentation(
|
||||||
revisionId,
|
revisionId,
|
||||||
format,
|
format,
|
||||||
baseUrl,
|
baseUrls,
|
||||||
(SingleSegmentBase) segmentBase,
|
(SingleSegmentBase) segmentBase,
|
||||||
inbandEventStreams,
|
inbandEventStreams,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
C.LENGTH_UNSET);
|
C.LENGTH_UNSET);
|
||||||
} else if (segmentBase instanceof MultiSegmentBase) {
|
} else if (segmentBase instanceof MultiSegmentBase) {
|
||||||
return new MultiSegmentRepresentation(
|
return new MultiSegmentRepresentation(
|
||||||
revisionId, format, baseUrl, (MultiSegmentBase) segmentBase, inbandEventStreams);
|
revisionId, format, baseUrls, (MultiSegmentBase) segmentBase, inbandEventStreams);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"segmentBase must be of type SingleSegmentBase or " + "MultiSegmentBase");
|
"segmentBase must be of type SingleSegmentBase or " + "MultiSegmentBase");
|
||||||
@ -124,12 +127,13 @@ public abstract class Representation {
|
|||||||
private Representation(
|
private Representation(
|
||||||
long revisionId,
|
long revisionId,
|
||||||
Format format,
|
Format format,
|
||||||
String baseUrl,
|
List<BaseUrl> baseUrls,
|
||||||
SegmentBase segmentBase,
|
SegmentBase segmentBase,
|
||||||
@Nullable List<Descriptor> inbandEventStreams) {
|
@Nullable List<Descriptor> inbandEventStreams) {
|
||||||
|
checkArgument(!baseUrls.isEmpty());
|
||||||
this.revisionId = revisionId;
|
this.revisionId = revisionId;
|
||||||
this.format = format;
|
this.format = format;
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrls = ImmutableList.copyOf(baseUrls);
|
||||||
this.inbandEventStreams =
|
this.inbandEventStreams =
|
||||||
inbandEventStreams == null
|
inbandEventStreams == null
|
||||||
? Collections.emptyList()
|
? Collections.emptyList()
|
||||||
@ -167,7 +171,6 @@ public abstract class Representation {
|
|||||||
|
|
||||||
/** The uri of the single segment. */
|
/** The uri of the single segment. */
|
||||||
public final Uri uri;
|
public final Uri uri;
|
||||||
|
|
||||||
/** The content length, or {@link C#LENGTH_UNSET} if unknown. */
|
/** The content length, or {@link C#LENGTH_UNSET} if unknown. */
|
||||||
public final long contentLength;
|
public final long contentLength;
|
||||||
|
|
||||||
@ -202,14 +205,15 @@ public abstract class Representation {
|
|||||||
new RangedUri(null, initializationStart, initializationEnd - initializationStart + 1);
|
new RangedUri(null, initializationStart, initializationEnd - initializationStart + 1);
|
||||||
SingleSegmentBase segmentBase =
|
SingleSegmentBase segmentBase =
|
||||||
new SingleSegmentBase(rangedUri, 1, 0, indexStart, indexEnd - indexStart + 1);
|
new SingleSegmentBase(rangedUri, 1, 0, indexStart, indexEnd - indexStart + 1);
|
||||||
|
List<BaseUrl> baseUrls = ImmutableList.of(new BaseUrl(uri));
|
||||||
return new SingleSegmentRepresentation(
|
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 revisionId Identifies the revision of the content.
|
||||||
* @param format The format of the representation.
|
* @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 segmentBase The segment base underlying the representation.
|
||||||
* @param inbandEventStreams The in-band event streams in the representation. May be null.
|
* @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.
|
* @param cacheKey An optional key to be returned from {@link #getCacheKey()}, or null.
|
||||||
@ -218,13 +222,13 @@ public abstract class Representation {
|
|||||||
public SingleSegmentRepresentation(
|
public SingleSegmentRepresentation(
|
||||||
long revisionId,
|
long revisionId,
|
||||||
Format format,
|
Format format,
|
||||||
String baseUrl,
|
List<BaseUrl> baseUrls,
|
||||||
SingleSegmentBase segmentBase,
|
SingleSegmentBase segmentBase,
|
||||||
@Nullable List<Descriptor> inbandEventStreams,
|
@Nullable List<Descriptor> inbandEventStreams,
|
||||||
@Nullable String cacheKey,
|
@Nullable String cacheKey,
|
||||||
long contentLength) {
|
long contentLength) {
|
||||||
super(revisionId, format, baseUrl, segmentBase, inbandEventStreams);
|
super(revisionId, format, baseUrls, segmentBase, inbandEventStreams);
|
||||||
this.uri = Uri.parse(baseUrl);
|
this.uri = Uri.parse(baseUrls.get(0).url);
|
||||||
this.indexUri = segmentBase.getIndex();
|
this.indexUri = segmentBase.getIndex();
|
||||||
this.cacheKey = cacheKey;
|
this.cacheKey = cacheKey;
|
||||||
this.contentLength = contentLength;
|
this.contentLength = contentLength;
|
||||||
@ -264,17 +268,17 @@ public abstract class Representation {
|
|||||||
*
|
*
|
||||||
* @param revisionId Identifies the revision of the content.
|
* @param revisionId Identifies the revision of the content.
|
||||||
* @param format The format of the representation.
|
* @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 segmentBase The segment base underlying the representation.
|
||||||
* @param inbandEventStreams The in-band event streams in the representation. May be null.
|
* @param inbandEventStreams The in-band event streams in the representation. May be null.
|
||||||
*/
|
*/
|
||||||
public MultiSegmentRepresentation(
|
public MultiSegmentRepresentation(
|
||||||
long revisionId,
|
long revisionId,
|
||||||
Format format,
|
Format format,
|
||||||
String baseUrl,
|
List<BaseUrl> baseUrls,
|
||||||
MultiSegmentBase segmentBase,
|
MultiSegmentBase segmentBase,
|
||||||
@Nullable List<Descriptor> inbandEventStreams) {
|
@Nullable List<Descriptor> inbandEventStreams) {
|
||||||
super(revisionId, format, baseUrl, segmentBase, inbandEventStreams);
|
super(revisionId, format, baseUrls, segmentBase, inbandEventStreams);
|
||||||
this.segmentBase = segmentBase;
|
this.segmentBase = segmentBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> {
|
|||||||
throw new DownloadException("Unbounded segment index");
|
throw new DownloadException("Unbounded segment index");
|
||||||
}
|
}
|
||||||
|
|
||||||
String baseUrl = representation.baseUrl;
|
String baseUrl = representation.baseUrls.get(0).url;
|
||||||
RangedUri initializationUri = representation.getInitializationUri();
|
RangedUri initializationUri = representation.getInitializationUri();
|
||||||
if (initializationUri != null) {
|
if (initializationUri != null) {
|
||||||
addSegment(periodStartUs, baseUrl, initializationUri, out);
|
addSegment(periodStartUs, baseUrl, initializationUri, out);
|
||||||
|
@ -23,11 +23,13 @@ import com.google.android.exoplayer2.Format;
|
|||||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
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.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.Period;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.Representation;
|
import com.google.android.exoplayer2.source.dash.manifest.Representation;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
|
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
|
||||||
import com.google.android.exoplayer2.upstream.DummyDataSource;
|
import com.google.android.exoplayer2.upstream.DummyDataSource;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -88,7 +90,11 @@ public final class DashUtilTest {
|
|||||||
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
||||||
.setDrmInitData(drmInitData)
|
.setDrmInitData(drmInitData)
|
||||||
.build();
|
.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() {
|
private static DrmInitData newDrmInitData() {
|
||||||
|
@ -105,10 +105,8 @@ public class DashManifestParserTest {
|
|||||||
(Representation.MultiSegmentRepresentation) representation;
|
(Representation.MultiSegmentRepresentation) representation;
|
||||||
long firstSegmentIndex = multiSegmentRepresentation.getFirstSegmentNum();
|
long firstSegmentIndex = multiSegmentRepresentation.getFirstSegmentNum();
|
||||||
RangedUri uri = multiSegmentRepresentation.getSegmentUrl(firstSegmentIndex);
|
RangedUri uri = multiSegmentRepresentation.getSegmentUrl(firstSegmentIndex);
|
||||||
assertThat(
|
assertThat(uri.resolveUriString(representation.baseUrls.get(0).url))
|
||||||
uri.resolveUriString(representation.baseUrl)
|
.contains("redirector.googlevideo.com");
|
||||||
.contains("redirector.googlevideo.com"))
|
|
||||||
.isTrue();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -572,6 +570,37 @@ public class DashManifestParserTest {
|
|||||||
assertThat(getAvailabilityTimeOffsetUs(adaptationSets1.get(0))).isEqualTo(9_999_000);
|
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
|
@Test
|
||||||
public void serviceDescriptionElement_allValuesSet() throws IOException {
|
public void serviceDescriptionElement_allValuesSet() throws IOException {
|
||||||
DashManifestParser parser = new DashManifestParser();
|
DashManifestParser parser = new DashManifestParser();
|
||||||
|
@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.offline.StreamKey;
|
import com.google.android.exoplayer2.offline.StreamKey;
|
||||||
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
|
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -231,7 +232,11 @@ public class DashManifestTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Representation newRepresentation() {
|
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(
|
private static DashManifest newDashManifest(
|
||||||
|
@ -2,31 +2,49 @@
|
|||||||
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns="urn:mpeg:DASH:schema:MPD:2011"
|
xmlns="urn:mpeg:DASH:schema:MPD:2011"
|
||||||
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"
|
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"
|
profiles="urn:mpeg:dash:profile:isoff-main:2011"
|
||||||
type="dynamic"
|
type="dynamic"
|
||||||
availabilityStartTime="2016-10-14T17:00:17">
|
availabilityStartTime="2016-10-14T17:00:17">
|
||||||
|
<BaseURL>http://video.com/baseUrl</BaseURL>
|
||||||
<Period start="PT0.000S">
|
<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/>
|
<SegmentTemplate/>
|
||||||
<AdaptationSet>
|
<AdaptationSet>
|
||||||
<Representation/>
|
<Representation/>
|
||||||
</AdaptationSet>
|
</AdaptationSet>
|
||||||
<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/>
|
<SegmentTemplate/>
|
||||||
<Representation/>
|
<Representation/>
|
||||||
</AdaptationSet>
|
</AdaptationSet>
|
||||||
<AdaptationSet>
|
<AdaptationSet>
|
||||||
<SegmentTemplate/>
|
<SegmentTemplate/>
|
||||||
<Representation>
|
<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/>
|
<SegmentTemplate/>
|
||||||
</Representation>
|
</Representation>
|
||||||
</AdaptationSet>
|
</AdaptationSet>
|
||||||
<AdaptationSet>
|
<AdaptationSet>
|
||||||
<BaseURL availabilityTimeOffset="0.5">http://video.com/baseUrl</BaseURL>
|
<BaseURL availabilityTimeOffset="0.5">http://video-foo.com/baseUrl/adaptationSet3</BaseURL>
|
||||||
<Representation>
|
<Representation>
|
||||||
<BaseURL availabilityTimeOffset="INF">http://video.com/baseUrl</BaseURL>
|
<BaseURL
|
||||||
|
dvb:priority="5"
|
||||||
|
dvb:weight="50"
|
||||||
|
serviceLocation="representation3"
|
||||||
|
availabilityTimeOffset="INF">/baseUrl/representation3</BaseURL>
|
||||||
<SegmentTemplate/>
|
<SegmentTemplate/>
|
||||||
</Representation>
|
</Representation>
|
||||||
</AdaptationSet>
|
</AdaptationSet>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user