From 1618e0ef8e8f90ccad147b3033c870654654f820 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 15 Nov 2021 14:25:17 +0000 Subject: [PATCH] Add parsed essential/supplemental properties to the Representation. We already parse essential and supplemental properties from the Representation, but don't add them to our Representation class so that they can be accessed by users. Issue: google/ExoPlayer#9579 PiperOrigin-RevId: 409961990 --- .../dash/manifest/DashManifestParser.java | 13 ++- .../dash/manifest/Representation.java | 92 +++++++++++++------ .../media3/exoplayer/dash/DashUtilTest.java | 4 + .../dash/manifest/DashManifestParserTest.java | 72 +++++++++++++++ ...mple_mpd_essential_supplemental_properties | 31 +++++++ 5 files changed, 184 insertions(+), 28 deletions(-) create mode 100644 libraries/test_data/src/test/assets/media/mpd/sample_mpd_essential_supplemental_properties diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/DashManifestParser.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/DashManifestParser.java index 3ca16f76c7..216ecc1422 100644 --- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/DashManifestParser.java +++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/DashManifestParser.java @@ -755,6 +755,8 @@ public class DashManifestParser extends DefaultHandler drmSchemeType, drmSchemeDatas, inbandEventStreams, + essentialProperties, + supplementalProperties, Representation.REVISION_ID_DEFAULT); } @@ -843,7 +845,10 @@ public class DashManifestParser extends DefaultHandler formatBuilder.build(), representationInfo.baseUrls, representationInfo.segmentBase, - inbandEventStreams); + inbandEventStreams, + representationInfo.essentialProperties, + representationInfo.supplementalProperties, + /* cacheKey= */ null); } // SegmentBase, SegmentList and SegmentTemplate parsing. @@ -1912,6 +1917,8 @@ public class DashManifestParser extends DefaultHandler public final ArrayList drmSchemeDatas; public final ArrayList inbandEventStreams; public final long revisionId; + public final List essentialProperties; + public final List supplementalProperties; public RepresentationInfo( Format format, @@ -1920,6 +1927,8 @@ public class DashManifestParser extends DefaultHandler @Nullable String drmSchemeType, ArrayList drmSchemeDatas, ArrayList inbandEventStreams, + List essentialProperties, + List supplementalProperties, long revisionId) { this.format = format; this.baseUrls = ImmutableList.copyOf(baseUrls); @@ -1927,6 +1936,8 @@ public class DashManifestParser extends DefaultHandler this.drmSchemeType = drmSchemeType; this.drmSchemeDatas = drmSchemeDatas; this.inbandEventStreams = inbandEventStreams; + this.essentialProperties = essentialProperties; + this.supplementalProperties = supplementalProperties; this.revisionId = revisionId; } } diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/Representation.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/Representation.java index d582eca487..043510096e 100644 --- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/Representation.java +++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/manifest/Representation.java @@ -52,6 +52,10 @@ public abstract class Representation { public final long presentationTimeOffsetUs; /** The in-band event streams in the representation. May be empty. */ public final List inbandEventStreams; + /** Essential properties in the representation. May be empty. */ + public final List essentialProperties; + /** Supplemental properties in the adaptation set. May be empty. */ + public final List supplementalProperties; private final RangedUri initializationUri; @@ -66,27 +70,15 @@ public abstract class Representation { */ public static Representation newInstance( long revisionId, Format format, List baseUrls, SegmentBase segmentBase) { - return newInstance(revisionId, format, baseUrls, segmentBase, /* inbandEventStreams= */ null); - } - - /** - * Constructs a new instance. - * - * @param revisionId Identifies the revision of the content. - * @param format The format 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. - * @return The constructed instance. - */ - public static Representation newInstance( - long revisionId, - Format format, - List baseUrls, - SegmentBase segmentBase, - @Nullable List inbandEventStreams) { return newInstance( - revisionId, format, baseUrls, segmentBase, inbandEventStreams, /* cacheKey= */ null); + revisionId, + format, + baseUrls, + segmentBase, + /* inbandEventStreams= */ null, + /* essentialProperties= */ ImmutableList.of(), + /* supplementalProperties= */ ImmutableList.of(), + /* cacheKey= */ null); } /** @@ -97,6 +89,8 @@ public abstract class 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 essentialProperties Essential properties in the representation. May be empty. + * @param supplementalProperties Supplemental properties in the representation. May be empty. * @param cacheKey An optional key to be returned from {@link #getCacheKey()}, or null. This * parameter is ignored if {@code segmentBase} consists of multiple segments. * @return The constructed instance. @@ -107,6 +101,8 @@ public abstract class Representation { List baseUrls, SegmentBase segmentBase, @Nullable List inbandEventStreams, + List essentialProperties, + List supplementalProperties, @Nullable String cacheKey) { if (segmentBase instanceof SingleSegmentBase) { return new SingleSegmentRepresentation( @@ -115,11 +111,19 @@ public abstract class Representation { baseUrls, (SingleSegmentBase) segmentBase, inbandEventStreams, + essentialProperties, + supplementalProperties, cacheKey, - C.LENGTH_UNSET); + /* contentLength= */ C.LENGTH_UNSET); } else if (segmentBase instanceof MultiSegmentBase) { return new MultiSegmentRepresentation( - revisionId, format, baseUrls, (MultiSegmentBase) segmentBase, inbandEventStreams); + revisionId, + format, + baseUrls, + (MultiSegmentBase) segmentBase, + inbandEventStreams, + essentialProperties, + supplementalProperties); } else { throw new IllegalArgumentException( "segmentBase must be of type SingleSegmentBase or " + "MultiSegmentBase"); @@ -131,7 +135,9 @@ public abstract class Representation { Format format, List baseUrls, SegmentBase segmentBase, - @Nullable List inbandEventStreams) { + @Nullable List inbandEventStreams, + List essentialProperties, + List supplementalProperties) { checkArgument(!baseUrls.isEmpty()); this.revisionId = revisionId; this.format = format; @@ -140,6 +146,8 @@ public abstract class Representation { inbandEventStreams == null ? Collections.emptyList() : Collections.unmodifiableList(inbandEventStreams); + this.essentialProperties = essentialProperties; + this.supplementalProperties = supplementalProperties; initializationUri = segmentBase.getInitialization(this); presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs(); } @@ -209,7 +217,15 @@ public abstract class Representation { new SingleSegmentBase(rangedUri, 1, 0, indexStart, indexEnd - indexStart + 1); List baseUrls = ImmutableList.of(new BaseUrl(uri)); return new SingleSegmentRepresentation( - revisionId, format, baseUrls, segmentBase, inbandEventStreams, cacheKey, contentLength); + revisionId, + format, + baseUrls, + segmentBase, + inbandEventStreams, + /* essentialProperties= */ ImmutableList.of(), + /* supplementalProperties= */ ImmutableList.of(), + cacheKey, + contentLength); } /** @@ -218,6 +234,8 @@ public abstract class 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 essentialProperties Essential properties in the representation. May be empty. + * @param supplementalProperties Supplemental properties in the representation. May be empty. * @param cacheKey An optional key to be returned from {@link #getCacheKey()}, or null. * @param contentLength The content length, or {@link C#LENGTH_UNSET} if unknown. */ @@ -227,9 +245,18 @@ public abstract class Representation { List baseUrls, SingleSegmentBase segmentBase, @Nullable List inbandEventStreams, + List essentialProperties, + List supplementalProperties, @Nullable String cacheKey, long contentLength) { - super(revisionId, format, baseUrls, segmentBase, inbandEventStreams); + super( + revisionId, + format, + baseUrls, + segmentBase, + inbandEventStreams, + essentialProperties, + supplementalProperties); this.uri = Uri.parse(baseUrls.get(0).url); this.indexUri = segmentBase.getIndex(); this.cacheKey = cacheKey; @@ -273,14 +300,25 @@ public abstract class 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 essentialProperties Essential properties in the representation. May be empty. + * @param supplementalProperties Supplemental properties in the representation. May be empty. */ public MultiSegmentRepresentation( long revisionId, Format format, List baseUrls, MultiSegmentBase segmentBase, - @Nullable List inbandEventStreams) { - super(revisionId, format, baseUrls, segmentBase, inbandEventStreams); + @Nullable List inbandEventStreams, + List essentialProperties, + List supplementalProperties) { + super( + revisionId, + format, + baseUrls, + segmentBase, + inbandEventStreams, + essentialProperties, + supplementalProperties); this.segmentBase = segmentBase; } diff --git a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashUtilTest.java b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashUtilTest.java index 34d704ab3e..4afe48ee38 100644 --- a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashUtilTest.java +++ b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashUtilTest.java @@ -79,6 +79,8 @@ public final class DashUtilTest { baseUrls, new SingleSegmentBase(), /* inbandEventStreams= */ null, + /* essentialProperties= */ ImmutableList.of(), + /* supplementalProperties= */ ImmutableList.of(), /* cacheKey= */ null, /* contentLength= */ 1); RangedUri rangedUri = new RangedUri("path/to/resource", /* start= */ 0, /* length= */ 1); @@ -99,6 +101,8 @@ public final class DashUtilTest { baseUrls, new SingleSegmentBase(), /* inbandEventStreams= */ null, + /* essentialProperties= */ ImmutableList.of(), + /* supplementalProperties= */ ImmutableList.of(), "cacheKey", /* contentLength= */ 1); RangedUri rangedUri = new RangedUri("path/to/resource", /* start= */ 0, /* length= */ 1); diff --git a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/manifest/DashManifestParserTest.java b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/manifest/DashManifestParserTest.java index 8cc35ca126..13e5fc7e01 100644 --- a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/manifest/DashManifestParserTest.java +++ b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/manifest/DashManifestParserTest.java @@ -23,6 +23,8 @@ import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Util; +import androidx.media3.exoplayer.dash.manifest.Representation.MultiSegmentRepresentation; +import androidx.media3.exoplayer.dash.manifest.Representation.SingleSegmentRepresentation; import androidx.media3.exoplayer.dash.manifest.SegmentBase.SegmentTimelineElement; import androidx.media3.extractor.metadata.emsg.EventMessage; import androidx.media3.test.utils.TestUtil; @@ -53,6 +55,8 @@ public class DashManifestParserTest { private static final String SAMPLE_MPD_ASSET_IDENTIFIER = "media/mpd/sample_mpd_asset_identifier"; private static final String SAMPLE_MPD_TEXT = "media/mpd/sample_mpd_text"; private static final String SAMPLE_MPD_TRICK_PLAY = "media/mpd/sample_mpd_trick_play"; + private static final String SAMPLE_MPD_ESSENTIAL_SUPPLEMENTAL_PROPERTIES = + "media/mpd/sample_mpd_essential_supplemental_properties"; 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 = @@ -504,6 +508,74 @@ public class DashManifestParserTest { assertThat(assetIdentifier.id).isEqualTo("uniqueId"); } + @Test + public void parseEssentialAndSupplementalProperties() throws IOException { + DashManifestParser parser = new DashManifestParser(); + DashManifest manifest = + parser.parse( + Uri.parse("https://example.com/test.mpd"), + TestUtil.getInputStream( + ApplicationProvider.getApplicationContext(), + SAMPLE_MPD_ESSENTIAL_SUPPLEMENTAL_PROPERTIES)); + + // Verify test setup. + assertThat(manifest.getPeriodCount()).isEqualTo(1); + assertThat(manifest.getPeriod(0).adaptationSets).hasSize(1); + AdaptationSet adaptationSet = manifest.getPeriod(0).adaptationSets.get(0); + assertThat(adaptationSet.representations).hasSize(2); + Representation representation0 = adaptationSet.representations.get(0); + Representation representation1 = adaptationSet.representations.get(1); + assertThat(representation0).isInstanceOf(SingleSegmentRepresentation.class); + assertThat(representation1).isInstanceOf(MultiSegmentRepresentation.class); + + // Verify parsed properties. + assertThat(adaptationSet.essentialProperties).hasSize(1); + assertThat(adaptationSet.essentialProperties.get(0).schemeIdUri) + .isEqualTo("urn:mpeg:dash:essential-scheme:2050"); + assertThat(adaptationSet.essentialProperties.get(0).value).isEqualTo("adaptationEssential"); + assertThat(adaptationSet.supplementalProperties).hasSize(1); + assertThat(adaptationSet.supplementalProperties.get(0).schemeIdUri) + .isEqualTo("urn:mpeg:dash:supplemental-scheme:2050"); + assertThat(adaptationSet.supplementalProperties.get(0).value) + .isEqualTo("adaptationSupplemental"); + + assertThat(representation0.essentialProperties).hasSize(2); + assertThat(representation0.essentialProperties.get(0).schemeIdUri) + .isEqualTo("urn:mpeg:dash:essential-scheme:2050"); + assertThat(representation0.essentialProperties.get(0).value).isEqualTo("adaptationEssential"); + assertThat(representation0.essentialProperties.get(1).schemeIdUri) + .isEqualTo("urn:mpeg:dash:essential-scheme:2050"); + assertThat(representation0.essentialProperties.get(1).value) + .isEqualTo("representationEssential"); + assertThat(representation0.supplementalProperties).hasSize(2); + assertThat(representation0.supplementalProperties.get(0).schemeIdUri) + .isEqualTo("urn:mpeg:dash:supplemental-scheme:2050"); + assertThat(representation0.supplementalProperties.get(0).value) + .isEqualTo("adaptationSupplemental"); + assertThat(representation0.supplementalProperties.get(1).schemeIdUri) + .isEqualTo("urn:mpeg:dash:supplemental-scheme:2050"); + assertThat(representation0.supplementalProperties.get(1).value) + .isEqualTo("representationSupplemental"); + + assertThat(representation1.essentialProperties).hasSize(2); + assertThat(representation0.essentialProperties.get(0).schemeIdUri) + .isEqualTo("urn:mpeg:dash:essential-scheme:2050"); + assertThat(representation0.essentialProperties.get(0).value).isEqualTo("adaptationEssential"); + assertThat(representation1.essentialProperties.get(1).schemeIdUri) + .isEqualTo("urn:mpeg:dash:essential-scheme:2050"); + assertThat(representation1.essentialProperties.get(1).value) + .isEqualTo("representationEssential"); + assertThat(representation1.supplementalProperties).hasSize(2); + assertThat(representation0.supplementalProperties.get(0).schemeIdUri) + .isEqualTo("urn:mpeg:dash:supplemental-scheme:2050"); + assertThat(representation0.supplementalProperties.get(0).value) + .isEqualTo("adaptationSupplemental"); + assertThat(representation1.supplementalProperties.get(1).schemeIdUri) + .isEqualTo("urn:mpeg:dash:supplemental-scheme:2050"); + assertThat(representation1.supplementalProperties.get(1).value) + .isEqualTo("representationSupplemental"); + } + @Test public void availabilityTimeOffset_staticManifest_setToTimeUnset() throws IOException { DashManifestParser parser = new DashManifestParser(); diff --git a/libraries/test_data/src/test/assets/media/mpd/sample_mpd_essential_supplemental_properties b/libraries/test_data/src/test/assets/media/mpd/sample_mpd_essential_supplemental_properties new file mode 100644 index 0000000000..1ef8a7e7f1 --- /dev/null +++ b/libraries/test_data/src/test/assets/media/mpd/sample_mpd_essential_supplemental_properties @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + +