From fc558f69a6261ddbd3ee48a4be88900fd8abef8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Thu, 17 Jul 2025 15:44:25 +0200 Subject: [PATCH] Selected automatically audio role flag from AccessibilityManager --- demos/main/src/main/assets/media.exolist.json | 13 +++++ .../common/TrackSelectionParameters.java | 31 ++++++++++ .../trackselection/DefaultTrackSelector.java | 58 +++++++++++++++++-- 3 files changed, 96 insertions(+), 6 deletions(-) diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index 2ac03400c2e..b8bd9d998e7 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -725,6 +725,19 @@ } ] }, + { + "name": "Audio description", + "samples": [ + { + "name": "DASH unified streaming - Audio description (ENG)", + "uri": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel-desc-aud.ism/.mpd" + }, + { + "name": "HLS The Morning Show - Change: Season 2", + "uri": "https://play-edge.itunes.apple.com/WebObjects/MZPlayLocal.woa/hls/subscription/playlist.m3u8?cc=CH&svcId=tvs.vds.4021&a=1568297173&isExternal=true&brandId=tvs.sbd.4000&id=518034010&l=en-GB&aec=UHD" + } + ] + }, { "name": "Progressive", "samples": [ diff --git a/libraries/common/src/main/java/androidx/media3/common/TrackSelectionParameters.java b/libraries/common/src/main/java/androidx/media3/common/TrackSelectionParameters.java index b28a4dadbc2..35b1f43c06d 100644 --- a/libraries/common/src/main/java/androidx/media3/common/TrackSelectionParameters.java +++ b/libraries/common/src/main/java/androidx/media3/common/TrackSelectionParameters.java @@ -95,6 +95,7 @@ public static class Builder { private ImmutableList preferredAudioLanguages; private ImmutableList preferredAudioLabels; private @C.RoleFlags int preferredAudioRoleFlags; + private boolean usePreferredAudioRoleFlagsFromAccessibilityManager; private int maxAudioChannelCount; private int maxAudioBitrate; private ImmutableList preferredAudioMimeTypes; @@ -134,6 +135,7 @@ public Builder() { preferredAudioLanguages = ImmutableList.of(); preferredAudioLabels = ImmutableList.of(); preferredAudioRoleFlags = 0; + usePreferredAudioRoleFlagsFromAccessibilityManager = true; maxAudioChannelCount = Integer.MAX_VALUE; maxAudioBitrate = Integer.MAX_VALUE; preferredAudioMimeTypes = ImmutableList.of(); @@ -213,6 +215,9 @@ protected Builder(Bundle bundle) { firstNonNull(bundle.getStringArray(FIELD_PREFERRED_AUDIO_LABELS), new String[0])); preferredAudioRoleFlags = bundle.getInt(FIELD_PREFERRED_AUDIO_ROLE_FLAGS, DEFAULT.preferredAudioRoleFlags); + usePreferredAudioRoleFlagsFromAccessibilityManager = bundle.getBoolean( + FIELD_USE_PREFERRED_AUDI_ROLE_FLAGS_FROM_ACCESSIBILITY_MANAGER, + DEFAULT.usePreferredAudioRoleFlagsFromAccessibilityManager); maxAudioChannelCount = bundle.getInt(FIELD_MAX_AUDIO_CHANNEL_COUNT, DEFAULT.maxAudioChannelCount); maxAudioBitrate = bundle.getInt(FIELD_MAX_AUDIO_BITRATE, DEFAULT.maxAudioBitrate); @@ -329,6 +334,7 @@ private void init(@UnknownInitialization Builder this, TrackSelectionParameters // Audio preferredAudioLanguages = parameters.preferredAudioLanguages; preferredAudioRoleFlags = parameters.preferredAudioRoleFlags; + usePreferredAudioRoleFlagsFromAccessibilityManager = parameters.usePreferredAudioRoleFlagsFromAccessibilityManager; preferredAudioLabels = parameters.preferredAudioLabels; maxAudioChannelCount = parameters.maxAudioChannelCount; maxAudioBitrate = parameters.maxAudioBitrate; @@ -635,6 +641,19 @@ public Builder setPreferredAudioLanguages(String... preferredAudioLanguages) { @CanIgnoreReturnValue public Builder setPreferredAudioRoleFlags(@C.RoleFlags int preferredAudioRoleFlags) { this.preferredAudioRoleFlags = preferredAudioRoleFlags; + this.usePreferredAudioRoleFlagsFromAccessibilityManager = false; + return this; + } + + /** + * Set preferred audio role flag from accessibility manager + * + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setPreferredAudioRoleFlagsFromAccessibilityManager() { + this.usePreferredAudioRoleFlagsFromAccessibilityManager = true; + this.preferredAudioRoleFlags = 0; return this; } @@ -1299,6 +1318,8 @@ public static TrackSelectionParameters getDefaults(Context context) { */ public final @C.RoleFlags int preferredAudioRoleFlags; + public final boolean usePreferredAudioRoleFlagsFromAccessibilityManager; + /** * Maximum allowed audio channel count. The default value is {@link Integer#MAX_VALUE} (i.e. no * constraint). @@ -1427,6 +1448,7 @@ protected TrackSelectionParameters(Builder builder) { // Audio this.preferredAudioLanguages = builder.preferredAudioLanguages; this.preferredAudioRoleFlags = builder.preferredAudioRoleFlags; + this.usePreferredAudioRoleFlagsFromAccessibilityManager = builder.usePreferredAudioRoleFlagsFromAccessibilityManager; this.maxAudioChannelCount = builder.maxAudioChannelCount; this.preferredAudioLabels = builder.preferredAudioLabels; this.maxAudioBitrate = builder.maxAudioBitrate; @@ -1485,6 +1507,8 @@ public boolean equals(@Nullable Object obj) { && preferredVideoRoleFlags == other.preferredVideoRoleFlags // Audio && preferredAudioLanguages.equals(other.preferredAudioLanguages) + && usePreferredAudioRoleFlagsFromAccessibilityManager + == other.usePreferredAudioRoleFlagsFromAccessibilityManager && preferredAudioRoleFlags == other.preferredAudioRoleFlags && maxAudioChannelCount == other.maxAudioChannelCount && preferredAudioLabels.equals(other.preferredAudioLabels) @@ -1532,6 +1556,7 @@ public int hashCode() { // Audio result = 31 * result + preferredAudioLanguages.hashCode(); result = 31 * result + preferredAudioRoleFlags; + result = 31 * result + (usePreferredAudioRoleFlagsFromAccessibilityManager ? 1 : 0); result = 31 * result + maxAudioChannelCount; result = 31 * result + preferredAudioLabels.hashCode(); result = 31 * result + maxAudioBitrate; @@ -1597,6 +1622,9 @@ public int hashCode() { private static final String FIELD_PREFERRED_VIDEO_LABELS = Util.intToStringMaxRadix(36); private static final String FIELD_PREFERRED_AUDIO_LABELS = Util.intToStringMaxRadix(37); private static final String FIELD_PREFERRED_TEXT_LABELS = Util.intToStringMaxRadix(38); + private static final String + FIELD_USE_PREFERRED_AUDI_ROLE_FLAGS_FROM_ACCESSIBILITY_MANAGER = + Util.intToStringMaxRadix(39); /** * Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()} @@ -1638,6 +1666,9 @@ public Bundle toBundle() { bundle.putStringArray( FIELD_PREFERRED_AUDIO_LANGUAGES, preferredAudioLanguages.toArray(new String[0])); bundle.putInt(FIELD_PREFERRED_AUDIO_ROLE_FLAGS, preferredAudioRoleFlags); + bundle.putBoolean( + FIELD_USE_PREFERRED_AUDI_ROLE_FLAGS_FROM_ACCESSIBILITY_MANAGER, + usePreferredAudioRoleFlagsFromAccessibilityManager); bundle.putInt(FIELD_MAX_AUDIO_CHANNEL_COUNT, maxAudioChannelCount); bundle.putInt(FIELD_MAX_AUDIO_BITRATE, maxAudioBitrate); bundle.putStringArray( diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java index 88900ec9281..a62e8a4052f 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java @@ -16,6 +16,8 @@ package androidx.media3.exoplayer.trackselection; import static android.os.Build.VERSION.SDK_INT; +import static androidx.media3.common.C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND; +import static androidx.media3.common.C.ROLE_FLAG_DESCRIBES_VIDEO; import static androidx.media3.common.TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_DISABLED; import static androidx.media3.common.TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_REQUIRED; import static androidx.media3.common.util.Assertions.checkNotNull; @@ -34,6 +36,7 @@ import android.media.AudioFormat; import android.media.AudioManager; import android.media.Spatializer; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -41,6 +44,7 @@ import android.util.Pair; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.view.accessibility.AccessibilityManager; import android.view.accessibility.CaptioningManager; import androidx.annotation.GuardedBy; import androidx.annotation.IntDef; @@ -410,6 +414,14 @@ public ParametersBuilder setPreferredAudioRoleFlags(@C.RoleFlags int preferredAu return this; } + @SuppressWarnings("deprecation") // Intentionally returning deprecated type + @CanIgnoreReturnValue + @Override + public ParametersBuilder setPreferredAudioRoleFlagsFromAccessibilityManager() { + delegate.setPreferredAudioRoleFlagsFromAccessibilityManager(); + return this; + } + @SuppressWarnings("deprecation") // Intentionally returning deprecated type @CanIgnoreReturnValue @Override @@ -1264,6 +1276,13 @@ public Builder setPreferredAudioRoleFlags(@C.RoleFlags int preferredAudioRoleFla return this; } + @CanIgnoreReturnValue + @Override + public Builder setPreferredAudioRoleFlagsFromAccessibilityManager() { + super.setPreferredAudioRoleFlagsFromAccessibilityManager(); + return this; + } + @CanIgnoreReturnValue @Override public Builder setPreferredAudioLabels(String... preferredAudioLabels) { @@ -2910,7 +2929,13 @@ protected Pair selectAudioTrack( break; } } + + @RoleFlags int preferredRoleFlagFromAccessibilityManager = 0; + if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + preferredRoleFlagFromAccessibilityManager = getPreferredRoleFlagFromAccessibilityManager(context); + } boolean hasVideoRendererWithMappedTracksFinal = hasVideoRendererWithMappedTracks; + @RoleFlags int preferredRoleFlagFromAccessibilityManagerFinal = preferredRoleFlagFromAccessibilityManager; return selectTracksForType( C.TRACK_TYPE_AUDIO, mappedTrackInfo, @@ -2923,7 +2948,8 @@ protected Pair selectAudioTrack( support, hasVideoRendererWithMappedTracksFinal, format -> isAudioFormatWithinAudioChannelCountConstraints(format, params), - rendererMixedMimeTypeAdaptationSupports[rendererIndex]), + rendererMixedMimeTypeAdaptationSupports[rendererIndex], + preferredRoleFlagFromAccessibilityManagerFinal), AudioTrackInfo::compareSelections); } @@ -3601,6 +3627,21 @@ private static String getPreferredLanguageFromCaptioningManager(@Nullable Contex return Util.getLocaleLanguageTag(preferredLocale); } + @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) + private static @RoleFlags int getPreferredRoleFlagFromAccessibilityManager( + @Nullable Context context) { + if (context == null) { + return 0; + } + AccessibilityManager accessibilityManager = (AccessibilityManager) context.getSystemService( + Context.ACCESSIBILITY_SERVICE); + if (accessibilityManager != null) { + return accessibilityManager.isAudioDescriptionRequested() ? ROLE_FLAG_DESCRIBES_VIDEO + | ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND : 0; + } + return 0; + } + /** Base class for track selection information of a {@link Format}. */ private abstract static class TrackInfo> { /** Factory for {@link TrackInfo} implementations for a given {@link TrackGroup}. */ @@ -3910,7 +3951,8 @@ public static ImmutableList createForTrackGroup( @Capabilities int[] formatSupport, boolean hasMappedVideoTracks, Predicate withinAudioChannelCountConstraints, - @AdaptiveSupport int mixedMimeTypeAdaptationSupport) { + @AdaptiveSupport int mixedMimeTypeAdaptationSupport, + @RoleFlags int preferredRoleFlagFromAccessibilityManager) { ImmutableList.Builder listBuilder = ImmutableList.builder(); for (int i = 0; i < trackGroup.length; i++) { listBuilder.add( @@ -3922,7 +3964,8 @@ public static ImmutableList createForTrackGroup( formatSupport[i], hasMappedVideoTracks, withinAudioChannelCountConstraints, - mixedMimeTypeAdaptationSupport)); + mixedMimeTypeAdaptationSupport, + preferredRoleFlagFromAccessibilityManager)); } return listBuilder.build(); } @@ -3957,7 +4000,8 @@ public AudioTrackInfo( @Capabilities int formatSupport, boolean hasMappedVideoTracks, Predicate withinAudioChannelCountConstraints, - @AdaptiveSupport int mixedMimeTypeAdaptationSupport) { + @AdaptiveSupport int mixedMimeTypeAdaptationSupport, + @RoleFlags int preferredRoleFlagFromAccessibilityManager) { super(rendererIndex, trackGroup, trackIndex); this.parameters = parameters; @SuppressLint("WrongConstant") @@ -3988,8 +4032,10 @@ public AudioTrackInfo( } preferredLanguageIndex = bestLanguageIndex; preferredLanguageScore = bestLanguageScore; - preferredRoleFlagsScore = - getRoleFlagMatchScore(format.roleFlags, parameters.preferredAudioRoleFlags); + @RoleFlags int preferredAudioRoleFlags = + preferredRoleFlagFromAccessibilityManager == 0 ? parameters.preferredAudioRoleFlags + : preferredRoleFlagFromAccessibilityManager; + preferredRoleFlagsScore = getRoleFlagMatchScore(format.roleFlags, preferredAudioRoleFlags); preferredLabelMatchIndex = getBestLabelMatchIndex(format, parameters.preferredAudioLabels); hasMainOrNoRoleFlag = format.roleFlags == 0 || (format.roleFlags & C.ROLE_FLAG_MAIN) != 0; isDefaultSelectionFlag = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;