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

PiperOrigin-RevId: 736564055
(cherry picked from commit d37f05238a2d8b45ea2e5f4ac026084b917f30df)
This commit is contained in:
tonihei 2025-03-13 10:50:13 -07:00
parent 23468ed55c
commit 24b3bf21b8
4 changed files with 89 additions and 1 deletions

View File

@ -11,6 +11,10 @@
* UI:
* Add `PlaybackSpeedState` class and the corresponding
`rememberPlaybackSpeedState` Composable to `media3-ui-compose` module.
* 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)).
## 1.6

View File

@ -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.
*

View File

@ -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<AdaptationSet> 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");

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD availabilityStartTime="2014-06-19T23:07:42" type="dynamic">
<Period start="PT7462826.784S" id="0">
<AdaptationSet id="0" lang="fr" contentType="video">
<Role schemeIdUri="urn:mpeg:DASH:role:2011" value="commentary"/>
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:adaptation-set-switching:2016"
value="1,2,3,4,5"/>
<Representation id="0"/>
</AdaptationSet>
<AdaptationSet id="1" lang="fr" contentType="video">
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:adaptation-set-switching:2016"
value="0,2,3,4,5"/>
<Representation id="1"/>
</AdaptationSet>
<AdaptationSet id="2" lang="en" contentType="video">
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:adaptation-set-switching:2016"
value="0,1,3,4,5"/>
<Representation id="2"/>
</AdaptationSet>
<AdaptationSet id="3" lang="fr" contentType="video">
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:adaptation-set-switching:2016"
value="0,1,2,4,5"/>
<Representation id="3"/>
</AdaptationSet>
<AdaptationSet id="4" lang="en" contentType="video">
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:adaptation-set-switching:2016"
value="0,1,2,3,5"/>
<Representation id="4"/>
</AdaptationSet>
<AdaptationSet id="5" lang="en" contentType="audio">
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:adaptation-set-switching:2016"
value="0,1,2,3,4"/>
<Representation id="4"/>
</AdaptationSet>
</Period>
</MPD>