Inherit parent properties for manifests with dvb profile only

Issue: google/ExoPlayer#9856
PiperOrigin-RevId: 421842579
This commit is contained in:
bachinger 2022-01-14 17:18:17 +00:00 committed by tonihei
parent f747fed874
commit c4a2579b43
12 changed files with 186 additions and 35 deletions

View file

@ -65,6 +65,10 @@
* Support the `forced-subtitle` track role
([#9727](https://github.com/google/ExoPlayer/issues/9727)).
* Stop interpreting the `main` track role as `C.SELECTION_FLAG_DEFAULT`.
* Fix bug when base URLs have been assigned the same service location and
priority in manifests that do not declare the dvb namespace. This
prevents the exclusion logic to exclude base URL when they actually
should be used as a fallback base URL.
* HLS:
* Use chunkless preparation by default to improve start up time. If your
renditions contain muxed closed-caption tracks that are *not* declared

View file

@ -66,7 +66,9 @@ public final class BaseUrlExclusionList {
public void exclude(BaseUrl baseUrlToExclude, long exclusionDurationMs) {
long excludeUntilMs = SystemClock.elapsedRealtime() + exclusionDurationMs;
addExclusion(baseUrlToExclude.serviceLocation, excludeUntilMs, excludedServiceLocations);
addExclusion(baseUrlToExclude.priority, excludeUntilMs, excludedPriorities);
if (baseUrlToExclude.priority != BaseUrl.PRIORITY_UNSET) {
addExclusion(baseUrlToExclude.priority, excludeUntilMs, excludedPriorities);
}
}
/**

View file

@ -21,10 +21,12 @@ 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 default priority. */
public static final int DEFAULT_DVB_PRIORITY = 1;
/** Constant representing an unset priority in a manifest that does not declare a DVB profile. */
public static final int PRIORITY_UNSET = Integer.MIN_VALUE;
/** The URL. */
public final String url;
@ -36,11 +38,11 @@ public final class BaseUrl {
public final int weight;
/**
* Creates an instance with {@link #DEFAULT_PRIORITY default priority}, {@link #DEFAULT_WEIGHT
* Creates an instance with {@link #PRIORITY_UNSET an unset 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);
this(url, /* serviceLocation= */ url, PRIORITY_UNSET, DEFAULT_WEIGHT);
}
/** Creates an instance. */

View file

@ -15,6 +15,10 @@
*/
package com.google.android.exoplayer2.source.dash.manifest;
import static com.google.android.exoplayer2.source.dash.manifest.BaseUrl.DEFAULT_DVB_PRIORITY;
import static com.google.android.exoplayer2.source.dash.manifest.BaseUrl.DEFAULT_WEIGHT;
import static com.google.android.exoplayer2.source.dash.manifest.BaseUrl.PRIORITY_UNSET;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Base64;
@ -103,14 +107,16 @@ public class DashManifestParser extends DefaultHandler
"inputStream does not contain a valid media presentation description",
/* cause= */ null);
}
return parseMediaPresentationDescription(xpp, new BaseUrl(uri.toString()));
return parseMediaPresentationDescription(xpp, uri);
} catch (XmlPullParserException e) {
throw ParserException.createForMalformedManifest(/* message= */ null, /* cause= */ e);
}
}
protected DashManifest parseMediaPresentationDescription(
XmlPullParser xpp, BaseUrl documentBaseUrl) throws XmlPullParserException, IOException {
protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, Uri documentBaseUri)
throws XmlPullParserException, IOException {
boolean dvbProfileDeclared =
isDvbProfileDeclared(parseProfiles(xpp, "profiles", new String[0]));
long availabilityStartTime = parseDateTime(xpp, "availabilityStartTime", C.TIME_UNSET);
long durationMs = parseDuration(xpp, "mediaPresentationDuration", C.TIME_UNSET);
long minBufferTimeMs = parseDuration(xpp, "minBufferTime", C.TIME_UNSET);
@ -128,6 +134,12 @@ public class DashManifestParser extends DefaultHandler
Uri location = null;
ServiceDescriptionElement serviceDescription = null;
long baseUrlAvailabilityTimeOffsetUs = dynamic ? 0 : C.TIME_UNSET;
BaseUrl documentBaseUrl =
new BaseUrl(
documentBaseUri.toString(),
/* serviceLocation= */ documentBaseUri.toString(),
dvbProfileDeclared ? DEFAULT_DVB_PRIORITY : PRIORITY_UNSET,
DEFAULT_WEIGHT);
ArrayList<BaseUrl> parentBaseUrls = Lists.newArrayList(documentBaseUrl);
List<Period> periods = new ArrayList<>();
@ -143,7 +155,7 @@ public class DashManifestParser extends DefaultHandler
parseAvailabilityTimeOffsetUs(xpp, baseUrlAvailabilityTimeOffsetUs);
seenFirstBaseUrl = true;
}
baseUrls.addAll(parseBaseUrl(xpp, parentBaseUrls));
baseUrls.addAll(parseBaseUrl(xpp, parentBaseUrls, dvbProfileDeclared));
} else if (XmlPullParserUtil.isStartTag(xpp, "ProgramInformation")) {
programInformation = parseProgramInformation(xpp);
} else if (XmlPullParserUtil.isStartTag(xpp, "UTCTiming")) {
@ -160,7 +172,8 @@ public class DashManifestParser extends DefaultHandler
nextPeriodStartMs,
baseUrlAvailabilityTimeOffsetUs,
availabilityStartTime,
timeShiftBufferDepthMs);
timeShiftBufferDepthMs,
dvbProfileDeclared);
Period period = periodWithDurationMs.first;
if (period.startMs == C.TIME_UNSET) {
if (dynamic) {
@ -280,7 +293,8 @@ public class DashManifestParser extends DefaultHandler
long defaultStartMs,
long baseUrlAvailabilityTimeOffsetUs,
long availabilityStartTimeMs,
long timeShiftBufferDepthMs)
long timeShiftBufferDepthMs,
boolean dvbProfileDeclared)
throws XmlPullParserException, IOException {
@Nullable String id = xpp.getAttributeValue(null, "id");
long startMs = parseDuration(xpp, "start", defaultStartMs);
@ -302,7 +316,7 @@ public class DashManifestParser extends DefaultHandler
parseAvailabilityTimeOffsetUs(xpp, baseUrlAvailabilityTimeOffsetUs);
seenFirstBaseUrl = true;
}
baseUrls.addAll(parseBaseUrl(xpp, parentBaseUrls));
baseUrls.addAll(parseBaseUrl(xpp, parentBaseUrls, dvbProfileDeclared));
} else if (XmlPullParserUtil.isStartTag(xpp, "AdaptationSet")) {
adaptationSets.add(
parseAdaptationSet(
@ -313,7 +327,8 @@ public class DashManifestParser extends DefaultHandler
baseUrlAvailabilityTimeOffsetUs,
segmentBaseAvailabilityTimeOffsetUs,
periodStartUnixTimeMs,
timeShiftBufferDepthMs));
timeShiftBufferDepthMs,
dvbProfileDeclared));
} else if (XmlPullParserUtil.isStartTag(xpp, "EventStream")) {
eventStreams.add(parseEventStream(xpp));
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) {
@ -373,7 +388,8 @@ public class DashManifestParser extends DefaultHandler
long baseUrlAvailabilityTimeOffsetUs,
long segmentBaseAvailabilityTimeOffsetUs,
long periodStartUnixTimeMs,
long timeShiftBufferDepthMs)
long timeShiftBufferDepthMs,
boolean dvbProfileDeclared)
throws XmlPullParserException, IOException {
int id = parseInt(xpp, "id", AdaptationSet.ID_UNSET);
@C.TrackType int contentType = parseContentType(xpp);
@ -406,7 +422,7 @@ public class DashManifestParser extends DefaultHandler
parseAvailabilityTimeOffsetUs(xpp, baseUrlAvailabilityTimeOffsetUs);
seenFirstBaseUrl = true;
}
baseUrls.addAll(parseBaseUrl(xpp, parentBaseUrls));
baseUrls.addAll(parseBaseUrl(xpp, parentBaseUrls, dvbProfileDeclared));
} else if (XmlPullParserUtil.isStartTag(xpp, "ContentProtection")) {
Pair<String, SchemeData> contentProtection = parseContentProtection(xpp);
if (contentProtection.first != null) {
@ -450,7 +466,8 @@ public class DashManifestParser extends DefaultHandler
periodDurationMs,
baseUrlAvailabilityTimeOffsetUs,
segmentBaseAvailabilityTimeOffsetUs,
timeShiftBufferDepthMs);
timeShiftBufferDepthMs,
dvbProfileDeclared);
contentType =
checkContentTypeConsistency(
contentType, MimeTypes.getTrackType(representationInfo.format.sampleMimeType));
@ -650,7 +667,8 @@ public class DashManifestParser extends DefaultHandler
long periodDurationMs,
long baseUrlAvailabilityTimeOffsetUs,
long segmentBaseAvailabilityTimeOffsetUs,
long timeShiftBufferDepthMs)
long timeShiftBufferDepthMs,
boolean dvbProfileDeclared)
throws XmlPullParserException, IOException {
String id = xpp.getAttributeValue(null, "id");
int bandwidth = parseInt(xpp, "bandwidth", Format.NO_VALUE);
@ -679,7 +697,7 @@ public class DashManifestParser extends DefaultHandler
parseAvailabilityTimeOffsetUs(xpp, baseUrlAvailabilityTimeOffsetUs);
seenFirstBaseUrl = true;
}
baseUrls.addAll(parseBaseUrl(xpp, parentBaseUrls));
baseUrls.addAll(parseBaseUrl(xpp, parentBaseUrls, dvbProfileDeclared));
} else if (XmlPullParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) {
audioChannels = parseAudioChannelConfiguration(xpp);
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) {
@ -1371,35 +1389,42 @@ public class DashManifestParser extends DefaultHandler
*
* @param xpp The parser from which to read.
* @param parentBaseUrls The parent base URLs for resolving the parsed URLs.
* @param dvbProfileDeclared Whether the dvb profile is declared.
* @throws XmlPullParserException If an error occurs parsing the element.
* @throws IOException If an error occurs reading the element.
* @return The list of parsed and resolved URLs.
*/
protected List<BaseUrl> parseBaseUrl(XmlPullParser xpp, List<BaseUrl> parentBaseUrls)
protected List<BaseUrl> parseBaseUrl(
XmlPullParser xpp, List<BaseUrl> parentBaseUrls, boolean dvbProfileDeclared)
throws XmlPullParserException, IOException {
@Nullable String priorityValue = xpp.getAttributeValue(null, "dvb:priority");
int priority =
priorityValue != null ? Integer.parseInt(priorityValue) : BaseUrl.DEFAULT_PRIORITY;
priorityValue != null
? Integer.parseInt(priorityValue)
: (dvbProfileDeclared ? DEFAULT_DVB_PRIORITY : PRIORITY_UNSET);
@Nullable String weightValue = xpp.getAttributeValue(null, "dvb:weight");
int weight = weightValue != null ? Integer.parseInt(weightValue) : BaseUrl.DEFAULT_WEIGHT;
int weight = weightValue != null ? Integer.parseInt(weightValue) : DEFAULT_WEIGHT;
@Nullable String serviceLocation = xpp.getAttributeValue(null, "serviceLocation");
String baseUrl = parseText(xpp, "BaseURL");
if (serviceLocation == null) {
serviceLocation = baseUrl;
}
if (UriUtil.isAbsolute(baseUrl)) {
if (serviceLocation == null) {
serviceLocation = baseUrl;
}
return Lists.newArrayList(new BaseUrl(baseUrl, serviceLocation, priority, weight));
}
List<BaseUrl> baseUrls = new ArrayList<>();
for (int i = 0; i < parentBaseUrls.size(); i++) {
BaseUrl parentBaseUrl = parentBaseUrls.get(i);
priority = parentBaseUrl.priority;
weight = parentBaseUrl.weight;
serviceLocation = parentBaseUrl.serviceLocation;
baseUrls.add(
new BaseUrl(
UriUtil.resolve(parentBaseUrl.url, baseUrl), serviceLocation, priority, weight));
String resolvedBaseUri = UriUtil.resolve(parentBaseUrl.url, baseUrl);
String resolvedServiceLocation = serviceLocation == null ? resolvedBaseUri : serviceLocation;
if (dvbProfileDeclared) {
// Inherit parent properties only if dvb profile is declared.
priority = parentBaseUrl.priority;
weight = parentBaseUrl.weight;
resolvedServiceLocation = parentBaseUrl.serviceLocation;
}
baseUrls.add(new BaseUrl(resolvedBaseUri, resolvedServiceLocation, priority, weight));
}
return baseUrls;
}
@ -1581,6 +1606,14 @@ public class DashManifestParser extends DefaultHandler
}
}
protected String[] parseProfiles(XmlPullParser xpp, String attributeName, String[] defaultValue) {
@Nullable String attributeValue = xpp.getAttributeValue(/* namespace= */ null, attributeName);
if (attributeValue == null) {
return defaultValue;
}
return attributeValue.split(",");
}
// Utility methods.
/**
@ -1907,6 +1940,15 @@ public class DashManifestParser extends DefaultHandler
return availabilityTimeOffsetUs;
}
private boolean isDvbProfileDeclared(String[] profiles) {
for (String profile : profiles) {
if (profile.startsWith("urn:dvb:dash:profile:dvb-dash:")) {
return true;
}
}
return false;
}
/** A parsed Representation element. */
protected static final class RepresentationInfo {

View file

@ -213,7 +213,7 @@ public abstract class Representation {
new RangedUri(null, initializationStart, initializationEnd - initializationStart + 1);
SingleSegmentBase segmentBase =
new SingleSegmentBase(rangedUri, 1, 0, indexStart, indexEnd - indexStart + 1);
List<BaseUrl> baseUrls = ImmutableList.of(new BaseUrl(uri));
ImmutableList<BaseUrl> baseUrls = ImmutableList.of(new BaseUrl(uri));
return new SingleSegmentRepresentation(
revisionId,
format,

View file

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.source.dash;
import static com.google.android.exoplayer2.source.dash.manifest.BaseUrl.DEFAULT_DVB_PRIORITY;
import static com.google.android.exoplayer2.source.dash.manifest.BaseUrl.DEFAULT_WEIGHT;
import static com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy.DEFAULT_LOCATION_EXCLUSION_MS;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
@ -173,6 +175,32 @@ public class BaseUrlExclusionListTest {
assertThat(baseUrlExclusionList.getPriorityCountAfterExclusion(baseUrls)).isEqualTo(2);
}
@Test
public void selectBaseUrl_priorityUnset_isNotExcluded() {
BaseUrlExclusionList baseUrlExclusionList = new BaseUrlExclusionList();
ImmutableList<BaseUrl> baseUrls =
ImmutableList.of(
new BaseUrl(
/* url= */ "a-1",
/* serviceLocation= */ "a",
BaseUrl.PRIORITY_UNSET,
/* weight= */ 1),
new BaseUrl(
/* url= */ "a-2",
/* serviceLocation= */ "a",
BaseUrl.PRIORITY_UNSET,
/* weight= */ 1),
new BaseUrl(
/* url= */ "b",
/* serviceLocation= */ "b",
BaseUrl.PRIORITY_UNSET,
/* weight= */ 1));
baseUrlExclusionList.exclude(baseUrls.get(0), 10_000);
assertThat(baseUrlExclusionList.selectBaseUrl(baseUrls).serviceLocation).isEqualTo("b");
}
@Test
public void selectBaseUrl_emptyBaseUrlList_selectionIsNull() {
BaseUrlExclusionList baseUrlExclusionList = new BaseUrlExclusionList();
@ -183,7 +211,8 @@ public class BaseUrlExclusionListTest {
@Test
public void reset_dropsAllExclusions() {
BaseUrlExclusionList baseUrlExclusionList = new BaseUrlExclusionList();
List<BaseUrl> baseUrls = ImmutableList.of(new BaseUrl("a"));
ImmutableList<BaseUrl> baseUrls =
ImmutableList.of(new BaseUrl("a", "a", DEFAULT_DVB_PRIORITY, DEFAULT_WEIGHT));
baseUrlExclusionList.exclude(baseUrls.get(0), 5000);
baseUrlExclusionList.reset();

View file

@ -61,6 +61,10 @@ public class DashManifestParserTest {
"media/mpd/sample_mpd_availabilityTimeOffset_baseUrl";
private static final String SAMPLE_MPD_MULTIPLE_BASE_URLS =
"media/mpd/sample_mpd_multiple_baseUrls";
private static final String SAMPLE_MPD_RELATIVE_BASE_URLS_DVB_PROFILE_NOT_DECLARED =
"media/mpd/sample_mpd_relative_baseUrls_dvb_profile_not_declared";
private static final String SAMPLE_MPD_RELATIVE_BASE_URLS_DVB_PROFILE_DECLARED =
"media/mpd/sample_mpd_relative_baseUrls_dvb_profile_declared";
private static final String SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_SEGMENT_TEMPLATE =
"media/mpd/sample_mpd_availabilityTimeOffset_segmentTemplate";
private static final String SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_SEGMENT_LIST =
@ -748,6 +752,41 @@ public class DashManifestParserTest {
assertThat(textBaseUrls.get(0).serviceLocation).isEqualTo("e");
}
@Test
public void baseUrl_relativeBaseUrlsNoDvbNamespace_hasDifferentPrioritiesAndServiceLocation()
throws IOException {
DashManifestParser parser = new DashManifestParser();
DashManifest manifest =
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(),
SAMPLE_MPD_RELATIVE_BASE_URLS_DVB_PROFILE_NOT_DECLARED));
ImmutableList<BaseUrl> baseUrls =
manifest.getPeriod(0).adaptationSets.get(0).representations.get(0).baseUrls;
assertThat(baseUrls.get(0).priority).isEqualTo(BaseUrl.PRIORITY_UNSET);
assertThat(baseUrls.get(1).priority).isEqualTo(BaseUrl.PRIORITY_UNSET);
assertThat(baseUrls.get(0).serviceLocation).isNotEqualTo(baseUrls.get(1).serviceLocation);
}
@Test
public void baseUrl_relativeBaseUrlsWithDvbNamespace_inheritsPrioritiesAndServiceLocation()
throws IOException {
DashManifestParser parser = new DashManifestParser();
DashManifest manifest =
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(),
SAMPLE_MPD_RELATIVE_BASE_URLS_DVB_PROFILE_DECLARED));
ImmutableList<BaseUrl> baseUrls =
manifest.getPeriod(0).adaptationSets.get(0).representations.get(0).baseUrls;
assertThat(baseUrls.get(0).priority).isEqualTo(baseUrls.get(1).priority);
assertThat(baseUrls.get(0).serviceLocation).isEqualTo(baseUrls.get(1).serviceLocation);
}
@Test
public void serviceDescriptionElement_allValuesSet() throws IOException {
DashManifestParser parser = new DashManifestParser();

View file

@ -3,7 +3,7 @@
xmlns="urn:mpeg:DASH:schema:MPD:2011"
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"
xmlns:dvb="urn:dvb:dash:dash-extensions:2014-1"
profiles="urn:mpeg:dash:profile:isoff-main:2011"
profiles="urn:mpeg:dash:profile:isoff-main:2011,urn:dvb:dash:profile:dvb-dash:2014"
type="dynamic"
availabilityStartTime="2016-10-14T17:00:17">
<BaseURL>http://video.com/baseUrl</BaseURL>

View file

@ -3,7 +3,7 @@
xmlns="urn:mpeg:DASH:schema:MPD:2011"
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"
xmlns:dvb="urn:dvb:dash:dash-extensions:2014-1"
profiles="urn:mpeg:dash:profile:isoff-main:2011"
profiles="urn:mpeg:dash:profile:isoff-main:2011,urn:dvb:dash:profile:dvb-dash:2014"
type="dynamic"
availabilityStartTime="2016-10-14T17:00:17">
<BaseURL serviceLocation="a" dvb:priority="1" dvb:weight="1">http://video.com/baseUrl/a/</BaseURL>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:mpeg:DASH:schema:MPD:2011"
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"
xmlns:dvb="urn:dvb:dash:dash-extensions:2014-1"
profiles="urn:mpeg:dash:profile:isoff-main:2011,urn:dvb:dash:profile:dvb-dash:2014"
type="dynamic"
availabilityStartTime="2016-10-14T17:00:17">
<Period start="PT0.000S">
<BaseURL>//anotherhost.com/some/url/1/</BaseURL>
<BaseURL>//anotherhost.com/some/url/2/</BaseURL>
<AdaptationSet contentType="audio">
<SegmentTemplate/>
<Representation/>
</AdaptationSet>
</Period>
</MPD>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:mpeg:DASH:schema:MPD:2011"
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"
profiles="urn:mpeg:dash:profile:isoff-main:2011"
type="dynamic"
availabilityStartTime="2016-10-14T17:00:17">
<Period start="PT0.000S">
<BaseURL>//anotherhost.com/some/url/1/</BaseURL>
<BaseURL>//anotherhost.com/some/url/2/</BaseURL>
<AdaptationSet contentType="audio">
<SegmentTemplate/>
<Representation/>
</AdaptationSet>
</Period>
</MPD>

View file

@ -4,7 +4,7 @@
xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011"
xmlns:dvb="urn:dvb:dash:dash-extensions:2014-1"
minBufferTime="PT1S"
profiles="urn:mpeg:dash:profile:isoff-main:2011"
profiles="urn:mpeg:dash:profile:isoff-main:2011,urn:dvb:dash:profile:dvb-dash:2014"
type="static"
mediaPresentationDuration="PT904S">
<BaseURL serviceLocation="a" dvb:priority="1" dvb:weight="1">http://video.com/baseUrl/a/</BaseURL>