From ba0028ca2c3dabb0b335618dbcec82a823b49cb5 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 15 Apr 2020 15:47:10 +0100 Subject: [PATCH] Parse trick-play role flags from DASH manifests Issue: #6054 PiperOrigin-RevId: 306641689 --- .../source/dash/manifest/AdaptationSet.java | 29 +++++------ .../source/dash/manifest/DashManifest.java | 11 +++-- .../dash/manifest/DashManifestParser.java | 49 +++++++++++++++++-- .../source/dash/DashMediaPeriodTest.java | 1 + .../exoplayer2/source/dash/DashUtilTest.java | 9 +++- .../dash/manifest/DashManifestParserTest.java | 45 ++++++++++++++++- .../dash/manifest/DashManifestTest.java | 8 ++- .../src/test/assets/mpd/sample_mpd_trick_play | 32 ++++++++++++ 8 files changed, 158 insertions(+), 26 deletions(-) create mode 100644 testdata/src/test/assets/mpd/sample_mpd_trick_play diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java index d962374745..b0689eeb11 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java @@ -50,9 +50,10 @@ public class AdaptationSet { */ public final List accessibilityDescriptors; - /** - * Supplemental properties in the adaptation set. - */ + /** Essential properties in the adaptation set. */ + public final List essentialProperties; + + /** Supplemental properties in the adaptation set. */ public final List supplementalProperties; /** @@ -62,21 +63,21 @@ public class AdaptationSet { * {@code TRACK_TYPE_*} constants. * @param representations {@link Representation}s in the adaptation set. * @param accessibilityDescriptors Accessibility descriptors in the adaptation set. + * @param essentialProperties Essential properties in the adaptation set. * @param supplementalProperties Supplemental properties in the adaptation set. */ - public AdaptationSet(int id, int type, List representations, - List accessibilityDescriptors, List supplementalProperties) { + public AdaptationSet( + int id, + int type, + List representations, + List accessibilityDescriptors, + List essentialProperties, + List supplementalProperties) { this.id = id; this.type = type; this.representations = Collections.unmodifiableList(representations); - this.accessibilityDescriptors = - accessibilityDescriptors == null - ? Collections.emptyList() - : Collections.unmodifiableList(accessibilityDescriptors); - this.supplementalProperties = - supplementalProperties == null - ? Collections.emptyList() - : Collections.unmodifiableList(supplementalProperties); + this.accessibilityDescriptors = Collections.unmodifiableList(accessibilityDescriptors); + this.essentialProperties = Collections.unmodifiableList(essentialProperties); + this.supplementalProperties = Collections.unmodifiableList(supplementalProperties); } - } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java index 2d8909f8b4..c21af45d15 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java @@ -224,9 +224,14 @@ public class DashManifest implements FilterableManifest { key = keys.poll(); } while (key.periodIndex == periodIndex && key.groupIndex == adaptationSetIndex); - copyAdaptationSets.add(new AdaptationSet(adaptationSet.id, adaptationSet.type, - copyRepresentations, adaptationSet.accessibilityDescriptors, - adaptationSet.supplementalProperties)); + copyAdaptationSets.add( + new AdaptationSet( + adaptationSet.id, + adaptationSet.type, + copyRepresentations, + adaptationSet.accessibilityDescriptors, + adaptationSet.essentialProperties, + adaptationSet.supplementalProperties)); } while(key.periodIndex == periodIndex); // Add back the last key which doesn't belong to the period being processed keys.addFirst(key); 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 1ceeb31c83..23f264e64b 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 @@ -289,6 +289,7 @@ public class DashManifestParser extends DefaultHandler ArrayList inbandEventStreams = new ArrayList<>(); ArrayList accessibilityDescriptors = new ArrayList<>(); ArrayList roleDescriptors = new ArrayList<>(); + ArrayList essentialProperties = new ArrayList<>(); ArrayList supplementalProperties = new ArrayList<>(); List representationInfos = new ArrayList<>(); @@ -317,6 +318,8 @@ public class DashManifestParser extends DefaultHandler audioChannels = parseAudioChannelConfiguration(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "Accessibility")) { accessibilityDescriptors.add(parseDescriptor(xpp, "Accessibility")); + } else if (XmlPullParserUtil.isStartTag(xpp, "EssentialProperty")) { + essentialProperties.add(parseDescriptor(xpp, "EssentialProperty")); } else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) { supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty")); } else if (XmlPullParserUtil.isStartTag(xpp, "Representation")) { @@ -334,6 +337,7 @@ public class DashManifestParser extends DefaultHandler language, roleDescriptors, accessibilityDescriptors, + essentialProperties, supplementalProperties, segmentBase, periodDurationMs); @@ -370,14 +374,28 @@ public class DashManifestParser extends DefaultHandler inbandEventStreams)); } - return buildAdaptationSet(id, contentType, representations, accessibilityDescriptors, + return buildAdaptationSet( + id, + contentType, + representations, + accessibilityDescriptors, + essentialProperties, supplementalProperties); } - protected AdaptationSet buildAdaptationSet(int id, int contentType, - List representations, List accessibilityDescriptors, + protected AdaptationSet buildAdaptationSet( + int id, + int contentType, + List representations, + List accessibilityDescriptors, + List essentialProperties, List supplementalProperties) { - return new AdaptationSet(id, contentType, representations, accessibilityDescriptors, + return new AdaptationSet( + id, + contentType, + representations, + accessibilityDescriptors, + essentialProperties, supplementalProperties); } @@ -492,6 +510,7 @@ public class DashManifestParser extends DefaultHandler @Nullable String adaptationSetLanguage, List adaptationSetRoleDescriptors, List adaptationSetAccessibilityDescriptors, + List adaptationSetEssentialProperties, List adaptationSetSupplementalProperties, @Nullable SegmentBase segmentBase, long periodDurationMs) @@ -509,7 +528,9 @@ public class DashManifestParser extends DefaultHandler String drmSchemeType = null; ArrayList drmSchemeDatas = new ArrayList<>(); ArrayList inbandEventStreams = new ArrayList<>(); - ArrayList supplementalProperties = new ArrayList<>(); + ArrayList essentialProperties = new ArrayList<>(adaptationSetEssentialProperties); + ArrayList supplementalProperties = + new ArrayList<>(adaptationSetSupplementalProperties); boolean seenFirstBaseUrl = false; do { @@ -542,6 +563,8 @@ public class DashManifestParser extends DefaultHandler } } else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) { inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream")); + } else if (XmlPullParserUtil.isStartTag(xpp, "EssentialProperty")) { + essentialProperties.add(parseDescriptor(xpp, "EssentialProperty")); } else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) { supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty")); } else { @@ -563,6 +586,7 @@ public class DashManifestParser extends DefaultHandler adaptationSetRoleDescriptors, adaptationSetAccessibilityDescriptors, codecs, + essentialProperties, supplementalProperties); segmentBase = segmentBase != null ? segmentBase : new SingleSegmentBase(); @@ -583,6 +607,7 @@ public class DashManifestParser extends DefaultHandler List roleDescriptors, List accessibilityDescriptors, @Nullable String codecs, + List essentialProperties, List supplementalProperties) { @Nullable String sampleMimeType = getSampleMimeType(containerMimeType, codecs); if (MimeTypes.AUDIO_E_AC3.equals(sampleMimeType)) { @@ -591,6 +616,8 @@ public class DashManifestParser extends DefaultHandler @C.SelectionFlags int selectionFlags = parseSelectionFlagsFromRoleDescriptors(roleDescriptors); @C.RoleFlags int roleFlags = parseRoleFlagsFromRoleDescriptors(roleDescriptors); roleFlags |= parseRoleFlagsFromAccessibilityDescriptors(accessibilityDescriptors); + roleFlags |= parseRoleFlagsFromProperties(essentialProperties); + roleFlags |= parseRoleFlagsFromProperties(supplementalProperties); Format.Builder formatBuilder = new Format.Builder() @@ -1185,6 +1212,18 @@ public class DashManifestParser extends DefaultHandler return result; } + @C.RoleFlags + protected int parseRoleFlagsFromProperties(List accessibilityDescriptors) { + @C.RoleFlags int result = 0; + for (int i = 0; i < accessibilityDescriptors.size(); i++) { + Descriptor descriptor = accessibilityDescriptors.get(i); + if ("http://dashif.org/guidelines/trickmode".equalsIgnoreCase(descriptor.schemeIdUri)) { + result |= C.ROLE_FLAG_TRICK_PLAY; + } + } + return result; + } + @C.RoleFlags protected int parseDashRoleSchemeValue(@Nullable String value) { if (value == null) { diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaPeriodTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaPeriodTest.java index 92aa49d3f1..e9e5f3030c 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaPeriodTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaPeriodTest.java @@ -165,6 +165,7 @@ public final class DashMediaPeriodTest { trackType, Arrays.asList(representations), /* accessibilityDescriptors= */ Collections.emptyList(), + /* essentialProperties= */ Collections.emptyList(), descriptor == null ? Collections.emptyList() : Collections.singletonList(descriptor)); } diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java index 28f15b2048..3176b06865 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java @@ -29,6 +29,7 @@ import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegm import com.google.android.exoplayer2.upstream.DummyDataSource; import com.google.android.exoplayer2.util.MimeTypes; import java.util.Arrays; +import java.util.Collections; import org.junit.Test; import org.junit.runner.RunWith; @@ -69,7 +70,13 @@ public final class DashUtilTest { } private static AdaptationSet newAdaptationSet(Representation... representations) { - return new AdaptationSet(0, C.TRACK_TYPE_VIDEO, Arrays.asList(representations), null, null); + return new AdaptationSet( + /* id= */ 0, + C.TRACK_TYPE_VIDEO, + Arrays.asList(representations), + /* accessibilityDescriptors= */ Collections.emptyList(), + /* essentialProperties= */ Collections.emptyList(), + /* supplementalProperties= */ Collections.emptyList()); } private static Representation newRepresentation(DrmInitData drmInitData) { 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 37b3046a54..47087472ae 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 @@ -49,6 +49,7 @@ public class DashManifestParserTest { private static final String SAMPLE_MPD_LABELS = "mpd/sample_mpd_labels"; private static final String SAMPLE_MPD_ASSET_IDENTIFIER = "mpd/sample_mpd_asset_identifier"; private static final String SAMPLE_MPD_TEXT = "mpd/sample_mpd_text"; + private static final String SAMPLE_MPD_TRICK_PLAY = "mpd/sample_mpd_trick_play"; private static final String NEXT_TAG_NAME = "Next"; private static final String NEXT_TAG = "<" + NEXT_TAG_NAME + "/>"; @@ -173,7 +174,7 @@ public class DashManifestParserTest { DashManifestParser parser = new DashManifestParser(); DashManifest manifest = parser.parse( - Uri.parse("Https://example.com/test.mpd"), + Uri.parse("https://example.com/test.mpd"), TestUtil.getInputStream(ApplicationProvider.getApplicationContext(), SAMPLE_MPD)); ProgramInformation expectedProgramInformation = new ProgramInformation( @@ -201,7 +202,7 @@ public class DashManifestParserTest { DashManifestParser parser = new DashManifestParser(); DashManifest manifest = parser.parse( - Uri.parse("Https://example.com/test.mpd"), + Uri.parse("https://example.com/test.mpd"), TestUtil.getInputStream(ApplicationProvider.getApplicationContext(), SAMPLE_MPD_TEXT)); List adaptationSets = manifest.getPeriod(0).adaptationSets; @@ -225,6 +226,46 @@ public class DashManifestParserTest { assertThat(adaptationSets.get(2).type).isEqualTo(C.TRACK_TYPE_TEXT); } + @Test + public void parseMediaPresentationDescription_trickPlay() throws IOException { + DashManifestParser parser = new DashManifestParser(); + DashManifest manifest = + parser.parse( + Uri.parse("https://example.com/test.mpd"), + TestUtil.getInputStream( + ApplicationProvider.getApplicationContext(), SAMPLE_MPD_TRICK_PLAY)); + + List adaptationSets = manifest.getPeriod(0).adaptationSets; + + AdaptationSet adaptationSet = adaptationSets.get(0); + assertThat(adaptationSet.essentialProperties).isEmpty(); + assertThat(adaptationSet.supplementalProperties).isEmpty(); + assertThat(adaptationSet.representations.get(0).format.roleFlags).isEqualTo(0); + + adaptationSet = adaptationSets.get(1); + assertThat(adaptationSet.essentialProperties).isEmpty(); + assertThat(adaptationSet.supplementalProperties).isEmpty(); + assertThat(adaptationSet.representations.get(0).format.roleFlags).isEqualTo(0); + + adaptationSet = adaptationSets.get(2); + assertThat(adaptationSet.essentialProperties).hasSize(1); + assertThat(adaptationSet.essentialProperties.get(0).schemeIdUri) + .isEqualTo("http://dashif.org/guidelines/trickmode"); + assertThat(adaptationSet.essentialProperties.get(0).value).isEqualTo("0"); + assertThat(adaptationSet.supplementalProperties).isEmpty(); + assertThat(adaptationSet.representations.get(0).format.roleFlags) + .isEqualTo(C.ROLE_FLAG_TRICK_PLAY); + + adaptationSet = adaptationSets.get(3); + assertThat(adaptationSet.essentialProperties).isEmpty(); + assertThat(adaptationSet.supplementalProperties).hasSize(1); + assertThat(adaptationSet.supplementalProperties.get(0).schemeIdUri) + .isEqualTo("http://dashif.org/guidelines/trickmode"); + assertThat(adaptationSet.supplementalProperties.get(0).value).isEqualTo("1"); + assertThat(adaptationSet.representations.get(0).format.roleFlags) + .isEqualTo(C.ROLE_FLAG_TRICK_PLAY); + } + @Test public void parseSegmentTimeline_repeatCount() throws Exception { DashManifestParser parser = new DashManifestParser(); diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java index b063c54489..b260bf2cee 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java @@ -239,6 +239,12 @@ public class DashManifestTest { } private static AdaptationSet newAdaptationSet(int seed, Representation... representations) { - return new AdaptationSet(++seed, ++seed, Arrays.asList(representations), null, null); + return new AdaptationSet( + ++seed, + ++seed, + Arrays.asList(representations), + /* accessibilityDescriptors= */ Collections.emptyList(), + /* essentialProperties= */ Collections.emptyList(), + /* supplementalProperties= */ Collections.emptyList()); } } diff --git a/testdata/src/test/assets/mpd/sample_mpd_trick_play b/testdata/src/test/assets/mpd/sample_mpd_trick_play new file mode 100644 index 0000000000..b35c906b5f --- /dev/null +++ b/testdata/src/test/assets/mpd/sample_mpd_trick_play @@ -0,0 +1,32 @@ + + + + + + + + + + + https://test.com/0 + + + + + https://test.com/0 + + + + + + https://test.com/0 + + + + + + https://test.com/0 + + + +