From b052ea9bed269a61681387bf73a7eb7a126cc60f Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 7 Oct 2019 16:30:04 +0100 Subject: [PATCH] Parse Label elements for adaptation sets Issue: #6297 PiperOrigin-RevId: 273297284 --- RELEASENOTES.md | 7 +- .../com/google/android/exoplayer2/Format.java | 33 ++++++++++ .../dash/manifest/DashManifestParser.java | 64 +++++++++++++++---- .../dash/src/test/assets/sample_mpd_labels | 21 ++++++ .../dash/manifest/DashManifestParserTest.java | 40 ++++++++++++ 5 files changed, 151 insertions(+), 14 deletions(-) create mode 100644 library/dash/src/test/assets/sample_mpd_labels diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 224972984f8..77edbd468f7 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -24,8 +24,11 @@ * Remove the `DataSpec.FLAG_ALLOW_ICY_METADATA` flag. Instead, set the header `IcyHeaders.REQUEST_HEADER_ENABLE_METADATA_NAME` in the `DataSpec` `httpRequestHeaders`. -* DASH: Support negative @r values in segment timelines - ([#1787](https://github.com/google/ExoPlayer/issues/1787)). +* DASH: + * Support negative @r values in segment timelines + ([#1787](https://github.com/google/ExoPlayer/issues/1787)). + * Support `Label` elements + ([#6297](https://github.com/google/ExoPlayer/issues/6297)). * Add `allowedCapturePolicy` field to `AudioAttributes` wrapper to allow to opt-out of audio recording. * Add `DataSpec.httpRequestHeaders` to set HTTP request headers when connecting diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Format.java b/library/core/src/main/java/com/google/android/exoplayer2/Format.java index 37539845dc9..f58893f4ebd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Format.java @@ -1099,6 +1099,39 @@ public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) { exoMediaCryptoType); } + public Format copyWithLabel(@Nullable String label) { + return new Format( + id, + label, + selectionFlags, + roleFlags, + bitrate, + codecs, + metadata, + containerMimeType, + sampleMimeType, + maxInputSize, + initializationData, + drmInitData, + subsampleOffsetUs, + width, + height, + frameRate, + rotationDegrees, + pixelWidthHeightRatio, + projectionData, + stereoMode, + colorInfo, + channelCount, + sampleRate, + pcmEncoding, + encoderDelay, + encoderPadding, + language, + accessibilityChannel, + exoMediaCryptoType); + } + public Format copyWithContainerInfo( @Nullable String id, @Nullable String label, 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 6feb529958b..d225f65cea3 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 @@ -316,7 +316,6 @@ protected AdaptationSet parseAdaptationSet( parseRepresentation( xpp, baseUrl, - label, mimeType, codecs, width, @@ -343,6 +342,8 @@ protected AdaptationSet parseAdaptationSet( xpp, (SegmentTemplate) segmentBase, supplementalProperties, periodDurationMs); } else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) { inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream")); + } else if (XmlPullParserUtil.isStartTag(xpp, "Label")) { + label = parseLabel(xpp); } else if (XmlPullParserUtil.isStartTag(xpp)) { parseAdaptationSetChild(xpp); } @@ -353,7 +354,11 @@ protected AdaptationSet parseAdaptationSet( for (int i = 0; i < representationInfos.size(); i++) { representations.add( buildRepresentation( - representationInfos.get(i), drmSchemeType, drmSchemeDatas, inbandEventStreams)); + representationInfos.get(i), + label, + drmSchemeType, + drmSchemeDatas, + inbandEventStreams)); } return buildAdaptationSet(id, contentType, representations, accessibilityDescriptors, @@ -482,7 +487,6 @@ protected void parseAdaptationSetChild(XmlPullParser xpp) protected RepresentationInfo parseRepresentation( XmlPullParser xpp, String baseUrl, - @Nullable String label, @Nullable String adaptationSetMimeType, @Nullable String adaptationSetCodecs, int adaptationSetWidth, @@ -553,7 +557,6 @@ protected RepresentationInfo parseRepresentation( Format format = buildFormat( id, - label, mimeType, width, height, @@ -574,7 +577,6 @@ protected RepresentationInfo parseRepresentation( protected Format buildFormat( @Nullable String id, - @Nullable String label, @Nullable String containerMimeType, int width, int height, @@ -598,7 +600,7 @@ protected Format buildFormat( if (MimeTypes.isVideo(sampleMimeType)) { return Format.createVideoContainerFormat( id, - label, + /* label= */ null, containerMimeType, sampleMimeType, codecs, @@ -613,7 +615,7 @@ protected Format buildFormat( } else if (MimeTypes.isAudio(sampleMimeType)) { return Format.createAudioContainerFormat( id, - label, + /* label= */ null, containerMimeType, sampleMimeType, codecs, @@ -636,7 +638,7 @@ protected Format buildFormat( } return Format.createTextContainerFormat( id, - label, + /* label= */ null, containerMimeType, sampleMimeType, codecs, @@ -649,7 +651,7 @@ protected Format buildFormat( } return Format.createContainerFormat( id, - label, + /* label= */ null, containerMimeType, sampleMimeType, codecs, @@ -661,10 +663,14 @@ protected Format buildFormat( protected Representation buildRepresentation( RepresentationInfo representationInfo, + @Nullable String label, @Nullable String extraDrmSchemeType, ArrayList extraDrmSchemeDatas, ArrayList extraInbandEventStreams) { Format format = representationInfo.format; + if (label != null) { + format = format.copyWithLabel(label); + } String drmSchemeType = representationInfo.drmSchemeType != null ? representationInfo.drmSchemeType : extraDrmSchemeType; ArrayList drmSchemeDatas = representationInfo.drmSchemeDatas; @@ -1132,6 +1138,32 @@ protected ProgramInformation parseProgramInformation(XmlPullParser xpp) return new ProgramInformation(title, source, copyright, moreInformationURL, lang); } + /** + * Parses a Label element. + * + * @param xpp The parser from which to read. + * @throws XmlPullParserException If an error occurs parsing the element. + * @throws IOException If an error occurs reading the element. + * @return The parsed label. + */ + protected String parseLabel(XmlPullParser xpp) throws XmlPullParserException, IOException { + return parseText(xpp, "Label"); + } + + /** + * Parses a BaseURL element. + * + * @param xpp The parser from which to read. + * @param parentBaseUrl A base URL for resolving the parsed URL. + * @throws XmlPullParserException If an error occurs parsing the element. + * @throws IOException If an error occurs reading the element. + * @return The parsed and resolved URL. + */ + protected String parseBaseUrl(XmlPullParser xpp, String parentBaseUrl) + throws XmlPullParserException, IOException { + return UriUtil.resolve(parentBaseUrl, parseText(xpp, "BaseURL")); + } + // AudioChannelConfiguration parsing. protected int parseAudioChannelConfiguration(XmlPullParser xpp) @@ -1487,10 +1519,18 @@ protected static long parseDateTime(XmlPullParser xpp, String name, long default } } - protected static String parseBaseUrl(XmlPullParser xpp, String parentBaseUrl) + protected static String parseText(XmlPullParser xpp, String label) throws XmlPullParserException, IOException { - xpp.next(); - return UriUtil.resolve(parentBaseUrl, xpp.getText()); + String text = ""; + do { + xpp.next(); + if (xpp.getEventType() == XmlPullParser.TEXT) { + text = xpp.getText(); + } else { + maybeSkipTag(xpp); + } + } while (!XmlPullParserUtil.isEndTag(xpp, label)); + return text; } protected static int parseInt(XmlPullParser xpp, String name, int defaultValue) { diff --git a/library/dash/src/test/assets/sample_mpd_labels b/library/dash/src/test/assets/sample_mpd_labels new file mode 100644 index 00000000000..58eceb8c42e --- /dev/null +++ b/library/dash/src/test/assets/sample_mpd_labels @@ -0,0 +1,21 @@ + + + + + + + + + + + https://test.com/0 + + + + + https://test.com/1 + + + + + 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 d2a4f1cd6fb..390a18d2cc0 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 @@ -44,6 +44,7 @@ public class DashManifestParserTest { private static final String SAMPLE_MPD_UNKNOWN_MIME_TYPE = "sample_mpd_unknown_mime_type"; private static final String SAMPLE_MPD_SEGMENT_TEMPLATE = "sample_mpd_segment_template"; private static final String SAMPLE_MPD_EVENT_STREAM = "sample_mpd_event_stream"; + private static final String SAMPLE_MPD_LABELS = "sample_mpd_labels"; private static final String NEXT_TAG_NAME = "Next"; private static final String NEXT_TAG = "<" + NEXT_TAG_NAME + "/>"; @@ -176,6 +177,21 @@ public void parseMediaPresentationDescription_programInformation() throws IOExce assertThat(mpd.programInformation).isEqualTo(expectedProgramInformation); } + @Test + public void parseMediaPresentationDescription_labels() throws IOException { + DashManifestParser parser = new DashManifestParser(); + DashManifest manifest = + parser.parse( + Uri.parse("https://example.com/test.mpd"), + TestUtil.getInputStream( + ApplicationProvider.getApplicationContext(), SAMPLE_MPD_LABELS)); + + List adaptationSets = manifest.getPeriod(0).adaptationSets; + + assertThat(adaptationSets.get(0).representations.get(0).format.label).isEqualTo("audio label"); + assertThat(adaptationSets.get(1).representations.get(0).format.label).isEqualTo("video label"); + } + @Test public void parseSegmentTimeline_repeatCount() throws Exception { DashManifestParser parser = new DashManifestParser(); @@ -251,6 +267,30 @@ public void parseSegmentTimeline_timeOffsetsAndUndefinedRepeatCount() throws Exc assertNextTag(xpp); } + @Test + public void parseLabel() throws Exception { + DashManifestParser parser = new DashManifestParser(); + XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser(); + xpp.setInput(new StringReader("" + NEXT_TAG)); + xpp.next(); + + String label = parser.parseLabel(xpp); + assertThat(label).isEqualTo("test label"); + assertNextTag(xpp); + } + + @Test + public void parseLabel_noText() throws Exception { + DashManifestParser parser = new DashManifestParser(); + XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser(); + xpp.setInput(new StringReader("