From b48b618bcee045ba699eb7bd1d550579f7608fd1 Mon Sep 17 00:00:00 2001 From: bachinger Date: Tue, 22 Jun 2021 11:54:53 +0100 Subject: [PATCH] Parse multiple BaseURL elements After this change, multiple BaseURL elements are parsed, but the player still only uses the first BaseURL element appearing in the manifest and its corresponding availabilityTimeOffsetUs. PiperOrigin-RevId: 380775256 --- .../source/dash/manifest/BaseUrl.java | 4 +- .../dash/manifest/DashManifestParser.java | 65 ++++++++++++------- .../dash/manifest/DashManifestParserTest.java | 51 +++++++++++++++ .../media/mpd/sample_mpd_multiple_baseUrls | 32 +++++++++ 4 files changed, 125 insertions(+), 27 deletions(-) create mode 100644 testdata/src/test/assets/media/mpd/sample_mpd_multiple_baseUrls 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 index 0126213051..1792afd905 100644 --- 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 @@ -29,7 +29,7 @@ public final class BaseUrl { /** The URL. */ public final String url; /** The service location. */ - @Nullable public final String serviceLocation; + public final String serviceLocation; /** The priority. */ public final int priority; /** The weight. */ @@ -44,7 +44,7 @@ public final class BaseUrl { } /** Creates an instance. */ - public BaseUrl(String url, @Nullable String serviceLocation, int priority, int weight) { + public BaseUrl(String url, String serviceLocation, int priority, int weight) { this.url = url; this.serviceLocation = serviceLocation; this.priority = priority; 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 ba47b3c5a2..3db266f6df 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 @@ -43,6 +43,7 @@ import com.google.android.exoplayer2.util.XmlPullParserUtil; import com.google.common.base.Ascii; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -99,8 +100,9 @@ public class DashManifestParser extends DefaultHandler xpp.setInput(inputStream, null); int eventType = xpp.next(); if (eventType != XmlPullParser.START_TAG || !"MPD".equals(xpp.getName())) { - throw new ParserException( - "inputStream does not contain a valid media presentation description"); + throw ParserException.createForMalformedManifest( + "inputStream does not contain a valid media presentation description", + /* cause= */ null); } return parseMediaPresentationDescription(xpp, new BaseUrl(uri.toString())); } catch (XmlPullParserException e) { @@ -108,8 +110,8 @@ public class DashManifestParser extends DefaultHandler } } - protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, BaseUrl baseUrl) - throws XmlPullParserException, IOException { + protected DashManifest parseMediaPresentationDescription( + XmlPullParser xpp, BaseUrl documentBaseUrl) throws XmlPullParserException, IOException { long availabilityStartTime = parseDateTime(xpp, "availabilityStartTime", C.TIME_UNSET); long durationMs = parseDuration(xpp, "mediaPresentationDuration", C.TIME_UNSET); long minBufferTimeMs = parseDuration(xpp, "minBufferTime", C.TIME_UNSET); @@ -127,8 +129,10 @@ public class DashManifestParser extends DefaultHandler Uri location = null; ServiceDescriptionElement serviceDescription = null; long baseUrlAvailabilityTimeOffsetUs = dynamic ? 0 : C.TIME_UNSET; + ArrayList parentBaseUrls = Lists.newArrayList(documentBaseUrl); List periods = new ArrayList<>(); + ArrayList baseUrls = new ArrayList<>(); long nextPeriodStartMs = dynamic ? C.TIME_UNSET : 0; boolean seenEarlyAccessPeriod = false; boolean seenFirstBaseUrl = false; @@ -138,9 +142,9 @@ public class DashManifestParser extends DefaultHandler if (!seenFirstBaseUrl) { baseUrlAvailabilityTimeOffsetUs = parseAvailabilityTimeOffsetUs(xpp, baseUrlAvailabilityTimeOffsetUs); - baseUrl = parseBaseUrl(xpp, baseUrl); seenFirstBaseUrl = true; } + baseUrls.addAll(parseBaseUrl(xpp, parentBaseUrls)); } else if (XmlPullParserUtil.isStartTag(xpp, "ProgramInformation")) { programInformation = parseProgramInformation(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "UTCTiming")) { @@ -153,7 +157,7 @@ public class DashManifestParser extends DefaultHandler Pair periodWithDurationMs = parsePeriod( xpp, - baseUrl, + !baseUrls.isEmpty() ? baseUrls : parentBaseUrls, nextPeriodStartMs, baseUrlAvailabilityTimeOffsetUs, availabilityStartTime, @@ -271,7 +275,7 @@ public class DashManifestParser extends DefaultHandler protected Pair parsePeriod( XmlPullParser xpp, - BaseUrl baseUrl, + List parentBaseUrls, long defaultStartMs, long baseUrlAvailabilityTimeOffsetUs, long availabilityStartTimeMs, @@ -286,6 +290,7 @@ public class DashManifestParser extends DefaultHandler @Nullable Descriptor assetIdentifier = null; List adaptationSets = new ArrayList<>(); List eventStreams = new ArrayList<>(); + ArrayList baseUrls = new ArrayList<>(); boolean seenFirstBaseUrl = false; long segmentBaseAvailabilityTimeOffsetUs = C.TIME_UNSET; do { @@ -294,14 +299,14 @@ public class DashManifestParser extends DefaultHandler if (!seenFirstBaseUrl) { baseUrlAvailabilityTimeOffsetUs = parseAvailabilityTimeOffsetUs(xpp, baseUrlAvailabilityTimeOffsetUs); - baseUrl = parseBaseUrl(xpp, baseUrl); seenFirstBaseUrl = true; } + baseUrls.addAll(parseBaseUrl(xpp, parentBaseUrls)); } else if (XmlPullParserUtil.isStartTag(xpp, "AdaptationSet")) { adaptationSets.add( parseAdaptationSet( xpp, - baseUrl, + !baseUrls.isEmpty() ? baseUrls : parentBaseUrls, segmentBase, durationMs, baseUrlAvailabilityTimeOffsetUs, @@ -361,7 +366,7 @@ public class DashManifestParser extends DefaultHandler protected AdaptationSet parseAdaptationSet( XmlPullParser xpp, - BaseUrl baseUrl, + List parentBaseUrls, @Nullable SegmentBase segmentBase, long periodDurationMs, long baseUrlAvailabilityTimeOffsetUs, @@ -389,6 +394,7 @@ public class DashManifestParser extends DefaultHandler ArrayList essentialProperties = new ArrayList<>(); ArrayList supplementalProperties = new ArrayList<>(); List representationInfos = new ArrayList<>(); + ArrayList baseUrls = new ArrayList<>(); boolean seenFirstBaseUrl = false; do { @@ -397,9 +403,9 @@ public class DashManifestParser extends DefaultHandler if (!seenFirstBaseUrl) { baseUrlAvailabilityTimeOffsetUs = parseAvailabilityTimeOffsetUs(xpp, baseUrlAvailabilityTimeOffsetUs); - baseUrl = parseBaseUrl(xpp, baseUrl); seenFirstBaseUrl = true; } + baseUrls.addAll(parseBaseUrl(xpp, parentBaseUrls)); } else if (XmlPullParserUtil.isStartTag(xpp, "ContentProtection")) { Pair contentProtection = parseContentProtection(xpp); if (contentProtection.first != null) { @@ -425,7 +431,7 @@ public class DashManifestParser extends DefaultHandler RepresentationInfo representationInfo = parseRepresentation( xpp, - baseUrl, + !baseUrls.isEmpty() ? baseUrls : parentBaseUrls, mimeType, codecs, width, @@ -625,7 +631,7 @@ public class DashManifestParser extends DefaultHandler protected RepresentationInfo parseRepresentation( XmlPullParser xpp, - BaseUrl baseUrl, + List parentBaseUrls, @Nullable String adaptationSetMimeType, @Nullable String adaptationSetCodecs, int adaptationSetWidth, @@ -661,6 +667,7 @@ public class DashManifestParser extends DefaultHandler ArrayList essentialProperties = new ArrayList<>(adaptationSetEssentialProperties); ArrayList supplementalProperties = new ArrayList<>(adaptationSetSupplementalProperties); + ArrayList baseUrls = new ArrayList<>(); boolean seenFirstBaseUrl = false; do { @@ -669,9 +676,9 @@ public class DashManifestParser extends DefaultHandler if (!seenFirstBaseUrl) { baseUrlAvailabilityTimeOffsetUs = parseAvailabilityTimeOffsetUs(xpp, baseUrlAvailabilityTimeOffsetUs); - baseUrl = parseBaseUrl(xpp, baseUrl); seenFirstBaseUrl = true; } + baseUrls.addAll(parseBaseUrl(xpp, parentBaseUrls)); } else if (XmlPullParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) { audioChannels = parseAudioChannelConfiguration(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { @@ -740,7 +747,7 @@ public class DashManifestParser extends DefaultHandler return new RepresentationInfo( format, - baseUrl, + !baseUrls.isEmpty() ? baseUrls : parentBaseUrls, segmentBase, drmSchemeType, drmSchemeDatas, @@ -829,7 +836,7 @@ public class DashManifestParser extends DefaultHandler return Representation.newInstance( representationInfo.revisionId, formatBuilder.build(), - ImmutableList.of(representationInfo.baseUrl), + representationInfo.baseUrls, representationInfo.segmentBase, inbandEventStreams); } @@ -1355,12 +1362,12 @@ public class DashManifestParser extends DefaultHandler * Parses a BaseURL element. * * @param xpp The parser from which to read. - * @param parentBaseUrl A base URL for resolving the parsed URL. + * @param parentBaseUrls The parent base URLs for resolving the parsed URLs. * @throws XmlPullParserException If an error occurs parsing the element. * @throws IOException If an error occurs reading the element. - * @return The parsed and resolved URL. + * @return The list of parsed and resolved URLs. */ - protected BaseUrl parseBaseUrl(XmlPullParser xpp, BaseUrl parentBaseUrl) + protected List parseBaseUrl(XmlPullParser xpp, List parentBaseUrls) throws XmlPullParserException, IOException { @Nullable String priorityValue = xpp.getAttributeValue(null, "dvb:priority"); int priority = @@ -1372,13 +1379,21 @@ public class DashManifestParser extends DefaultHandler if (serviceLocation == null) { serviceLocation = baseUrl; } - if (!UriUtil.isAbsolute(baseUrl)) { - baseUrl = UriUtil.resolve(parentBaseUrl.url, baseUrl); + if (UriUtil.isAbsolute(baseUrl)) { + return Lists.newArrayList(new BaseUrl(baseUrl, serviceLocation, priority, weight)); + } + + List 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)); } - return new BaseUrl(baseUrl, serviceLocation, priority, weight); + return baseUrls; } /** @@ -1882,7 +1897,7 @@ public class DashManifestParser extends DefaultHandler protected static final class RepresentationInfo { public final Format format; - public final BaseUrl baseUrl; + public final ImmutableList baseUrls; public final SegmentBase segmentBase; @Nullable public final String drmSchemeType; public final ArrayList drmSchemeDatas; @@ -1891,14 +1906,14 @@ public class DashManifestParser extends DefaultHandler public RepresentationInfo( Format format, - BaseUrl baseUrl, + List baseUrls, SegmentBase segmentBase, @Nullable String drmSchemeType, ArrayList drmSchemeDatas, ArrayList inbandEventStreams, long revisionId) { this.format = format; - this.baseUrl = baseUrl; + this.baseUrls = ImmutableList.copyOf(baseUrls); this.segmentBase = segmentBase; this.drmSchemeType = drmSchemeType; this.drmSchemeDatas = drmSchemeDatas; 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 91198fdd95..8c3ea0c127 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 @@ -29,6 +29,7 @@ import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.io.StringReader; import java.util.Collections; @@ -55,6 +56,8 @@ public class DashManifestParserTest { private static final String SAMPLE_MPD_TRICK_PLAY = "media/mpd/sample_mpd_trick_play"; private static final String SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_BASE_URL = "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_AVAILABILITY_TIME_OFFSET_SEGMENT_TEMPLATE = "media/mpd/sample_mpd_availabilityTimeOffset_segmentTemplate"; private static final String SAMPLE_MPD_AVAILABILITY_TIME_OFFSET_SEGMENT_LIST = @@ -601,6 +604,54 @@ public class DashManifestParserTest { .isEqualTo("http://video-foo.com/baseUrl/representation3"); } + @Test + public void baseUrl_multipleBaseUrls_correctParsingAndUnfolding() throws IOException { + DashManifestParser parser = new DashManifestParser(); + DashManifest manifest = + parser.parse( + Uri.parse("https://example.com/test.mpd"), + TestUtil.getInputStream( + ApplicationProvider.getApplicationContext(), SAMPLE_MPD_MULTIPLE_BASE_URLS)); + + ImmutableList audioBaseUrls = + manifest.getPeriod(0).adaptationSets.get(0).representations.get(0).baseUrls; + assertThat(audioBaseUrls).hasSize(6); + assertThat(audioBaseUrls.get(0).url).endsWith("/baseUrl/a/media/audio"); + assertThat(audioBaseUrls.get(1).url).endsWith("/baseUrl/b/media/audio"); + assertThat(audioBaseUrls.get(2).url).endsWith("/baseUrl/c/media/audio"); + assertThat(audioBaseUrls.get(3).url).endsWith("/baseUrl/a/files/audio"); + assertThat(audioBaseUrls.get(4).url).endsWith("/baseUrl/b/files/audio"); + assertThat(audioBaseUrls.get(5).url).endsWith("/baseUrl/c/files/audio"); + assertThat(audioBaseUrls.get(0).serviceLocation).isEqualTo("a"); + assertThat(audioBaseUrls.get(1).serviceLocation).isEqualTo("b"); + assertThat(audioBaseUrls.get(2).serviceLocation).isEqualTo("c"); + assertThat(audioBaseUrls.get(3).serviceLocation).isEqualTo("a"); + assertThat(audioBaseUrls.get(4).serviceLocation).isEqualTo("b"); + assertThat(audioBaseUrls.get(5).serviceLocation).isEqualTo("c"); + ImmutableList videoBaseUrls = + manifest.getPeriod(0).adaptationSets.get(1).representations.get(0).baseUrls; + assertThat(videoBaseUrls).hasSize(7); + assertThat(videoBaseUrls.get(0).url).endsWith("/baseUrl/a/media/video"); + assertThat(videoBaseUrls.get(1).url).endsWith("/baseUrl/b/media/video"); + assertThat(videoBaseUrls.get(2).url).endsWith("/baseUrl/c/media/video"); + assertThat(videoBaseUrls.get(3).url).endsWith("/baseUrl/a/files/video"); + assertThat(videoBaseUrls.get(4).url).endsWith("/baseUrl/b/files/video"); + assertThat(videoBaseUrls.get(5).url).endsWith("/baseUrl/c/files/video"); + assertThat(videoBaseUrls.get(6).url).endsWith("/baseUrl/d/alternative/"); + assertThat(videoBaseUrls.get(0).serviceLocation).isEqualTo("a"); + assertThat(videoBaseUrls.get(1).serviceLocation).isEqualTo("b"); + assertThat(videoBaseUrls.get(2).serviceLocation).isEqualTo("c"); + assertThat(videoBaseUrls.get(3).serviceLocation).isEqualTo("a"); + assertThat(videoBaseUrls.get(4).serviceLocation).isEqualTo("b"); + assertThat(videoBaseUrls.get(5).serviceLocation).isEqualTo("c"); + assertThat(videoBaseUrls.get(6).serviceLocation).isEqualTo("d"); + ImmutableList textBaseUrls = + manifest.getPeriod(0).adaptationSets.get(2).representations.get(0).baseUrls; + assertThat(textBaseUrls).hasSize(1); + assertThat(textBaseUrls.get(0).url).endsWith("/baseUrl/e/text/"); + assertThat(textBaseUrls.get(0).serviceLocation).isEqualTo("e"); + } + @Test public void serviceDescriptionElement_allValuesSet() throws IOException { DashManifestParser parser = new DashManifestParser(); diff --git a/testdata/src/test/assets/media/mpd/sample_mpd_multiple_baseUrls b/testdata/src/test/assets/media/mpd/sample_mpd_multiple_baseUrls new file mode 100644 index 0000000000..228e64a03b --- /dev/null +++ b/testdata/src/test/assets/media/mpd/sample_mpd_multiple_baseUrls @@ -0,0 +1,32 @@ + + + http://video.com/baseUrl/a/ + http://video.com/baseUrl/b/ + http://video.com/baseUrl/c/ + + media/ + files/ + + audio + + + + + video + http://video.com/baseUrl/d/alternative/ + + + + + http://video.com/baseUrl/e/text/ + + + + +