From d37f05238a2d8b45ea2e5f4ac026084b917f30df Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 13 Mar 2025 10:50:13 -0700 Subject: [PATCH] Check language/role flags before merging adaptation sets The spec technically allows to mark adaptation sets with the switching property if they allow seamless switching with non-overlapping segments. This is typically only used for compatible content (e.g. different codecs), but the spec allows it to be used for other content as well (e.g. different languages, roles). ExoPlayer's concept of a TrackGroup only allows formats with the same language and role flags to be merged, so we should check that before merging. Issue: androidx/media#2222 #cherrypick PiperOrigin-RevId: 736564055 --- RELEASENOTES.md | 3 ++ .../exoplayer/dash/DashMediaPeriod.java | 19 ++++++++- .../exoplayer/dash/DashMediaPeriodTest.java | 25 +++++++++++ ...sample_mpd_switching_property_incompatible | 42 +++++++++++++++++++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 libraries/test_data/src/test/assets/media/mpd/sample_mpd_switching_property_incompatible diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5bb265c82c..f870e54a6e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -51,6 +51,9 @@ * RTMP extension: * HLS extension: * DASH extension: + * Fix issue where adaptation sets marked with `adaptation-set-switching` + but different languages or role flags are merged together + ([#2222](https://github.com/androidx/media/issues/2222)). * Smooth Streaming extension: * RTSP extension: * Decoder extensions (FFmpeg, VP9, AV1, etc.): diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java index 8ae8dbc367..3d07a2efd0 100644 --- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java +++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaPeriod.java @@ -72,6 +72,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -624,7 +625,9 @@ import java.util.regex.Pattern; @Nullable Integer otherAdaptationSetIndex = adaptationSetIdToIndex.get(Long.parseLong(adaptationSetId)); - if (otherAdaptationSetIndex != null) { + if (otherAdaptationSetIndex != null + && canMergeAdaptationSets( + adaptationSet, adaptationSets.get(otherAdaptationSetIndex))) { mergedGroupIndex = min(mergedGroupIndex, otherAdaptationSetIndex); } } @@ -650,6 +653,20 @@ import java.util.regex.Pattern; return groupedAdaptationSetIndices; } + private static boolean canMergeAdaptationSets( + AdaptationSet adaptationSet1, AdaptationSet adaptationSet2) { + if (adaptationSet1.type != adaptationSet2.type) { + return false; + } + if (adaptationSet1.representations.isEmpty() || adaptationSet2.representations.isEmpty()) { + return true; + } + Format format1 = adaptationSet1.representations.get(0).format; + Format format2 = adaptationSet2.representations.get(0).format; + return Objects.equals(format1.language, format2.language) + && format1.roleFlags == format2.roleFlags; + } + /** * Iterates through list of primary track groups and identifies embedded tracks. * diff --git a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashMediaPeriodTest.java b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashMediaPeriodTest.java index eba317eb50..8ae40cd615 100644 --- a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashMediaPeriodTest.java +++ b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashMediaPeriodTest.java @@ -94,6 +94,31 @@ public final class DashMediaPeriodTest { MediaPeriodAsserts.assertTrackGroups(dashMediaPeriod, expectedTrackGroups); } + @Test + public void + adaptationSetSwitchingProperty_withIncompatibleFormats_mergesOnlyCompatibleTrackGroups() + throws IOException { + DashManifest manifest = parseManifest("media/mpd/sample_mpd_switching_property_incompatible"); + DashMediaPeriod dashMediaPeriod = createDashMediaPeriod(manifest, /* periodIndex= */ 0); + List adaptationSets = manifest.getPeriod(0).adaptationSets; + + // Only the two matching pairs of adaptation sets should be merged. + TrackGroupArray expectedTrackGroups = + new TrackGroupArray( + new TrackGroup(/* id= */ "0", adaptationSets.get(0).representations.get(0).format), + new TrackGroup( + /* id= */ "1", + adaptationSets.get(1).representations.get(0).format, + adaptationSets.get(3).representations.get(0).format), + new TrackGroup( + /* id= */ "2", + adaptationSets.get(2).representations.get(0).format, + adaptationSets.get(4).representations.get(0).format), + new TrackGroup(/* id= */ "5", adaptationSets.get(5).representations.get(0).format)); + + MediaPeriodAsserts.assertTrackGroups(dashMediaPeriod, expectedTrackGroups); + } + @Test public void trickPlayProperty_mergesTrackGroups() throws IOException { DashManifest manifest = parseManifest("media/mpd/sample_mpd_trick_play_property"); diff --git a/libraries/test_data/src/test/assets/media/mpd/sample_mpd_switching_property_incompatible b/libraries/test_data/src/test/assets/media/mpd/sample_mpd_switching_property_incompatible new file mode 100644 index 0000000000..7438d47015 --- /dev/null +++ b/libraries/test_data/src/test/assets/media/mpd/sample_mpd_switching_property_incompatible @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +