diff --git a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.java b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.java new file mode 100644 index 0000000000..65c659cce8 --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverrides.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.trackselection; + +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.BundleableUtil.fromBundleNullableList; +import static com.google.android.exoplayer2.util.BundleableUtil.toBundleArrayList; +import static java.util.Collections.max; +import static java.util.Collections.min; + +import android.os.Bundle; +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.trackselection.C.TrackType; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.primitives.Ints; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Forces the selection of the specified tracks in {@link TrackGroup TrackGroups}. + * + *

Each {@link TrackSelectionOverride override} only affects the selection of tracks of that + * {@link TrackType type}. For example overriding the selection of an {@link C#TRACK_TYPE_AUDIO + * audio} {@link TrackGroup} will not affect the selection of {@link C#TRACK_TYPE_VIDEO video} or + * {@link C#TRACK_TYPE_TEXT text} tracks. + * + *

If multiple {@link TrackGroup TrackGroups} of the same {@link TrackType} are overridden, which + * tracks will be selected depend on the player capabilities. For example, by default {@code + * ExoPlayer} doesn't support selecting more than one {@link TrackGroup} per {@link TrackType}. + * + *

Overrides of {@link TrackGroup} that are not currently available are ignored. For example, + * when the player transitions to the next {@link MediaItem} in a playlist, any overrides of the + * previous {@link MediaItem} are ignored. + * + * @see TrackSelectionParameters#trackSelectionOverrides + */ +public final class TrackSelectionOverrides implements Bundleable { + + /** Builder for {@link TrackSelectionOverrides}. */ + public static final class Builder { + // Cannot use ImmutableMap.Builder as it doesn't support removing entries. + private final HashMap overrides; + + /** Creates an builder with no {@link TrackSelectionOverride}. */ + public Builder() { + overrides = new HashMap<>(); + } + + private Builder(Map overrides) { + this.overrides = new HashMap<>(overrides); + } + + /** Adds an override for the provided {@link TrackGroup}. */ + public Builder addOverride(TrackSelectionOverride override) { + overrides.put(override.trackGroup, override); + return this; + } + + /** Removes the override associated with the provided {@link TrackGroup} if present. */ + public Builder clearOverride(TrackGroup trackGroup) { + overrides.remove(trackGroup); + return this; + } + + /** Set the override for the type of the provided {@link TrackGroup}. */ + public Builder setOverrideForType(TrackSelectionOverride override) { + clearOverridesOfType(override.getTrackType()); + overrides.put(override.trackGroup, override); + return this; + } + + /** + * Remove any override associated with {@link TrackGroup TrackGroups} of type {@code trackType}. + */ + public Builder clearOverridesOfType(@TrackType int trackType) { + for (Iterator it = overrides.values().iterator(); it.hasNext(); ) { + TrackSelectionOverride trackSelectionOverride = it.next(); + if (trackSelectionOverride.getTrackType() == trackType) { + it.remove(); + } + } + return this; + } + + /** Returns a new {@link TrackSelectionOverrides} instance with the current builder values. */ + public TrackSelectionOverrides build() { + return new TrackSelectionOverrides(overrides); + } + } + + /** + * Forces the selection of {@link #trackIndexes} for a {@link TrackGroup}. + * + *

If multiple {link #tracks} are overridden, as many as possible will be selected depending on + * the player capabilities. + * + *

If a {@link TrackSelectionOverride} has no tracks ({@code tracks.isEmpty()}), no tracks will + * be played. This is similar to {@link TrackSelectionParameters#disabledTrackTypes}, except it + * will only affect the playback of the associated {@link TrackGroup}. For example, if the only + * {@link C#TRACK_TYPE_VIDEO} {@link TrackGroup} is associated with no tracks, no video will play + * until the next video starts. + */ + public static final class TrackSelectionOverride implements Bundleable { + + /** The {@link TrackGroup} whose {@link #trackIndexes} are forced to be selected. */ + public final TrackGroup trackGroup; + /** The index of tracks in a {@link TrackGroup} to be selected. */ + public final ImmutableList trackIndexes; + + /** Constructs an instance to force all tracks in {@code trackGroup} to be selected. */ + public TrackSelectionOverride(TrackGroup trackGroup) { + this.trackGroup = trackGroup; + ImmutableList.Builder builder = new ImmutableList.Builder<>(); + for (int i = 0; i < trackGroup.length; i++) { + builder.add(i); + } + this.trackIndexes = builder.build(); + } + + /** + * Constructs an instance to force {@code trackIndexes} in {@code trackGroup} to be selected. + * + * @param trackGroup The {@link TrackGroup} for which to override the track selection. + * @param trackIndexes The indexes of the tracks in the {@link TrackGroup} to select. + */ + public TrackSelectionOverride(TrackGroup trackGroup, List trackIndexes) { + if (!trackIndexes.isEmpty()) { + if (min(trackIndexes) < 0 || max(trackIndexes) >= trackGroup.length) { + throw new IndexOutOfBoundsException(); + } + } + this.trackGroup = trackGroup; + this.trackIndexes = ImmutableList.copyOf(trackIndexes); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TrackSelectionOverride that = (TrackSelectionOverride) obj; + return trackGroup.equals(that.trackGroup) && trackIndexes.equals(that.trackIndexes); + } + + @Override + public int hashCode() { + return trackGroup.hashCode() + 31 * trackIndexes.hashCode(); + } + + private @TrackType int getTrackType() { + return MimeTypes.getTrackType(trackGroup.getFormat(0).sampleMimeType); + } + + // Bundleable implementation + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_TRACK_GROUP, + FIELD_TRACKS, + }) + private @interface FieldNumber {} + + private static final int FIELD_TRACK_GROUP = 0; + private static final int FIELD_TRACKS = 1; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putBundle(keyForField(FIELD_TRACK_GROUP), trackGroup.toBundle()); + bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(trackIndexes)); + return bundle; + } + + /** Object that can restore {@code TrackSelectionOverride} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> { + @Nullable Bundle trackGroupBundle = bundle.getBundle(keyForField(FIELD_TRACK_GROUP)); + checkNotNull(trackGroupBundle); // Mandatory as there are no reasonable defaults. + TrackGroup trackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle); + @Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS)); + if (tracks == null) { + return new TrackSelectionOverride(trackGroup); + } + return new TrackSelectionOverride(trackGroup, Ints.asList(tracks)); + }; + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } + } + + /** Empty {@code TrackSelectionOverrides}, where no track selection is overridden. */ + public static final TrackSelectionOverrides EMPTY = + new TrackSelectionOverrides(ImmutableMap.of()); + + private final ImmutableMap overrides; + + private TrackSelectionOverrides(Map overrides) { + this.overrides = ImmutableMap.copyOf(overrides); + } + + /** Returns a {@link Builder} initialized with the values of this instance. */ + public Builder buildUpon() { + return new Builder(overrides); + } + + /** Returns all {@link TrackSelectionOverride} contained. */ + public ImmutableList asList() { + return ImmutableList.copyOf(overrides.values()); + } + + /** + * Returns the {@link TrackSelectionOverride} of the provided {@link TrackGroup} or {@code null} + * if there is none. + */ + @Nullable + public TrackSelectionOverride getOverride(TrackGroup trackGroup) { + return overrides.get(trackGroup); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TrackSelectionOverrides that = (TrackSelectionOverrides) obj; + return overrides.equals(that.overrides); + } + + @Override + public int hashCode() { + return overrides.hashCode(); + } + + // Bundleable implementation + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_OVERRIDES, + }) + private @interface FieldNumber {} + + private static final int FIELD_OVERRIDES = 0; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList( + keyForField(FIELD_OVERRIDES), toBundleArrayList(overrides.values())); + return bundle; + } + + /** Object that can restore {@code TrackSelectionOverrides} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> { + List trackSelectionOverrides = + fromBundleNullableList( + TrackSelectionOverride.CREATOR, + bundle.getParcelableArrayList(keyForField(FIELD_OVERRIDES)), + ImmutableList.of()); + ImmutableMap.Builder builder = + new ImmutableMap.Builder<>(); + for (int i = 0; i < trackSelectionOverrides.size(); i++) { + TrackSelectionOverride trackSelectionOverride = trackSelectionOverrides.get(i); + builder.put(trackSelectionOverride.trackGroup, trackSelectionOverride); + } + return new TrackSelectionOverrides(builder.build()); + }; + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } +} diff --git a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java index 57cdc3a970..738cf74e2b 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionParameters.java @@ -16,8 +16,7 @@ package com.google.android.exoplayer2.trackselection; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; -import static com.google.android.exoplayer2.util.BundleableUtil.fromBundleNullableList; -import static com.google.android.exoplayer2.util.BundleableUtil.toBundleArrayList; +import static com.google.android.exoplayer2.util.BundleableUtil.fromNullableBundle; import static com.google.common.base.MoreObjects.firstNonNull; import android.content.Context; @@ -30,23 +29,17 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.primitives.Ints; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Set; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; /** * Constraint parameters for track selection. @@ -100,7 +93,7 @@ public class TrackSelectionParameters implements Bundleable { // General private boolean forceLowestBitrate; private boolean forceHighestSupportedBitrate; - private ImmutableMap trackSelectionOverrides; + private TrackSelectionOverrides trackSelectionOverrides; private ImmutableSet<@C.TrackType Integer> disabledTrackTypes; /** @@ -131,7 +124,7 @@ public class TrackSelectionParameters implements Bundleable { // General forceLowestBitrate = false; forceHighestSupportedBitrate = false; - trackSelectionOverrides = ImmutableMap.of(); + trackSelectionOverrides = TrackSelectionOverrides.EMPTY; disabledTrackTypes = ImmutableSet.of(); } @@ -233,17 +226,11 @@ public class TrackSelectionParameters implements Bundleable { bundle.getBoolean( keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate); - List keys = - fromBundleNullableList( - TrackGroup.CREATOR, - bundle.getParcelableArrayList(keyForField(FIELD_SELECTION_OVERRIDE_KEYS)), - ImmutableList.of()); - List values = - fromBundleNullableList( - TrackSelectionOverride.CREATOR, - bundle.getParcelableArrayList(keyForField(FIELD_SELECTION_OVERRIDE_VALUES)), - ImmutableList.of()); - trackSelectionOverrides = zipToMap(keys, values); + trackSelectionOverrides = + fromNullableBundle( + TrackSelectionOverrides.CREATOR, + bundle.getBundle(keyForField(FIELD_SELECTION_OVERRIDE_KEYS)), + TrackSelectionOverrides.EMPTY); disabledTrackTypes = ImmutableSet.copyOf( Ints.asList( @@ -642,9 +629,8 @@ public class TrackSelectionParameters implements Bundleable { * @param trackSelectionOverrides The track selection overrides. * @return This builder. */ - public Builder setTrackSelectionOverrides( - Map trackSelectionOverrides) { - this.trackSelectionOverrides = ImmutableMap.copyOf(trackSelectionOverrides); + public Builder setTrackSelectionOverrides(TrackSelectionOverrides trackSelectionOverrides) { + this.trackSelectionOverrides = trackSelectionOverrides; return this; } @@ -692,83 +678,6 @@ public class TrackSelectionParameters implements Bundleable { } return listBuilder.build(); } - - private static ImmutableMap<@NonNull K, @NonNull V> zipToMap( - List<@NonNull K> keys, List<@NonNull V> values) { - ImmutableMap.Builder<@NonNull K, @NonNull V> builder = new ImmutableMap.Builder<>(); - for (int i = 0; i < keys.size(); i++) { - builder.put(keys.get(i), values.get(i)); - } - return builder.build(); - } - } - - /** - * Forces the selection of {@link #tracks} for a {@link TrackGroup}. - * - * @see #trackSelectionOverrides - */ - public static final class TrackSelectionOverride implements Bundleable { - /** Force the selection of the associated {@link TrackGroup}, but no track will be played. */ - public static final TrackSelectionOverride DISABLE = - new TrackSelectionOverride(ImmutableSet.of()); - - /** The index of tracks in a {@link TrackGroup} to be selected. */ - public final ImmutableSet tracks; - - /** Constructs an instance to force {@code tracks} to be selected. */ - public TrackSelectionOverride(ImmutableSet tracks) { - this.tracks = tracks; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - TrackSelectionOverride that = (TrackSelectionOverride) obj; - return tracks.equals(that.tracks); - } - - @Override - public int hashCode() { - return tracks.hashCode(); - } - - // Bundleable implementation - - @Documented - @Retention(RetentionPolicy.SOURCE) - @IntDef({ - FIELD_TRACKS, - }) - private @interface FieldNumber {} - - private static final int FIELD_TRACKS = 0; - - @Override - public Bundle toBundle() { - Bundle bundle = new Bundle(); - bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(tracks)); - return bundle; - } - - /** Object that can restore {@code TrackSelectionOverride} from a {@link Bundle}. */ - public static final Creator CREATOR = - bundle -> { - @Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS)); - if (tracks == null) { - return DISABLE; - } - return new TrackSelectionOverride(ImmutableSet.copyOf(Ints.asList(tracks))); - }; - - private static String keyForField(@FieldNumber int field) { - return Integer.toString(field, Character.MAX_RADIX); - } } /** @@ -921,29 +830,8 @@ public class TrackSelectionParameters implements Bundleable { */ public final boolean forceHighestSupportedBitrate; - /** - * For each {@link TrackGroup} in the map, forces the tracks associated with it to be selected for - * playback. - * - *

For example if {@code trackSelectionOverrides.equals(ImmutableMap.of(trackGroup, - * ImmutableSet.of(1, 2, 3)))}, the tracks 1, 2 and 3 of {@code trackGroup} will be selected. - * - *

If multiple of the current {@link TrackGroup}s of the same {@link C.TrackType} are - * overridden, it is undetermined which one(s) will be selected. For example if a {@link - * MediaItem} has 2 video track groups (for example 2 different angles), and both are overriden, - * it is undetermined which one will be selected. - * - *

If multiple tracks of the {@link TrackGroup} are overriden, all supported (see {@link - * C.FormatSupport}) will be selected. - * - *

If a {@link TrackGroup} is associated with an empty set of tracks, no tracks will be played. - * This is similar to {@link #disabledTrackTypes}, except it will only affect the playback of the - * associated {@link TrackGroup}. For example, if the {@link C#TRACK_TYPE_VIDEO} {@link - * TrackGroup} is associated with no tracks, no video will play until the next video starts. - * - *

The default value is that no {@link TrackGroup} selections are overridden (empty map). - */ - public final ImmutableMap trackSelectionOverrides; + /** Overrides to force tracks to be selected. */ + public final TrackSelectionOverrides trackSelectionOverrides; /** * The track types that are disabled. No track of a disabled type will be selected, thus no track * type contained in the set will be played. The default value is that no track type is disabled @@ -1159,12 +1047,8 @@ public class TrackSelectionParameters implements Bundleable { bundle.putBoolean(keyForField(FIELD_FORCE_LOWEST_BITRATE), forceLowestBitrate); bundle.putBoolean( keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), forceHighestSupportedBitrate); - bundle.putParcelableArrayList( - keyForField(FIELD_SELECTION_OVERRIDE_KEYS), - toBundleArrayList(trackSelectionOverrides.keySet())); - bundle.putParcelableArrayList( - keyForField(FIELD_SELECTION_OVERRIDE_VALUES), - toBundleArrayList(trackSelectionOverrides.values())); + bundle.putBundle( + keyForField(FIELD_SELECTION_OVERRIDE_KEYS), trackSelectionOverrides.toBundle()); bundle.putIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE), Ints.toArray(disabledTrackTypes)); return bundle; diff --git a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java new file mode 100644 index 0000000000..2d2a27134b --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionOverridesTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.trackselection; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; +import com.google.common.collect.ImmutableList; +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +// packages.bara.sky: import com.google.android.exoplayer2.util.MimeTypes; +// packages.bara.sky: import com.google.android.exoplayer2.Bundleable; +// packages.bara.sky: import com.google.android.exoplayer2.C; +// packages.bara.sky: import com.google.android.exoplayer2.Format; + +/** Unit tests for {@link TrackSelectionOverrides}. */ +@RunWith(AndroidJUnit4.class) +public final class TrackSelectionOverridesTest { + + public static final TrackGroup AAC_TRACK_GROUP = + new TrackGroup(new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AAC).build()); + + private static TrackGroup newTrackGroupWithIds(int... ids) { + return new TrackGroup( + Arrays.stream(ids) + .mapToObj(id -> new Format.Builder().setId(id).build()) + .toArray(Format[]::new)); + } + + @Test + public void newTrackSelectionOverride_withJustTrackGroup_selectsAllTracks() { + TrackSelectionOverride trackSelectionOverride = + new TrackSelectionOverride(newTrackGroupWithIds(1, 2)); + + assertThat(trackSelectionOverride.trackGroup).isEqualTo(newTrackGroupWithIds(1, 2)); + assertThat(trackSelectionOverride.trackIndexes).containsExactly(0, 1).inOrder(); + } + + @Test + public void newTrackSelectionOverride_withTracks_selectsOnlySpecifiedTracks() { + TrackSelectionOverride trackSelectionOverride = + new TrackSelectionOverride(newTrackGroupWithIds(1, 2), ImmutableList.of(1)); + + assertThat(trackSelectionOverride.trackGroup).isEqualTo(newTrackGroupWithIds(1, 2)); + assertThat(trackSelectionOverride.trackIndexes).containsExactly(1); + } + + @Test + public void newTrackSelectionOverride_with0Tracks_selectsAllSpecifiedTracks() { + TrackSelectionOverride trackSelectionOverride = + new TrackSelectionOverride(newTrackGroupWithIds(1, 2), ImmutableList.of()); + + assertThat(trackSelectionOverride.trackGroup).isEqualTo(newTrackGroupWithIds(1, 2)); + assertThat(trackSelectionOverride.trackIndexes).isEmpty(); + } + + @Test + public void newTrackSelectionOverride_withInvalidIndex_throws() { + assertThrows( + IndexOutOfBoundsException.class, + () -> new TrackSelectionOverride(newTrackGroupWithIds(1, 2), ImmutableList.of(2))); + } + + @Test + public void roundTripViaBundle_withOverrides_yieldsEqualInstance() { + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder() + .setOverrideForType( + new TrackSelectionOverride(newTrackGroupWithIds(3, 4), ImmutableList.of(1))) + .addOverride(new TrackSelectionOverride(newTrackGroupWithIds(5, 6))) + .build(); + + TrackSelectionOverrides fromBundle = + TrackSelectionOverrides.CREATOR.fromBundle(trackSelectionOverrides.toBundle()); + + assertThat(fromBundle).isEqualTo(trackSelectionOverrides); + assertThat(fromBundle.asList()).isEqualTo(trackSelectionOverrides.asList()); + } + + @Test + public void builder_byDefault_isEmpty() { + TrackSelectionOverrides trackSelectionOverrides = new TrackSelectionOverrides.Builder().build(); + + assertThat(trackSelectionOverrides.asList()).isEmpty(); + assertThat(trackSelectionOverrides).isEqualTo(TrackSelectionOverrides.EMPTY); + } + + @Test + public void addOverride_onDifferentGroups_addsOverride() { + TrackSelectionOverride override1 = new TrackSelectionOverride(newTrackGroupWithIds(1)); + TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(2)); + + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder().addOverride(override1).addOverride(override2).build(); + + assertThat(trackSelectionOverrides.asList()).containsExactly(override1, override2); + assertThat(trackSelectionOverrides.getOverride(override1.trackGroup)).isEqualTo(override1); + assertThat(trackSelectionOverrides.getOverride(override2.trackGroup)).isEqualTo(override2); + } + + @Test + public void addOverride_onSameGroup_replacesOverride() { + TrackGroup trackGroup = newTrackGroupWithIds(1, 2, 3); + TrackSelectionOverride override1 = + new TrackSelectionOverride(trackGroup, /* trackIndexes= */ ImmutableList.of(0)); + TrackSelectionOverride override2 = + new TrackSelectionOverride(trackGroup, /* trackIndexes= */ ImmutableList.of(1)); + + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder().addOverride(override1).addOverride(override2).build(); + + assertThat(trackSelectionOverrides.asList()).containsExactly(override2); + assertThat(trackSelectionOverrides.getOverride(override2.trackGroup)).isEqualTo(override2); + } + + @Test + public void setOverrideForType_onSameType_replacesOverride() { + TrackSelectionOverride override1 = new TrackSelectionOverride(newTrackGroupWithIds(1)); + TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(2)); + + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder() + .setOverrideForType(override1) + .setOverrideForType(override2) + .build(); + + assertThat(trackSelectionOverrides.asList()).containsExactly(override2); + assertThat(trackSelectionOverrides.getOverride(override2.trackGroup)).isEqualTo(override2); + } + + @Test + public void clearOverridesOfType_ofTypeAudio_removesAudioOverride() { + TrackSelectionOverride override1 = new TrackSelectionOverride(AAC_TRACK_GROUP); + TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(1)); + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder() + .addOverride(override1) + .addOverride(override2) + .clearOverridesOfType(C.TRACK_TYPE_AUDIO) + .build(); + + assertThat(trackSelectionOverrides.asList()).containsExactly(override2); + assertThat(trackSelectionOverrides.getOverride(override2.trackGroup)).isEqualTo(override2); + } + + @Test + public void clearOverride_ofTypeGroup_removesOverride() { + TrackSelectionOverride override1 = new TrackSelectionOverride(AAC_TRACK_GROUP); + TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(1)); + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder() + .addOverride(override1) + .addOverride(override2) + .clearOverride(override2.trackGroup) + .build(); + + assertThat(trackSelectionOverrides.asList()).containsExactly(override1); + assertThat(trackSelectionOverrides.getOverride(override1.trackGroup)).isEqualTo(override1); + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java index 701d88d0e6..075898d4d0 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionParametersTest.java @@ -23,16 +23,16 @@ import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; -import com.google.android.exoplayer2.trackselection.TrackSelectionParameters.TrackSelectionOverride; +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.util.MimeTypes; -import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.junit.Test; import org.junit.runner.RunWith; /** Tests for {@link TrackSelectionParameters}. */ @RunWith(AndroidJUnit4.class) -public class TrackSelectionParametersTest { +public final class TrackSelectionParametersTest { @Test public void defaultValue_withoutChange_isAsExpected() { @@ -64,16 +64,22 @@ public class TrackSelectionParametersTest { // General assertThat(parameters.forceLowestBitrate).isFalse(); assertThat(parameters.forceHighestSupportedBitrate).isFalse(); - assertThat(parameters.trackSelectionOverrides).isEmpty(); + assertThat(parameters.trackSelectionOverrides.asList()).isEmpty(); assertThat(parameters.disabledTrackTypes).isEmpty(); } @Test public void parametersSet_fromDefault_isAsExpected() { - ImmutableMap trackSelectionOverrides = - ImmutableMap.of( - new TrackGroup(new Format.Builder().build()), - new TrackSelectionOverride(/* tracks= */ ImmutableSet.of(2, 3))); + TrackSelectionOverrides trackSelectionOverrides = + new TrackSelectionOverrides.Builder() + .addOverride(new TrackSelectionOverride(new TrackGroup(new Format.Builder().build()))) + .addOverride( + new TrackSelectionOverride( + new TrackGroup( + new Format.Builder().setId(4).build(), + new Format.Builder().setId(5).build()), + /* trackIndexes= */ ImmutableList.of(1))) + .build(); TrackSelectionParameters parameters = TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT .buildUpon() diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 40814ceffd..3e0ed7e887 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -39,7 +39,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.TrackSelectionParameters.TrackSelectionOverride; +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.BundleableUtil; import com.google.android.exoplayer2.util.Util; @@ -611,7 +611,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { @Override public ParametersBuilder setTrackSelectionOverrides( - Map trackSelectionOverrides) { + TrackSelectionOverrides trackSelectionOverrides) { super.setTrackSelectionOverrides(trackSelectionOverrides); return this; } @@ -710,7 +710,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { * @param groups The {@link TrackGroupArray} for which the override should be applied. * @param override The override. * @return This builder. + * @deprecated Use {@link TrackSelectionParameters.Builder#setTrackSelectionOverrides}. */ + @Deprecated public final ParametersBuilder setSelectionOverride( int rendererIndex, TrackGroupArray groups, @Nullable SelectionOverride override) { Map overrides = @@ -733,7 +735,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { * @param rendererIndex The renderer index. * @param groups The {@link TrackGroupArray} for which the override should be cleared. * @return This builder. + * @deprecated Use {@link TrackSelectionParameters.Builder#setTrackSelectionOverrides}. */ + @Deprecated public final ParametersBuilder clearSelectionOverride( int rendererIndex, TrackGroupArray groups) { Map overrides = @@ -754,7 +758,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { * * @param rendererIndex The renderer index. * @return This builder. + * @deprecated Use {@link TrackSelectionParameters.Builder#setTrackSelectionOverrides}. */ + @Deprecated public final ParametersBuilder clearSelectionOverrides(int rendererIndex) { Map overrides = selectionOverrides.get(rendererIndex); @@ -770,7 +776,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { * Clears all track selection overrides for all renderers. * * @return This builder. + * @deprecated Use {@link TrackSelectionParameters.Builder#setTrackSelectionOverrides}. */ + @Deprecated public final ParametersBuilder clearSelectionOverrides() { if (selectionOverrides.size() == 0) { // Nothing to clear. @@ -1560,9 +1568,11 @@ public class DefaultTrackSelector extends MappingTrackSelector { for (int j = 0; j < rendererTrackGroups.length; j++) { TrackGroup trackGroup = rendererTrackGroups.get(j); @Nullable - TrackSelectionOverride overrideTracks = params.trackSelectionOverrides.get(trackGroup); + TrackSelectionOverride overrideTracks = + params.trackSelectionOverrides.getOverride(trackGroup); if (overrideTracks != null) { - return new ExoTrackSelection.Definition(trackGroup, Ints.toArray(overrideTracks.tracks)); + return new ExoTrackSelection.Definition( + trackGroup, Ints.toArray(overrideTracks.trackIndexes)); } } return currentDefinition; // No override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java index 42f0ca68a0..a06d15b5cf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java @@ -17,21 +17,11 @@ package com.google.android.exoplayer2.trackselection; import android.os.SystemClock; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.TracksInfo; -import com.google.android.exoplayer2.TracksInfo.TrackGroupInfo; -import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; import com.google.android.exoplayer2.trackselection.ExoTrackSelection.Definition; -import com.google.android.exoplayer2.trackselection.TrackSelectionParameters.TrackSelectionOverride; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; -import com.google.android.exoplayer2.util.MimeTypes; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import java.util.Map; import org.checkerframework.checker.nullness.compatqual.NullableType; -import org.checkerframework.dataflow.qual.Pure; /** Track selection related utility methods. */ public final class TrackSelectionUtil { @@ -134,67 +124,4 @@ public final class TrackSelectionUtil { numberOfTracks, numberOfExcludedTracks); } - - /** - * 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 - public static ImmutableMap forceTrackSelection( - ImmutableMap trackSelectionOverrides, - TracksInfo tracksInfo, - int forcedTrackGroupIndex, - TrackSelectionOverride forcedTrackSelectionOverride) { - @C.TrackType - int trackType = tracksInfo.getTrackGroupInfos().get(forcedTrackGroupIndex).getTrackType(); - ImmutableMap.Builder overridesBuilder = - new ImmutableMap.Builder<>(); - // Maintain overrides for the other track types. - for (Map.Entry entry : trackSelectionOverrides.entrySet()) { - if (MimeTypes.getTrackType(entry.getKey().getFormat(0).sampleMimeType) != trackType) { - overridesBuilder.put(entry); - } - } - ImmutableList trackGroupInfos = tracksInfo.getTrackGroupInfos(); - for (int i = 0; i < trackGroupInfos.size(); i++) { - TrackGroup trackGroup = trackGroupInfos.get(i).getTrackGroup(); - if (i == forcedTrackGroupIndex) { - overridesBuilder.put(trackGroup, forcedTrackSelectionOverride); - } else { - overridesBuilder.put(trackGroup, TrackSelectionOverride.DISABLE); - } - } - return overridesBuilder.build(); - } - - /** - * Removes all {@link TrackSelectionOverride overrides} associated with {@link TrackGroup - * TrackGroups} of type {@code trackType}. - * - * @param trackType The {@link C.TrackType} of all overrides to remove. - * @param trackSelectionOverrides The current {@link TrackSelectionOverride overrides}. - * @return The updated {@link TrackSelectionOverride overrides}. - */ - @Pure - public static ImmutableMap - clearTrackSelectionOverridesForType( - @C.TrackType int trackType, - ImmutableMap trackSelectionOverrides) { - ImmutableMap.Builder overridesBuilder = - ImmutableMap.builder(); - for (Map.Entry entry : trackSelectionOverrides.entrySet()) { - if (MimeTypes.getTrackType(entry.getKey().getFormat(0).sampleMimeType) != trackType) { - overridesBuilder.put(entry); - } - } - return overridesBuilder.build(); - } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index e04a0530a7..0a12b61a12 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -46,13 +46,12 @@ import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.ParametersBuilder; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; -import com.google.android.exoplayer2.trackselection.TrackSelectionParameters.TrackSelectionOverride; +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.trackselection.TrackSelector.InvalidationListener; import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; 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; @@ -158,12 +157,15 @@ public final class DefaultTrackSelectorTest { /** Tests that an empty override clears a track selection. */ @Test - public void selectTracks_withNullOverride_clearsTrackSelection() throws ExoPlaybackException { + public void selectTracks_withOverrideWithoutTracks_clearsTrackSelection() + throws ExoPlaybackException { trackSelector.setParameters( trackSelector .buildUponParameters() .setTrackSelectionOverrides( - ImmutableMap.of(VIDEO_TRACK_GROUP, new TrackSelectionOverride(ImmutableSet.of())))); + new TrackSelectionOverrides.Builder() + .addOverride(new TrackSelectionOverride(VIDEO_TRACK_GROUP, ImmutableList.of())) + .build())); TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE); @@ -210,8 +212,11 @@ public final class DefaultTrackSelectorTest { trackSelector .buildUponParameters() .setTrackSelectionOverrides( - ImmutableMap.of( - new TrackGroup(VIDEO_FORMAT, VIDEO_FORMAT), TrackSelectionOverride.DISABLE))); + new TrackSelectionOverrides.Builder() + .setOverrideForType( + new TrackSelectionOverride( + new TrackGroup(VIDEO_FORMAT, VIDEO_FORMAT), ImmutableList.of())) + .build())); TrackSelectorResult result = trackSelector.selectTracks( @@ -1874,9 +1879,12 @@ public final class DefaultTrackSelectorTest { .setRendererDisabled(3, true) .setRendererDisabled(5, false) .setTrackSelectionOverrides( - ImmutableMap.of( - AUDIO_TRACK_GROUP, - new TrackSelectionOverride(/* tracks= */ ImmutableSet.of(3, 4, 5)))) + new TrackSelectionOverrides.Builder() + .setOverrideForType( + new TrackSelectionOverride( + new TrackGroup(AUDIO_FORMAT, AUDIO_FORMAT, AUDIO_FORMAT, AUDIO_FORMAT), + /* trackIndexes= */ ImmutableList.of(0, 2, 3))) + .build()) .setDisabledTrackTypes(ImmutableSet.of(C.TRACK_TYPE_AUDIO)) .build(); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java index 95dee700e5..30ca26f4f9 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java @@ -32,9 +32,8 @@ import static com.google.android.exoplayer2.Player.EVENT_SEEK_FORWARD_INCREMENT_ import static com.google.android.exoplayer2.Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_TIMELINE_CHANGED; import static com.google.android.exoplayer2.Player.EVENT_TRACKS_CHANGED; -import static com.google.android.exoplayer2.trackselection.TrackSelectionUtil.clearTrackSelectionOverridesForType; -import static com.google.android.exoplayer2.trackselection.TrackSelectionUtil.forceTrackSelection; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Util.castNonNull; import android.annotation.SuppressLint; import android.content.Context; @@ -68,13 +67,13 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.TracksInfo.TrackGroupInfo; import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides; +import com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; -import com.google.android.exoplayer2.trackselection.TrackSelectionParameters.TrackSelectionOverride; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.util.ArrayList; import java.util.Arrays; @@ -82,8 +81,8 @@ import java.util.Collections; import java.util.Formatter; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; +import org.checkerframework.dataflow.qual.Pure; /** * A view for controlling {@link Player} instances. @@ -2032,14 +2031,8 @@ public class StyledPlayerControlView extends FrameLayout { // Audio track selection option includes "Auto" at the top. holder.textView.setText(R.string.exo_track_selection_auto); // hasSelectionOverride is true means there is an explicit track selection, not "Auto". - boolean hasSelectionOverride = false; TrackSelectionParameters parameters = checkNotNull(player).getTrackSelectionParameters(); - for (int i = 0; i < tracks.size(); i++) { - if (parameters.trackSelectionOverrides.containsKey(tracks.get(i).trackGroup)) { - hasSelectionOverride = true; - break; - } - } + boolean hasSelectionOverride = hasSelectionOverride(parameters.trackSelectionOverrides); holder.checkView.setVisibility(hasSelectionOverride ? INVISIBLE : VISIBLE); holder.itemView.setOnClickListener( v -> { @@ -2048,15 +2041,18 @@ public class StyledPlayerControlView extends FrameLayout { } TrackSelectionParameters trackSelectionParameters = player.getTrackSelectionParameters(); - // Remove all audio overrides. - ImmutableMap trackSelectionOverrides = - clearTrackSelectionOverridesForType( - C.TRACK_TYPE_AUDIO, trackSelectionParameters.trackSelectionOverrides); - player.setTrackSelectionParameters( + TrackSelectionOverrides trackSelectionOverrides = trackSelectionParameters + .trackSelectionOverrides .buildUpon() - .setTrackSelectionOverrides(trackSelectionOverrides) - .build()); + .clearOverridesOfType(C.TRACK_TYPE_AUDIO) + .build(); + castNonNull(player) + .setTrackSelectionParameters( + trackSelectionParameters + .buildUpon() + .setTrackSelectionOverrides(trackSelectionOverrides) + .build()); settingsAdapter.setSubTextAtPosition( SETTINGS_AUDIO_TRACK_SELECTION_POSITION, getResources().getString(R.string.exo_track_selection_auto)); @@ -2064,6 +2060,21 @@ public class StyledPlayerControlView extends FrameLayout { }); } + private boolean hasSelectionOverride(TrackSelectionOverrides trackSelectionOverrides) { + int previousTrackGroupIndex = C.INDEX_UNSET; + for (int i = 0; i < tracks.size(); i++) { + TrackInformation track = tracks.get(i); + if (track.trackGroupIndex == previousTrackGroupIndex) { + continue; + } + if (trackSelectionOverrides.getOverride(track.trackGroup) != null) { + return true; + } + previousTrackGroupIndex = track.trackGroupIndex; + } + return false; + } + @Override public void onTrackSelection(String subtext) { settingsAdapter.setSubTextAtPosition(SETTINGS_AUDIO_TRACK_SELECTION_POSITION, subtext); @@ -2075,9 +2086,10 @@ public class StyledPlayerControlView extends FrameLayout { boolean hasSelectionOverride = false; for (int i = 0; i < trackInformations.size(); i++) { if (checkNotNull(player) - .getTrackSelectionParameters() - .trackSelectionOverrides - .containsKey(trackInformations.get(i).trackGroup)) { + .getTrackSelectionParameters() + .trackSelectionOverrides + .getOverride(trackInformations.get(i).trackGroup) + != null) { hasSelectionOverride = true; break; } @@ -2140,9 +2152,10 @@ public class StyledPlayerControlView extends FrameLayout { TrackInformation track = tracks.get(position - 1); boolean explicitlySelected = checkNotNull(player) - .getTrackSelectionParameters() - .trackSelectionOverrides - .containsKey(track.trackGroup) + .getTrackSelectionParameters() + .trackSelectionOverrides + .getOverride(track.trackGroup) + != null && track.isSelected(); holder.textView.setText(track.trackName); holder.checkView.setVisibility(explicitlySelected ? VISIBLE : INVISIBLE); @@ -2153,12 +2166,13 @@ public class StyledPlayerControlView extends FrameLayout { } TrackSelectionParameters trackSelectionParameters = player.getTrackSelectionParameters(); - Map overrides = + TrackSelectionOverrides overrides = forceTrackSelection( trackSelectionParameters.trackSelectionOverrides, track.tracksInfo, track.trackGroupIndex, - new TrackSelectionOverride(ImmutableSet.of(track.trackIndex))); + new TrackSelectionOverride( + track.trackGroup, ImmutableList.of(track.trackIndex))); checkNotNull(player) .setTrackSelectionParameters( trackSelectionParameters @@ -2196,4 +2210,41 @@ 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(); + } }