Skip to content

Commit 1f3f22a

Browse files
krocardojw28
authored andcommitted
Encapsulate TrackSelectionOverrides in its own class
The current API exposes an `ImmutableMap` of `TrackGroup` -> `TrackSelectionOverride`. This has several disadvantages: - A difficult to use API for mutation (`ImmutableMap.Builder` doesn't support key removal). - There is no track selection specific methods, how the generic map API mapps to the selection override is not complex but to obvious for a casual reader. - The internal data type is exposed, making internal refactor difficult. This was done to have the API ready as quick as possible. When transitioning the clients to the map API in <unknown commit>, it became clear that the map API was too verbose and not mapping to the clients needs, so utility methods were added to make operations clearer and more concise. Nevertheless, having to use utility method to use easily and correctly an API is not the sign of a good API. This cl refactors the track selection API for several improvements: - Add a type `TrackSelectionParameters` that encapsulate the internal data structure (map currently). - For iteration, expose as a list. - Add a `Builder` for easy mutable operations. - Add track selection specific methods to avoid having utilities functions. - Those operations are the same as `DefaultTrackSelector.Parameters` for easier migration. (`setOverride` was renamed to `addOverride`) - Move `TrackSelection` classes outside of `TrackSelectionParameters` as their own top level classes. The migration of the client code is straightforward as most of it were already using the previously mentioned utility functions that are now native methods. The full migration has not been done yet, and is pending on this cl approval. PiperOrigin-RevId: 405362719
1 parent 2ee7207 commit 1f3f22a

File tree

8 files changed

+614
-251
lines changed

8 files changed

+614
-251
lines changed
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
/*
2+
* Copyright (C) 2021 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.android.exoplayer2.trackselection;
17+
18+
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
19+
import static com.google.android.exoplayer2.util.BundleableUtil.fromBundleNullableList;
20+
import static com.google.android.exoplayer2.util.BundleableUtil.toBundleArrayList;
21+
import static java.util.Collections.max;
22+
import static java.util.Collections.min;
23+
24+
import android.os.Bundle;
25+
import androidx.annotation.IntDef;
26+
import androidx.annotation.Nullable;
27+
import com.google.android.exoplayer2.trackselection.C.TrackType;
28+
import com.google.common.collect.ImmutableList;
29+
import com.google.common.collect.ImmutableMap;
30+
import com.google.common.primitives.Ints;
31+
import java.lang.annotation.Documented;
32+
import java.lang.annotation.Retention;
33+
import java.lang.annotation.RetentionPolicy;
34+
import java.util.HashMap;
35+
import java.util.Iterator;
36+
import java.util.List;
37+
import java.util.Map;
38+
39+
/**
40+
* Forces the selection of the specified tracks in {@link TrackGroup TrackGroups}.
41+
*
42+
* <p>Each {@link TrackSelectionOverride override} only affects the selection of tracks of that
43+
* {@link TrackType type}. For example overriding the selection of an {@link C#TRACK_TYPE_AUDIO
44+
* audio} {@link TrackGroup} will not affect the selection of {@link C#TRACK_TYPE_VIDEO video} or
45+
* {@link C#TRACK_TYPE_TEXT text} tracks.
46+
*
47+
* <p>If multiple {@link TrackGroup TrackGroups} of the same {@link TrackType} are overridden, which
48+
* tracks will be selected depend on the player capabilities. For example, by default {@code
49+
* ExoPlayer} doesn't support selecting more than one {@link TrackGroup} per {@link TrackType}.
50+
*
51+
* <p>Overrides of {@link TrackGroup} that are not currently available are ignored. For example,
52+
* when the player transitions to the next {@link MediaItem} in a playlist, any overrides of the
53+
* previous {@link MediaItem} are ignored.
54+
*
55+
* @see TrackSelectionParameters#trackSelectionOverrides
56+
*/
57+
public final class TrackSelectionOverrides implements Bundleable {
58+
59+
/** Builder for {@link TrackSelectionOverrides}. */
60+
public static final class Builder {
61+
// Cannot use ImmutableMap.Builder as it doesn't support removing entries.
62+
private final HashMap<TrackGroup, TrackSelectionOverride> overrides;
63+
64+
/** Creates an builder with no {@link TrackSelectionOverride}. */
65+
public Builder() {
66+
overrides = new HashMap<>();
67+
}
68+
69+
private Builder(Map<TrackGroup, TrackSelectionOverride> overrides) {
70+
this.overrides = new HashMap<>(overrides);
71+
}
72+
73+
/** Adds an override for the provided {@link TrackGroup}. */
74+
public Builder addOverride(TrackSelectionOverride override) {
75+
overrides.put(override.trackGroup, override);
76+
return this;
77+
}
78+
79+
/** Removes the override associated with the provided {@link TrackGroup} if present. */
80+
public Builder clearOverride(TrackGroup trackGroup) {
81+
overrides.remove(trackGroup);
82+
return this;
83+
}
84+
85+
/** Set the override for the type of the provided {@link TrackGroup}. */
86+
public Builder setOverrideForType(TrackSelectionOverride override) {
87+
clearOverridesOfType(override.getTrackType());
88+
overrides.put(override.trackGroup, override);
89+
return this;
90+
}
91+
92+
/**
93+
* Remove any override associated with {@link TrackGroup TrackGroups} of type {@code trackType}.
94+
*/
95+
public Builder clearOverridesOfType(@TrackType int trackType) {
96+
for (Iterator<TrackSelectionOverride> it = overrides.values().iterator(); it.hasNext(); ) {
97+
TrackSelectionOverride trackSelectionOverride = it.next();
98+
if (trackSelectionOverride.getTrackType() == trackType) {
99+
it.remove();
100+
}
101+
}
102+
return this;
103+
}
104+
105+
/** Returns a new {@link TrackSelectionOverrides} instance with the current builder values. */
106+
public TrackSelectionOverrides build() {
107+
return new TrackSelectionOverrides(overrides);
108+
}
109+
}
110+
111+
/**
112+
* Forces the selection of {@link #trackIndexes} for a {@link TrackGroup}.
113+
*
114+
* <p>If multiple {link #tracks} are overridden, as many as possible will be selected depending on
115+
* the player capabilities.
116+
*
117+
* <p>If a {@link TrackSelectionOverride} has no tracks ({@code tracks.isEmpty()}), no tracks will
118+
* be played. This is similar to {@link TrackSelectionParameters#disabledTrackTypes}, except it
119+
* will only affect the playback of the associated {@link TrackGroup}. For example, if the only
120+
* {@link C#TRACK_TYPE_VIDEO} {@link TrackGroup} is associated with no tracks, no video will play
121+
* until the next video starts.
122+
*/
123+
public static final class TrackSelectionOverride implements Bundleable {
124+
125+
/** The {@link TrackGroup} whose {@link #trackIndexes} are forced to be selected. */
126+
public final TrackGroup trackGroup;
127+
/** The index of tracks in a {@link TrackGroup} to be selected. */
128+
public final ImmutableList<Integer> trackIndexes;
129+
130+
/** Constructs an instance to force all tracks in {@code trackGroup} to be selected. */
131+
public TrackSelectionOverride(TrackGroup trackGroup) {
132+
this.trackGroup = trackGroup;
133+
ImmutableList.Builder<Integer> builder = new ImmutableList.Builder<>();
134+
for (int i = 0; i < trackGroup.length; i++) {
135+
builder.add(i);
136+
}
137+
this.trackIndexes = builder.build();
138+
}
139+
140+
/**
141+
* Constructs an instance to force {@code trackIndexes} in {@code trackGroup} to be selected.
142+
*
143+
* @param trackGroup The {@link TrackGroup} for which to override the track selection.
144+
* @param trackIndexes The indexes of the tracks in the {@link TrackGroup} to select.
145+
*/
146+
public TrackSelectionOverride(TrackGroup trackGroup, List<Integer> trackIndexes) {
147+
if (!trackIndexes.isEmpty()) {
148+
if (min(trackIndexes) < 0 || max(trackIndexes) >= trackGroup.length) {
149+
throw new IndexOutOfBoundsException();
150+
}
151+
}
152+
this.trackGroup = trackGroup;
153+
this.trackIndexes = ImmutableList.copyOf(trackIndexes);
154+
}
155+
156+
@Override
157+
public boolean equals(@Nullable Object obj) {
158+
if (this == obj) {
159+
return true;
160+
}
161+
if (obj == null || getClass() != obj.getClass()) {
162+
return false;
163+
}
164+
TrackSelectionOverride that = (TrackSelectionOverride) obj;
165+
return trackGroup.equals(that.trackGroup) && trackIndexes.equals(that.trackIndexes);
166+
}
167+
168+
@Override
169+
public int hashCode() {
170+
return trackGroup.hashCode() + 31 * trackIndexes.hashCode();
171+
}
172+
173+
private @TrackType int getTrackType() {
174+
return MimeTypes.getTrackType(trackGroup.getFormat(0).sampleMimeType);
175+
}
176+
177+
// Bundleable implementation
178+
179+
@Documented
180+
@Retention(RetentionPolicy.SOURCE)
181+
@IntDef({
182+
FIELD_TRACK_GROUP,
183+
FIELD_TRACKS,
184+
})
185+
private @interface FieldNumber {}
186+
187+
private static final int FIELD_TRACK_GROUP = 0;
188+
private static final int FIELD_TRACKS = 1;
189+
190+
@Override
191+
public Bundle toBundle() {
192+
Bundle bundle = new Bundle();
193+
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), trackGroup.toBundle());
194+
bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(trackIndexes));
195+
return bundle;
196+
}
197+
198+
/** Object that can restore {@code TrackSelectionOverride} from a {@link Bundle}. */
199+
public static final Creator<TrackSelectionOverride> CREATOR =
200+
bundle -> {
201+
@Nullable Bundle trackGroupBundle = bundle.getBundle(keyForField(FIELD_TRACK_GROUP));
202+
checkNotNull(trackGroupBundle); // Mandatory as there are no reasonable defaults.
203+
TrackGroup trackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle);
204+
@Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS));
205+
if (tracks == null) {
206+
return new TrackSelectionOverride(trackGroup);
207+
}
208+
return new TrackSelectionOverride(trackGroup, Ints.asList(tracks));
209+
};
210+
211+
private static String keyForField(@FieldNumber int field) {
212+
return Integer.toString(field, Character.MAX_RADIX);
213+
}
214+
}
215+
216+
/** Empty {@code TrackSelectionOverrides}, where no track selection is overridden. */
217+
public static final TrackSelectionOverrides EMPTY =
218+
new TrackSelectionOverrides(ImmutableMap.of());
219+
220+
private final ImmutableMap<TrackGroup, TrackSelectionOverride> overrides;
221+
222+
private TrackSelectionOverrides(Map<TrackGroup, TrackSelectionOverride> overrides) {
223+
this.overrides = ImmutableMap.copyOf(overrides);
224+
}
225+
226+
/** Returns a {@link Builder} initialized with the values of this instance. */
227+
public Builder buildUpon() {
228+
return new Builder(overrides);
229+
}
230+
231+
/** Returns all {@link TrackSelectionOverride} contained. */
232+
public ImmutableList<TrackSelectionOverride> asList() {
233+
return ImmutableList.copyOf(overrides.values());
234+
}
235+
236+
/**
237+
* Returns the {@link TrackSelectionOverride} of the provided {@link TrackGroup} or {@code null}
238+
* if there is none.
239+
*/
240+
@Nullable
241+
public TrackSelectionOverride getOverride(TrackGroup trackGroup) {
242+
return overrides.get(trackGroup);
243+
}
244+
245+
@Override
246+
public boolean equals(@Nullable Object obj) {
247+
if (this == obj) {
248+
return true;
249+
}
250+
if (obj == null || getClass() != obj.getClass()) {
251+
return false;
252+
}
253+
TrackSelectionOverrides that = (TrackSelectionOverrides) obj;
254+
return overrides.equals(that.overrides);
255+
}
256+
257+
@Override
258+
public int hashCode() {
259+
return overrides.hashCode();
260+
}
261+
262+
// Bundleable implementation
263+
264+
@Documented
265+
@Retention(RetentionPolicy.SOURCE)
266+
@IntDef({
267+
FIELD_OVERRIDES,
268+
})
269+
private @interface FieldNumber {}
270+
271+
private static final int FIELD_OVERRIDES = 0;
272+
273+
@Override
274+
public Bundle toBundle() {
275+
Bundle bundle = new Bundle();
276+
bundle.putParcelableArrayList(
277+
keyForField(FIELD_OVERRIDES), toBundleArrayList(overrides.values()));
278+
return bundle;
279+
}
280+
281+
/** Object that can restore {@code TrackSelectionOverrides} from a {@link Bundle}. */
282+
public static final Creator<TrackSelectionOverrides> CREATOR =
283+
bundle -> {
284+
List<TrackSelectionOverride> trackSelectionOverrides =
285+
fromBundleNullableList(
286+
TrackSelectionOverride.CREATOR,
287+
bundle.getParcelableArrayList(keyForField(FIELD_OVERRIDES)),
288+
ImmutableList.of());
289+
ImmutableMap.Builder<TrackGroup, TrackSelectionOverride> builder =
290+
new ImmutableMap.Builder<>();
291+
for (int i = 0; i < trackSelectionOverrides.size(); i++) {
292+
TrackSelectionOverride trackSelectionOverride = trackSelectionOverrides.get(i);
293+
builder.put(trackSelectionOverride.trackGroup, trackSelectionOverride);
294+
}
295+
return new TrackSelectionOverrides(builder.build());
296+
};
297+
298+
private static String keyForField(@FieldNumber int field) {
299+
return Integer.toString(field, Character.MAX_RADIX);
300+
}
301+
}

0 commit comments

Comments
 (0)