diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a80e5102bd9..c74db8f847b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -90,6 +90,9 @@ * Metadata: * Ensure that timed metadata near the end of a period is not dropped ([#8710](https://github.com/google/ExoPlayer/issues/8710)). +* DASH: + * Parse `forced_subtitle` role from DASH manifests + ([#8781](https://github.com/google/ExoPlayer/issues/8781)). * MediaSession extension: Remove dependency to core module and rely on common only. The `TimelineQueueEditor` uses a new `MediaDescriptionConverter` for this purpose and does not rely on the `ConcatenatingMediaSource` anymore. 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 20300f78119..8c932f800d7 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 @@ -1391,15 +1391,31 @@ protected int parseAudioChannelConfiguration(XmlPullParser xpp) // Selection flag parsing. + @C.SelectionFlags protected int parseSelectionFlagsFromRoleDescriptors(List roleDescriptors) { + @C.SelectionFlags int result = 0; for (int i = 0; i < roleDescriptors.size(); i++) { Descriptor descriptor = roleDescriptors.get(i); - if (Ascii.equalsIgnoreCase("urn:mpeg:dash:role:2011", descriptor.schemeIdUri) - && "main".equals(descriptor.value)) { - return C.SELECTION_FLAG_DEFAULT; + if (Ascii.equalsIgnoreCase("urn:mpeg:dash:role:2011", descriptor.schemeIdUri)) { + result |= parseSelectionFlagsFromDashRoleScheme(descriptor.value); } } - return 0; + return result; + } + + @C.SelectionFlags + protected int parseSelectionFlagsFromDashRoleScheme(@Nullable String value) { + if (value == null) { + return 0; + } + switch (value) { + case "main": + return C.SELECTION_FLAG_DEFAULT; + case "forced_subtitle": + return C.SELECTION_FLAG_FORCED; + default: + return 0; + } } // Role and Accessibility parsing. @@ -1410,7 +1426,7 @@ protected int parseRoleFlagsFromRoleDescriptors(List roleDescriptors for (int i = 0; i < roleDescriptors.size(); i++) { Descriptor descriptor = roleDescriptors.get(i); if (Ascii.equalsIgnoreCase("urn:mpeg:dash:role:2011", descriptor.schemeIdUri)) { - result |= parseDashRoleSchemeValue(descriptor.value); + result |= parseRoleFlagsFromDashRoleScheme(descriptor.value); } } return result; @@ -1423,7 +1439,7 @@ protected int parseRoleFlagsFromAccessibilityDescriptors( for (int i = 0; i < accessibilityDescriptors.size(); i++) { Descriptor descriptor = accessibilityDescriptors.get(i); if (Ascii.equalsIgnoreCase("urn:mpeg:dash:role:2011", descriptor.schemeIdUri)) { - result |= parseDashRoleSchemeValue(descriptor.value); + result |= parseRoleFlagsFromDashRoleScheme(descriptor.value); } else if (Ascii.equalsIgnoreCase( "urn:tva:metadata:cs:AudioPurposeCS:2007", descriptor.schemeIdUri)) { result |= parseTvaAudioPurposeCsValue(descriptor.value); @@ -1446,7 +1462,7 @@ protected int parseRoleFlagsFromProperties(List accessibilityDescrip } @C.RoleFlags - protected int parseDashRoleSchemeValue(@Nullable String value) { + protected int parseRoleFlagsFromDashRoleScheme(@Nullable String value) { if (value == null) { return 0; } @@ -1465,6 +1481,7 @@ protected int parseDashRoleSchemeValue(@Nullable String value) { return C.ROLE_FLAG_EMERGENCY; case "caption": return C.ROLE_FLAG_CAPTION; + case "forced_subtitle": case "subtitle": return C.ROLE_FLAG_SUBTITLE; case "sign": 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 c82f2012678..24ae834f33e 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 @@ -220,18 +220,22 @@ public void parseMediaPresentationDescription_text() throws IOException { assertThat(format.containerMimeType).isEqualTo(MimeTypes.APPLICATION_RAWCC); assertThat(format.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_CEA608); assertThat(format.codecs).isEqualTo("cea608"); + assertThat(format.roleFlags).isEqualTo(C.ROLE_FLAG_SUBTITLE); assertThat(adaptationSets.get(0).type).isEqualTo(C.TRACK_TYPE_TEXT); format = adaptationSets.get(1).representations.get(0).format; assertThat(format.containerMimeType).isEqualTo(MimeTypes.APPLICATION_MP4); assertThat(format.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_TTML); assertThat(format.codecs).isEqualTo("stpp.ttml.im1t"); + assertThat(format.roleFlags).isEqualTo(C.ROLE_FLAG_SUBTITLE); + assertThat(format.selectionFlags).isEqualTo(C.SELECTION_FLAG_FORCED); assertThat(adaptationSets.get(1).type).isEqualTo(C.TRACK_TYPE_TEXT); format = adaptationSets.get(2).representations.get(0).format; assertThat(format.containerMimeType).isEqualTo(MimeTypes.APPLICATION_TTML); assertThat(format.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_TTML); assertThat(format.codecs).isNull(); + assertThat(format.roleFlags).isEqualTo(0); assertThat(adaptationSets.get(2).type).isEqualTo(C.TRACK_TYPE_TEXT); } diff --git a/testdata/src/test/assets/media/mpd/sample_mpd_text b/testdata/src/test/assets/media/mpd/sample_mpd_text index d3235d5ed00..f940527037a 100644 --- a/testdata/src/test/assets/media/mpd/sample_mpd_text +++ b/testdata/src/test/assets/media/mpd/sample_mpd_text @@ -7,11 +7,13 @@ + https://test.com/0 + https://test.com/0