From 1a60b6bea601c995eea1b66be70e9ba47755a194 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 11 Nov 2021 13:16:49 +0000 Subject: [PATCH] Fully support per-track-type selection overrides. Currently, TrackSelectionOverrides are documented as being applied per track type, meaning that one override for a type disables all other selections for the same track type. However, the actual implementation only applies it per track group, relying on the track selector to never select another renderer of the same type. This change fixes DefaultTrackSelector to fully adhere to the TrackSelectionsOverride definition. This solves problems when overriding tracks for extension renderers (see Issue: google/ExoPlayer#9675) and also simplifies a workaround added to StyledPlayerView. #minor-release PiperOrigin-RevId: 409121711 --- .../trackselection/DefaultTrackSelector.java | 152 +++++++++++++----- .../DefaultTrackSelectorTest.java | 96 ++++++++++- .../media3/ui/StyledPlayerControlView.java | 49 +----- 3 files changed, 208 insertions(+), 89 deletions(-) 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 982268dfed..0629681806 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 @@ -1443,9 +1443,32 @@ public class DefaultTrackSelector extends MappingTrackSelector { rendererMixedMimeTypeAdaptationSupports, params); - // Apply track disabling and overriding. + // Apply per track type overrides. + SparseArray> applicableOverridesByTrackType = + getApplicableOverrides(mappedTrackInfo, params); + for (int i = 0; i < applicableOverridesByTrackType.size(); i++) { + Pair overrideAndRendererIndex = + applicableOverridesByTrackType.valueAt(i); + applyTrackTypeOverride( + mappedTrackInfo, + definitions, + /* trackType= */ applicableOverridesByTrackType.keyAt(i), + /* override= */ overrideAndRendererIndex.first, + /* overrideRendererIndex= */ overrideAndRendererIndex.second); + } + + // Apply legacy per renderer overrides. for (int i = 0; i < rendererCount; i++) { - definitions[i] = maybeApplyOverride(mappedTrackInfo, params, i, definitions[i]); + if (hasLegacyRendererOverride(mappedTrackInfo, params, /* rendererIndex= */ i)) { + definitions[i] = getLegacyRendererOverride(mappedTrackInfo, params, /* rendererIndex= */ i); + } + } + + // Disable renderers if needed. + for (int i = 0; i < rendererCount; i++) { + if (isRendererDisabled(mappedTrackInfo, params, /* rendererIndex= */ i)) { + definitions[i] = null; + } } @NullableType @@ -1477,53 +1500,94 @@ public class DefaultTrackSelector extends MappingTrackSelector { return Pair.create(rendererConfigurations, rendererTrackSelections); } - /** - * Returns the {@link ExoTrackSelection.Definition} of a renderer after applying selection - * overriding and renderer disabling. - */ - protected ExoTrackSelection.@NullableType Definition maybeApplyOverride( - MappedTrackInfo mappedTrackInfo, - Parameters params, - int rendererIndex, - ExoTrackSelection.@NullableType Definition currentDefinition) { - // Per renderer and per track type disabling + private boolean isRendererDisabled( + MappedTrackInfo mappedTrackInfo, Parameters params, int rendererIndex) { @C.TrackType int rendererType = mappedTrackInfo.getRendererType(rendererIndex); - if (params.getRendererDisabled(rendererIndex) - || params.disabledTrackTypes.contains(rendererType)) { + return params.getRendererDisabled(rendererIndex) + || params.disabledTrackTypes.contains(rendererType); + } + + @SuppressWarnings("deprecation") // Calling deprecated hasSelectionOverride. + private boolean hasLegacyRendererOverride( + MappedTrackInfo mappedTrackInfo, Parameters params, int rendererIndex) { + TrackGroupArray rendererTrackGroups = mappedTrackInfo.getTrackGroups(rendererIndex); + return params.hasSelectionOverride(rendererIndex, rendererTrackGroups); + } + + @SuppressWarnings("deprecation") // Calling deprecated getSelectionOverride. + private ExoTrackSelection.@NullableType Definition getLegacyRendererOverride( + MappedTrackInfo mappedTrackInfo, Parameters params, int rendererIndex) { + TrackGroupArray rendererTrackGroups = mappedTrackInfo.getTrackGroups(rendererIndex); + @Nullable + SelectionOverride override = params.getSelectionOverride(rendererIndex, rendererTrackGroups); + if (override == null) { return null; } - // Per TrackGroupArray overrides. - TrackGroupArray rendererTrackGroups = mappedTrackInfo.getTrackGroups(rendererIndex); - if (params.hasSelectionOverride(rendererIndex, rendererTrackGroups)) { - @Nullable - SelectionOverride override = params.getSelectionOverride(rendererIndex, rendererTrackGroups); - if (override == null) { - return null; - } - return new ExoTrackSelection.Definition( - rendererTrackGroups.get(override.groupIndex), override.tracks, override.type); - } - // Per TrackGroup overrides. - for (int j = 0; j < rendererTrackGroups.length; j++) { - TrackGroup trackGroup = rendererTrackGroups.get(j); - @Nullable - TrackSelectionOverride overrideTracks = - params.trackSelectionOverrides.getOverride(trackGroup); - if (overrideTracks != null) { - if (overrideTracks.trackIndices.isEmpty()) { - // TrackGroup is disabled. Deselect the currentDefinition if applicable. Otherwise ignore. - if (currentDefinition != null && currentDefinition.group.equals(trackGroup)) { - currentDefinition = null; - } - } else { - // Override current definition with new selection. - currentDefinition = - new ExoTrackSelection.Definition( - trackGroup, Ints.toArray(overrideTracks.trackIndices)); - } + return new ExoTrackSelection.Definition( + rendererTrackGroups.get(override.groupIndex), override.tracks, override.type); + } + + /** + * Returns applicable overrides. Mapping from track type to a pair of override and renderer index + * for this override. + */ + private SparseArray> getApplicableOverrides( + MappedTrackInfo mappedTrackInfo, Parameters params) { + SparseArray> applicableOverrides = new SparseArray<>(); + // Iterate through all existing track groups to ensure only overrides for those groups are used. + int rendererCount = mappedTrackInfo.getRendererCount(); + for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) { + TrackGroupArray rendererTrackGroups = mappedTrackInfo.getTrackGroups(rendererIndex); + for (int j = 0; j < rendererTrackGroups.length; j++) { + maybeUpdateApplicableOverrides( + applicableOverrides, + params.trackSelectionOverrides.getOverride(rendererTrackGroups.get(j)), + rendererIndex); + } + } + // Also iterate unmapped groups to see if they have overrides. + TrackGroupArray unmappedGroups = mappedTrackInfo.getUnmappedTrackGroups(); + for (int i = 0; i < unmappedGroups.length; i++) { + maybeUpdateApplicableOverrides( + applicableOverrides, + params.trackSelectionOverrides.getOverride(unmappedGroups.get(i)), + /* rendererIndex= */ C.INDEX_UNSET); + } + return applicableOverrides; + } + + private void maybeUpdateApplicableOverrides( + SparseArray> applicableOverrides, + @Nullable TrackSelectionOverride override, + int rendererIndex) { + if (override == null) { + return; + } + @C.TrackType int trackType = override.getTrackType(); + @Nullable + Pair existingOverride = applicableOverrides.get(trackType); + if (existingOverride == null || existingOverride.first.trackIndices.isEmpty()) { + // We only need to choose one non-empty override per type. + applicableOverrides.put(trackType, Pair.create(override, rendererIndex)); + } + } + + private void applyTrackTypeOverride( + MappedTrackInfo mappedTrackInfo, + ExoTrackSelection.@NullableType Definition[] definitions, + @C.TrackType int trackType, + TrackSelectionOverride override, + int overrideRendererIndex) { + for (int i = 0; i < definitions.length; i++) { + if (overrideRendererIndex == i) { + definitions[i] = + new ExoTrackSelection.Definition( + override.trackGroup, Ints.toArray(override.trackIndices)); + } else if (mappedTrackInfo.getRendererType(i) == trackType) { + // Disable other renderers of the same type. + definitions[i] = null; } } - return currentDefinition; } // Track selection prior to overrides and disabled flags being applied. diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java index c7b33310fc..ff2d0a01ba 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java @@ -18,6 +18,7 @@ package androidx.media3.exoplayer.trackselection; import static androidx.media3.common.C.FORMAT_EXCEEDS_CAPABILITIES; import static androidx.media3.common.C.FORMAT_HANDLED; import static androidx.media3.common.C.FORMAT_UNSUPPORTED_SUBTYPE; +import static androidx.media3.common.C.FORMAT_UNSUPPORTED_TYPE; import static androidx.media3.exoplayer.RendererCapabilities.ADAPTIVE_NOT_SEAMLESS; import static androidx.media3.exoplayer.RendererCapabilities.TUNNELING_NOT_SUPPORTED; import static androidx.media3.exoplayer.RendererConfiguration.DEFAULT; @@ -54,6 +55,7 @@ import androidx.media3.test.utils.FakeTimeline; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.util.HashMap; import java.util.Map; @@ -240,7 +242,8 @@ public final class DefaultTrackSelectorTest { assertThat(result.selections) .asList() - .containsExactly(new FixedTrackSelection(videoGroupMidBitrate, /* track= */ 0), null); + .containsExactly(new FixedTrackSelection(videoGroupMidBitrate, /* track= */ 0), null) + .inOrder(); } /** Tests that an empty override is not applied for a different set of available track groups. */ @@ -269,6 +272,97 @@ public final class DefaultTrackSelectorTest { .isEqualTo(new RendererConfiguration[] {DEFAULT, DEFAULT}); } + @Test + public void selectTrack_withOverrideForDifferentRenderer_clearsDefaultSelectionOfSameType() + throws Exception { + Format videoFormatH264 = + VIDEO_FORMAT.buildUpon().setId("H264").setSampleMimeType(MimeTypes.VIDEO_H264).build(); + Format videoFormatAv1 = + VIDEO_FORMAT.buildUpon().setId("AV1").setSampleMimeType(MimeTypes.VIDEO_AV1).build(); + TrackGroup videoGroupH264 = new TrackGroup(videoFormatH264); + TrackGroup videoGroupAv1 = new TrackGroup(videoFormatAv1); + Map rendererCapabilitiesMap = + ImmutableMap.of( + videoFormatH264.id, FORMAT_HANDLED, videoFormatAv1.id, FORMAT_UNSUPPORTED_TYPE); + RendererCapabilities rendererCapabilitiesH264 = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_VIDEO, rendererCapabilitiesMap); + rendererCapabilitiesMap = + ImmutableMap.of( + videoFormatH264.id, FORMAT_UNSUPPORTED_TYPE, videoFormatAv1.id, FORMAT_HANDLED); + RendererCapabilities rendererCapabilitiesAv1 = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_VIDEO, rendererCapabilitiesMap); + + // Try to force selection of one TrackGroup in both directions to ensure the default gets + // overridden without having to know what the default is. + trackSelector.setParameters( + trackSelector + .buildUponParameters() + .setTrackSelectionOverrides( + new TrackSelectionOverrides.Builder() + .setOverrideForType(new TrackSelectionOverride(videoGroupH264)) + .build())); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {rendererCapabilitiesH264, rendererCapabilitiesAv1}, + new TrackGroupArray(videoGroupH264, videoGroupAv1), + periodId, + TIMELINE); + + assertThat(result.selections) + .asList() + .containsExactly(new FixedTrackSelection(videoGroupH264, /* track= */ 0), null) + .inOrder(); + + trackSelector.setParameters( + trackSelector + .buildUponParameters() + .setTrackSelectionOverrides( + new TrackSelectionOverrides.Builder() + .setOverrideForType(new TrackSelectionOverride(videoGroupAv1)) + .build())); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {rendererCapabilitiesH264, rendererCapabilitiesAv1}, + new TrackGroupArray(videoGroupH264, videoGroupAv1), + periodId, + TIMELINE); + + assertThat(result.selections) + .asList() + .containsExactly(null, new FixedTrackSelection(videoGroupAv1, /* track= */ 0)) + .inOrder(); + } + + @Test + public void selectTracks_withOverrideForUnmappedGroup_disablesAllRenderersOfSameType() + throws Exception { + Format audioSupported = AUDIO_FORMAT.buildUpon().setId("supported").build(); + Format audioUnsupported = AUDIO_FORMAT.buildUpon().setId("unsupported").build(); + TrackGroup audioGroupSupported = new TrackGroup(audioSupported); + TrackGroup audioGroupUnsupported = new TrackGroup(audioUnsupported); + Map audioRendererCapabilitiesMap = + ImmutableMap.of( + audioSupported.id, FORMAT_HANDLED, audioUnsupported.id, FORMAT_UNSUPPORTED_TYPE); + RendererCapabilities audioRendererCapabilties = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, audioRendererCapabilitiesMap); + + trackSelector.setParameters( + trackSelector + .buildUponParameters() + .setTrackSelectionOverrides( + new TrackSelectionOverrides.Builder() + .setOverrideForType(new TrackSelectionOverride(audioGroupUnsupported)) + .build())); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {VIDEO_CAPABILITIES, audioRendererCapabilties}, + new TrackGroupArray(VIDEO_TRACK_GROUP, audioGroupSupported, audioGroupUnsupported), + periodId, + TIMELINE); + + assertThat(result.selections).asList().containsExactly(VIDEO_TRACK_SELECTION, null).inOrder(); + } + /** Tests that an override is not applied for a different set of available track groups. */ @Test public void selectTracksWithNullOverrideForDifferentTracks() throws ExoPlaybackException { diff --git a/libraries/ui/src/main/java/androidx/media3/ui/StyledPlayerControlView.java b/libraries/ui/src/main/java/androidx/media3/ui/StyledPlayerControlView.java index 20a7ef5475..f7ac247886 100644 --- a/libraries/ui/src/main/java/androidx/media3/ui/StyledPlayerControlView.java +++ b/libraries/ui/src/main/java/androidx/media3/ui/StyledPlayerControlView.java @@ -83,7 +83,6 @@ import java.util.Formatter; import java.util.List; import java.util.Locale; import java.util.concurrent.CopyOnWriteArrayList; -import org.checkerframework.dataflow.qual.Pure; /** * A view for controlling {@link Player} instances. @@ -2168,12 +2167,11 @@ public class StyledPlayerControlView extends FrameLayout { TrackSelectionParameters trackSelectionParameters = player.getTrackSelectionParameters(); TrackSelectionOverrides overrides = - forceTrackSelection( - trackSelectionParameters.trackSelectionOverrides, - track.tracksInfo, - track.trackGroupIndex, - new TrackSelectionOverride( - track.trackGroup, ImmutableList.of(track.trackIndex))); + new TrackSelectionOverrides.Builder() + .setOverrideForType( + new TrackSelectionOverride( + track.trackGroup, ImmutableList.of(track.trackIndex))) + .build(); checkNotNull(player) .setTrackSelectionParameters( trackSelectionParameters @@ -2211,41 +2209,4 @@ public class StyledPlayerControlView extends FrameLayout { checkView = itemView.findViewById(R.id.exo_check); } } - - /** - * Forces tracks in a {@link TrackGroup} to be the only ones selected for a {@link C.TrackType}. - * No other tracks of that type will be selectable. If the forced tracks are not supported, then - * no tracks of that type will be selected. - * - * @param trackSelectionOverrides The current {@link TrackSelectionOverride overrides}. - * @param tracksInfo The current {@link TracksInfo}. - * @param forcedTrackGroupIndex The index of the {@link TrackGroup} in {@code tracksInfo} that - * should have its track selected. - * @param forcedTrackSelectionOverride The tracks to force selection of. - * @return The updated {@link TrackSelectionOverride overrides}. - */ - @Pure - private static TrackSelectionOverrides forceTrackSelection( - TrackSelectionOverrides trackSelectionOverrides, - TracksInfo tracksInfo, - int forcedTrackGroupIndex, - TrackSelectionOverride forcedTrackSelectionOverride) { - TrackSelectionOverrides.Builder overridesBuilder = trackSelectionOverrides.buildUpon(); - - @C.TrackType - int trackType = tracksInfo.getTrackGroupInfos().get(forcedTrackGroupIndex).getTrackType(); - overridesBuilder.setOverrideForType(forcedTrackSelectionOverride); - // TrackSelectionOverride doesn't currently guarantee that only overwritten track - // group of a given type are selected, so the others have to be explicitly disabled. - // This guarantee is provided in the following patch that removes the need for this method. - ImmutableList trackGroupInfos = tracksInfo.getTrackGroupInfos(); - for (int i = 0; i < trackGroupInfos.size(); i++) { - TrackGroupInfo trackGroupInfo = trackGroupInfos.get(i); - if (i != forcedTrackGroupIndex && trackGroupInfo.getTrackType() == trackType) { - TrackGroup trackGroup = trackGroupInfo.getTrackGroup(); - overridesBuilder.addOverride(new TrackSelectionOverride(trackGroup, ImmutableList.of())); - } - } - return overridesBuilder.build(); - } }