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 3ca16f76c7f..216ecc14228 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 @@ protected RepresentationInfo parseRepresentation( drmSchemeType, drmSchemeDatas, inbandEventStreams, + essentialProperties, + supplementalProperties, Representation.REVISION_ID_DEFAULT); } @@ -843,7 +845,10 @@ protected Representation buildRepresentation( formatBuilder.build(), representationInfo.baseUrls, representationInfo.segmentBase, - inbandEventStreams); + inbandEventStreams, + representationInfo.essentialProperties, + representationInfo.supplementalProperties, + /* cacheKey= */ null); } // SegmentBase, SegmentList and SegmentTemplate parsing. @@ -1912,6 +1917,8 @@ protected static final class RepresentationInfo { 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 RepresentationInfo( @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 RepresentationInfo( 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 d582eca4872..043510096eb 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 static Representation newInstance( * @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 static Representation newInstance( 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 static Representation newInstance( 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 @@ private 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 @@ private 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 static SingleSegmentRepresentation newInstance( 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 static SingleSegmentRepresentation newInstance( * @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 SingleSegmentRepresentation( 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 static class MultiSegmentRepresentation extends 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 34d704ab3e3..4afe48ee38a 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 void resolveCacheKey_representationCacheKeyIsNull_resolvesRangedUriWithFi 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 void resolveCacheKey_representationCacheKeyDefined_usesRepresentationCach 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 8cc35ca1261..13e5fc7e01c 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.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 void parsePeriodAssetIdentifier() throws IOException { 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 00000000000..1ef8a7e7f17 --- /dev/null +++ b/libraries/test_data/src/test/assets/media/mpd/sample_mpd_essential_supplemental_properties @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + +