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
This commit is contained in:
tonihei 2021-11-11 13:16:49 +00:00 committed by Ian Baker
parent 0221e0e159
commit 1a60b6bea6
3 changed files with 208 additions and 89 deletions

View File

@ -1443,9 +1443,32 @@ public class DefaultTrackSelector extends MappingTrackSelector {
rendererMixedMimeTypeAdaptationSupports,
params);
// Apply track disabling and overriding.
// Apply per track type overrides.
SparseArray<Pair<TrackSelectionOverride, Integer>> applicableOverridesByTrackType =
getApplicableOverrides(mappedTrackInfo, params);
for (int i = 0; i < applicableOverridesByTrackType.size(); i++) {
Pair<TrackSelectionOverride, Integer> 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,24 +1500,24 @@ 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 null;
return params.getRendererDisabled(rendererIndex)
|| params.disabledTrackTypes.contains(rendererType);
}
// Per TrackGroupArray overrides.
@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);
if (params.hasSelectionOverride(rendererIndex, rendererTrackGroups)) {
@Nullable
SelectionOverride override = params.getSelectionOverride(rendererIndex, rendererTrackGroups);
if (override == null) {
@ -1503,28 +1526,69 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return new ExoTrackSelection.Definition(
rendererTrackGroups.get(override.groupIndex), override.tracks, override.type);
}
// Per TrackGroup overrides.
/**
* Returns applicable overrides. Mapping from track type to a pair of override and renderer index
* for this override.
*/
private SparseArray<Pair<TrackSelectionOverride, Integer>> getApplicableOverrides(
MappedTrackInfo mappedTrackInfo, Parameters params) {
SparseArray<Pair<TrackSelectionOverride, Integer>> 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++) {
TrackGroup trackGroup = rendererTrackGroups.get(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<Pair<TrackSelectionOverride, Integer>> applicableOverrides,
@Nullable TrackSelectionOverride override,
int rendererIndex) {
if (override == null) {
return;
}
@C.TrackType int trackType = override.getTrackType();
@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;
Pair<TrackSelectionOverride, Integer> 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));
}
} else {
// Override current definition with new selection.
currentDefinition =
}
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(
trackGroup, Ints.toArray(overrideTracks.trackIndices));
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.

View File

@ -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<String, Integer> 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<String, Integer> 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 {

View File

@ -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 TrackSelectionOverrides.Builder()
.setOverrideForType(
new TrackSelectionOverride(
track.trackGroup, ImmutableList.of(track.trackIndex)));
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<TrackGroupInfo> 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();
}
}