Skip to content

Commit

Permalink
Fully support per-track-type selection overrides.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
tonihei authored and icbaker committed Nov 19, 2021
1 parent 0221e0e commit 1a60b6b
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1443,9 +1443,32 @@ private void setParametersInternal(Parameters parameters) {
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
Expand Down Expand Up @@ -1477,53 +1500,94 @@ private void setParametersInternal(Parameters parameters) {
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);
}

/**
* 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++) {
maybeUpdateApplicableOverrides(
applicableOverrides,
params.trackSelectionOverrides.getOverride(rendererTrackGroups.get(j)),
rendererIndex);
}
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));
}
// 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
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));
}
}

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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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;
Expand Down Expand Up @@ -54,6 +55,7 @@
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;
Expand Down Expand Up @@ -240,7 +242,8 @@ public void selectTrack_withMixedEmptyAndNonEmptyTrackOverrides_appliesNonEmptyO

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. */
Expand Down Expand Up @@ -269,6 +272,97 @@ public void selectTracks_withEmptyTrackOverrideForDifferentTracks_hasNoEffect()
.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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@
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.
Expand Down Expand Up @@ -2168,12 +2167,11 @@ public void onBindViewHolder(SubSettingViewHolder holder, int position) {
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
Expand Down Expand Up @@ -2211,41 +2209,4 @@ public SubSettingViewHolder(View itemView) {
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();
}
}

0 comments on commit 1a60b6b

Please sign in to comment.