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 @@ + + + + + + + + + + + + + + + + + + +