Decouple UI module from ExoPlayer

This change rewrites the UI module's track selection
components to depend on the Player API, allowing us to
finally remove the UI module's dependency on ExoPlayer
as a concrete player implementation.

PiperOrigin-RevId: 432989318
This commit is contained in:
olly 2022-03-07 19:06:17 +00:00 committed by Ian Baker
parent 661d7ddedb
commit b2a5298e27
7 changed files with 308 additions and 348 deletions

View File

@ -29,6 +29,12 @@
`SubtitleConfiguration` field and fall back to the `Factory` value if
it's not set
([#10016](https://github.com/google/ExoPlayer/issues/10016)).
* UI:
* Rewrite `TrackSelectionView` and `TrackSelectionDialogBuilder` to work
with the `Player` interface rather than `ExoPlayer`. This allows the
views to be used with other `Player` implementations, and removes the
dependency from the UI module to the ExoPlayer module. This is a
breaking change.
* RTSP:
* Add RTP reader for HEVC
([#36](https://github.com/androidx/media/pull/36)).

View File

@ -29,6 +29,7 @@ import androidx.fragment.app.FragmentManager;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.TracksInfo;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
@ -43,8 +44,8 @@ import com.google.android.exoplayer2.offline.DownloadRequest;
import com.google.android.exoplayer2.offline.DownloadService;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
@ -69,7 +70,6 @@ public class DownloadTracker {
private final CopyOnWriteArraySet<Listener> listeners;
private final HashMap<Uri, Download> downloads;
private final DownloadIndex downloadIndex;
private final DefaultTrackSelector.Parameters trackSelectorParameters;
@Nullable private StartDownloadDialogHelper startDownloadDialogHelper;
@ -82,7 +82,6 @@ public class DownloadTracker {
listeners = new CopyOnWriteArraySet<>();
downloads = new HashMap<>();
downloadIndex = downloadManager.getDownloadIndex();
trackSelectorParameters = DownloadHelper.getDefaultTrackSelectorParameters(context);
downloadManager.addListener(new DownloadManagerListener());
loadDownloads();
}
@ -159,7 +158,7 @@ public class DownloadTracker {
private final class StartDownloadDialogHelper
implements DownloadHelper.Callback,
DialogInterface.OnClickListener,
TrackSelectionDialog.TrackSelectionListener,
DialogInterface.OnDismissListener {
private final FragmentManager fragmentManager;
@ -167,7 +166,6 @@ public class DownloadTracker {
private final MediaItem mediaItem;
private TrackSelectionDialog trackSelectionDialog;
private MappedTrackInfo mappedTrackInfo;
private WidevineOfflineLicenseFetchTask widevineOfflineLicenseFetchTask;
@Nullable private byte[] keySetId;
@ -237,21 +235,13 @@ public class DownloadTracker {
Log.e(TAG, logMessage, e);
}
// DialogInterface.OnClickListener implementation.
// TrackSelectionListener implementation.
@Override
public void onClick(DialogInterface dialog, int which) {
public void onTracksSelected(TrackSelectionParameters trackSelectionParameters) {
for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) {
downloadHelper.clearTrackSelections(periodIndex);
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
if (!trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i)) {
downloadHelper.addTrackSelectionForSingleRenderer(
periodIndex,
/* rendererIndex= */ i,
trackSelectorParameters,
trackSelectionDialog.getOverrides(/* rendererIndex= */ i));
}
}
downloadHelper.addTrackSelection(periodIndex, trackSelectionParameters);
}
DownloadRequest downloadRequest = buildDownloadRequest();
if (downloadRequest.streamKeys.isEmpty()) {
@ -316,21 +306,21 @@ public class DownloadTracker {
return;
}
mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
if (!TrackSelectionDialog.willHaveContent(mappedTrackInfo)) {
TracksInfo tracksInfo = downloadHelper.getTracksInfo(/* periodIndex= */ 0);
if (!TrackSelectionDialog.willHaveContent(tracksInfo)) {
Log.d(TAG, "No dialog content. Downloading entire stream.");
startDownload();
downloadHelper.release();
return;
}
trackSelectionDialog =
TrackSelectionDialog.createForMappedTrackInfoAndParameters(
TrackSelectionDialog.createForTracksInfoAndParameters(
/* titleId= */ R.string.exo_download_description,
mappedTrackInfo,
trackSelectorParameters,
tracksInfo,
DownloadHelper.getDefaultTrackSelectorParameters(context),
/* allowAdaptiveSelections= */ false,
/* allowMultipleOverrides= */ true,
/* onClickListener= */ this,
/* onTracksSelectedListener= */ this,
/* onDismissListener= */ this);
trackSelectionDialog.show(fragmentManager, /* tag= */ null);
}

View File

@ -45,7 +45,7 @@ import com.google.android.exoplayer2.offline.DownloadRequest;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
import com.google.android.exoplayer2.ui.StyledPlayerControlView;
import com.google.android.exoplayer2.ui.StyledPlayerView;
import com.google.android.exoplayer2.upstream.DataSource;
@ -79,8 +79,7 @@ public class PlayerActivity extends AppCompatActivity
private Button selectTracksButton;
private DataSource.Factory dataSourceFactory;
private List<MediaItem> mediaItems;
private DefaultTrackSelector trackSelector;
private DefaultTrackSelector.Parameters trackSelectionParameters;
private TrackSelectionParameters trackSelectionParameters;
private DebugTextViewHelper debugViewHelper;
private TracksInfo lastSeenTracksInfo;
private boolean startAutoPlay;
@ -113,9 +112,8 @@ public class PlayerActivity extends AppCompatActivity
playerView.requestFocus();
if (savedInstanceState != null) {
// Restore as DefaultTrackSelector.Parameters in case ExoPlayer specific parameters were set.
trackSelectionParameters =
DefaultTrackSelector.Parameters.CREATOR.fromBundle(
TrackSelectionParameters.CREATOR.fromBundle(
savedInstanceState.getBundle(KEY_TRACK_SELECTION_PARAMETERS));
startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY);
startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX);
@ -127,8 +125,7 @@ public class PlayerActivity extends AppCompatActivity
adsLoaderStateBundle);
}
} else {
trackSelectionParameters =
new DefaultTrackSelector.ParametersBuilder(/* context= */ this).build();
trackSelectionParameters = new TrackSelectionParameters.Builder(/* context= */ this).build();
clearStartPosition();
}
}
@ -237,11 +234,11 @@ public class PlayerActivity extends AppCompatActivity
public void onClick(View view) {
if (view == selectTracksButton
&& !isShowingTrackSelectionDialog
&& TrackSelectionDialog.willHaveContent(trackSelector)) {
&& TrackSelectionDialog.willHaveContent(player)) {
isShowingTrackSelectionDialog = true;
TrackSelectionDialog trackSelectionDialog =
TrackSelectionDialog.createForTrackSelector(
trackSelector,
TrackSelectionDialog.createForPlayer(
player,
/* onDismissListener= */ dismissedDialog -> isShowingTrackSelectionDialog = false);
trackSelectionDialog.show(getSupportFragmentManager(), /* tag= */ null);
}
@ -277,13 +274,11 @@ public class PlayerActivity extends AppCompatActivity
RenderersFactory renderersFactory =
DemoUtil.buildRenderersFactory(/* context= */ this, preferExtensionDecoders);
trackSelector = new DefaultTrackSelector(/* context= */ this);
lastSeenTracksInfo = TracksInfo.EMPTY;
player =
new ExoPlayer.Builder(/* context= */ this)
.setRenderersFactory(renderersFactory)
.setMediaSourceFactory(createMediaSourceFactory())
.setTrackSelector(trackSelector)
.build();
player.setTrackSelectionParameters(trackSelectionParameters);
player.addListener(new PlayerEventListener());
@ -400,10 +395,7 @@ public class PlayerActivity extends AppCompatActivity
private void updateTrackSelectorParameters() {
if (player != null) {
// Until the demo app is fully migrated to TrackSelectionParameters, rely on ExoPlayer to use
// DefaultTrackSelector by default.
trackSelectionParameters =
(DefaultTrackSelector.Parameters) player.getTrackSelectionParameters();
trackSelectionParameters = player.getTrackSelectionParameters();
}
}
@ -424,8 +416,7 @@ public class PlayerActivity extends AppCompatActivity
// User controls
private void updateButtonVisibility() {
selectTracksButton.setEnabled(
player != null && TrackSelectionDialog.willHaveContent(trackSelector));
selectTracksButton.setEnabled(player != null && TrackSelectionDialog.willHaveContent(player));
}
private void showControls() {

View File

@ -32,20 +32,41 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.Player;
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.TrackSelectionOverride;
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
import com.google.android.exoplayer2.ui.TrackSelectionView;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.material.tabs.TabLayout;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Dialog to select tracks. */
public final class TrackSelectionDialog extends DialogFragment {
/** Called when tracks are selected. */
public interface TrackSelectionListener {
/**
* Called when tracks are selected.
*
* @param trackSelectionParameters A {@link TrackSelectionParameters} representing the selected
* tracks. Any manual selections are defined by {@link
* TrackSelectionParameters#disabledTrackTypes} and {@link
* TrackSelectionParameters#overrides}.
*/
void onTracksSelected(TrackSelectionParameters trackSelectionParameters);
}
public static final ImmutableList<Integer> SUPPORTED_TRACK_TYPES =
ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT);
private final SparseArray<TrackSelectionViewFragment> tabFragments;
private final ArrayList<Integer> tabTrackTypes;
@ -55,20 +76,19 @@ public final class TrackSelectionDialog extends DialogFragment {
/**
* Returns whether a track selection dialog will have content to display if initialized with the
* specified {@link DefaultTrackSelector} in its current state.
* specified {@link Player}.
*/
public static boolean willHaveContent(DefaultTrackSelector trackSelector) {
MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
return mappedTrackInfo != null && willHaveContent(mappedTrackInfo);
public static boolean willHaveContent(Player player) {
return willHaveContent(player.getCurrentTracksInfo());
}
/**
* Returns whether a track selection dialog will have content to display if initialized with the
* specified {@link MappedTrackInfo}.
* specified {@link TracksInfo}.
*/
public static boolean willHaveContent(MappedTrackInfo mappedTrackInfo) {
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
if (showTabForRenderer(mappedTrackInfo, i)) {
public static boolean willHaveContent(TracksInfo tracksInfo) {
for (TrackGroupInfo trackGroupInfo : tracksInfo.getTrackGroupInfos()) {
if (SUPPORTED_TRACK_TYPES.contains(trackGroupInfo.getTrackType())) {
return true;
}
}
@ -76,78 +96,67 @@ public final class TrackSelectionDialog extends DialogFragment {
}
/**
* Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be
* automatically updated when tracks are selected.
* Creates a dialog for a given {@link Player}, whose parameters will be automatically updated
* when tracks are selected.
*
* @param trackSelector The {@link DefaultTrackSelector}.
* @param player The {@link Player}.
* @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is
* dismissed.
*/
public static TrackSelectionDialog createForTrackSelector(
DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) {
MappedTrackInfo mappedTrackInfo =
Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog();
DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();
trackSelectionDialog.init(
/* titleId= */ R.string.track_selection_title,
mappedTrackInfo,
/* initialParameters = */ parameters,
public static TrackSelectionDialog createForPlayer(
Player player, DialogInterface.OnDismissListener onDismissListener) {
return createForTracksInfoAndParameters(
R.string.track_selection_title,
player.getCurrentTracksInfo(),
player.getTrackSelectionParameters(),
/* allowAdaptiveSelections= */ true,
/* allowMultipleOverrides= */ false,
/* onClickListener= */ (dialog, which) -> {
DefaultTrackSelector.ParametersBuilder builder = parameters.buildUpon();
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
builder
.clearSelectionOverrides(/* rendererIndex= */ i)
.setRendererDisabled(
/* rendererIndex= */ i,
trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i));
List<SelectionOverride> overrides =
trackSelectionDialog.getOverrides(/* rendererIndex= */ i);
if (!overrides.isEmpty()) {
builder.setSelectionOverride(
/* rendererIndex= */ i,
mappedTrackInfo.getTrackGroups(/* rendererIndex= */ i),
overrides.get(0));
}
}
trackSelector.setParameters(builder);
},
player::setTrackSelectionParameters,
onDismissListener);
return trackSelectionDialog;
}
/**
* Creates a dialog for given {@link MappedTrackInfo} and {@link DefaultTrackSelector.Parameters}.
* Creates a dialog for given {@link TracksInfo} and {@link TrackSelectionParameters}.
*
* @param titleId The resource id of the dialog title.
* @param mappedTrackInfo The {@link MappedTrackInfo} to display.
* @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial
* track selection.
* @param tracksInfo The {@link TracksInfo} describing the tracks to display.
* @param trackSelectionParameters The initial {@link TrackSelectionParameters}.
* @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track)
* can be made.
* @param allowMultipleOverrides Whether tracks from multiple track groups can be selected.
* @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are selected.
* @param trackSelectionListener Called when tracks are selected.
* @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is
* dismissed.
*/
public static TrackSelectionDialog createForMappedTrackInfoAndParameters(
public static TrackSelectionDialog createForTracksInfoAndParameters(
int titleId,
MappedTrackInfo mappedTrackInfo,
DefaultTrackSelector.Parameters initialParameters,
TracksInfo tracksInfo,
TrackSelectionParameters trackSelectionParameters,
boolean allowAdaptiveSelections,
boolean allowMultipleOverrides,
DialogInterface.OnClickListener onClickListener,
TrackSelectionListener trackSelectionListener,
DialogInterface.OnDismissListener onDismissListener) {
TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog();
trackSelectionDialog.init(
tracksInfo,
trackSelectionParameters,
titleId,
mappedTrackInfo,
initialParameters,
allowAdaptiveSelections,
allowMultipleOverrides,
onClickListener,
/* onClickListener= */ (dialog, which) -> {
TrackSelectionParameters.Builder builder = trackSelectionParameters.buildUpon();
for (int i = 0; i < SUPPORTED_TRACK_TYPES.size(); i++) {
int trackType = SUPPORTED_TRACK_TYPES.get(i);
builder.setTrackTypeDisabled(trackType, trackSelectionDialog.getIsDisabled(trackType));
builder.clearOverridesOfType(trackType);
Map<TrackGroup, TrackSelectionOverride> overrides =
trackSelectionDialog.getOverrides(trackType);
for (TrackSelectionOverride override : overrides.values()) {
builder.addOverride(override);
}
}
trackSelectionListener.onTracksSelected(builder.build());
},
onDismissListener);
return trackSelectionDialog;
}
@ -160,9 +169,9 @@ public final class TrackSelectionDialog extends DialogFragment {
}
private void init(
TracksInfo tracksInfo,
TrackSelectionParameters trackSelectionParameters,
int titleId,
MappedTrackInfo mappedTrackInfo,
DefaultTrackSelector.Parameters initialParameters,
boolean allowAdaptiveSelections,
boolean allowMultipleOverrides,
DialogInterface.OnClickListener onClickListener,
@ -170,45 +179,49 @@ public final class TrackSelectionDialog extends DialogFragment {
this.titleId = titleId;
this.onClickListener = onClickListener;
this.onDismissListener = onDismissListener;
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
if (showTabForRenderer(mappedTrackInfo, i)) {
int trackType = mappedTrackInfo.getRendererType(/* rendererIndex= */ i);
TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(i);
for (int i = 0; i < SUPPORTED_TRACK_TYPES.size(); i++) {
@C.TrackType int trackType = SUPPORTED_TRACK_TYPES.get(i);
ArrayList<TrackGroupInfo> trackGroupInfos = new ArrayList<>();
for (TrackGroupInfo trackGroupInfo : tracksInfo.getTrackGroupInfos()) {
if (trackGroupInfo.getTrackType() == trackType) {
trackGroupInfos.add(trackGroupInfo);
}
}
if (!trackGroupInfos.isEmpty()) {
TrackSelectionViewFragment tabFragment = new TrackSelectionViewFragment();
tabFragment.init(
mappedTrackInfo,
/* rendererIndex= */ i,
initialParameters.getRendererDisabled(/* rendererIndex= */ i),
initialParameters.getSelectionOverride(/* rendererIndex= */ i, trackGroupArray),
trackGroupInfos,
trackSelectionParameters.disabledTrackTypes.contains(trackType),
trackSelectionParameters.overrides,
allowAdaptiveSelections,
allowMultipleOverrides);
tabFragments.put(i, tabFragment);
tabFragments.put(trackType, tabFragment);
tabTrackTypes.add(trackType);
}
}
}
/**
* Returns whether a renderer is disabled.
* Returns whether the disabled option is selected for the specified track type.
*
* @param rendererIndex Renderer index.
* @return Whether the renderer is disabled.
* @param trackType The track type.
* @return Whether the disabled option is selected for the track type.
*/
public boolean getIsDisabled(int rendererIndex) {
TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex);
return rendererView != null && rendererView.isDisabled;
public boolean getIsDisabled(int trackType) {
TrackSelectionViewFragment trackView = tabFragments.get(trackType);
return trackView != null && trackView.isDisabled;
}
/**
* Returns the list of selected track selection overrides for the specified renderer. There will
* be at most one override for each track group.
* Returns the selected track overrides for the specified track type.
*
* @param rendererIndex Renderer index.
* @return The list of track selection overrides for this renderer.
* @param trackType The track type.
* @return The track overrides for the track type.
*/
public List<SelectionOverride> getOverrides(int rendererIndex) {
TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex);
return rendererView == null ? Collections.emptyList() : rendererView.overrides;
public Map<TrackGroup, TrackSelectionOverride> getOverrides(int trackType) {
TrackSelectionViewFragment trackView = tabFragments.get(trackType);
return trackView == null ? Collections.emptyMap() : trackView.overrides;
}
@Override
@ -248,27 +261,7 @@ public final class TrackSelectionDialog extends DialogFragment {
return dialogView;
}
private static boolean showTabForRenderer(MappedTrackInfo mappedTrackInfo, int rendererIndex) {
TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(rendererIndex);
if (trackGroupArray.length == 0) {
return false;
}
int trackType = mappedTrackInfo.getRendererType(rendererIndex);
return isSupportedTrackType(trackType);
}
private static boolean isSupportedTrackType(int trackType) {
switch (trackType) {
case C.TRACK_TYPE_VIDEO:
case C.TRACK_TYPE_AUDIO:
case C.TRACK_TYPE_TEXT:
return true;
default:
return false;
}
}
private static String getTrackTypeString(Resources resources, int trackType) {
private static String getTrackTypeString(Resources resources, @C.TrackType int trackType) {
switch (trackType) {
case C.TRACK_TYPE_VIDEO:
return resources.getString(R.string.exo_track_selection_title_video);
@ -289,12 +282,12 @@ public final class TrackSelectionDialog extends DialogFragment {
@Override
public Fragment getItem(int position) {
return tabFragments.valueAt(position);
return tabFragments.get(tabTrackTypes.get(position));
}
@Override
public int getCount() {
return tabFragments.size();
return tabTrackTypes.size();
}
@Override
@ -307,13 +300,12 @@ public final class TrackSelectionDialog extends DialogFragment {
public static final class TrackSelectionViewFragment extends Fragment
implements TrackSelectionView.TrackSelectionListener {
private MappedTrackInfo mappedTrackInfo;
private int rendererIndex;
private List<TrackGroupInfo> trackGroupInfos;
private boolean allowAdaptiveSelections;
private boolean allowMultipleOverrides;
/* package */ boolean isDisabled;
/* package */ List<SelectionOverride> overrides;
/* package */ Map<TrackGroup, TrackSelectionOverride> overrides;
public TrackSelectionViewFragment() {
// Retain instance across activity re-creation to prevent losing access to init data.
@ -321,21 +313,21 @@ public final class TrackSelectionDialog extends DialogFragment {
}
public void init(
MappedTrackInfo mappedTrackInfo,
int rendererIndex,
boolean initialIsDisabled,
@Nullable SelectionOverride initialOverride,
List<TrackGroupInfo> trackGroupInfos,
boolean isDisabled,
Map<TrackGroup, TrackSelectionOverride> overrides,
boolean allowAdaptiveSelections,
boolean allowMultipleOverrides) {
this.mappedTrackInfo = mappedTrackInfo;
this.rendererIndex = rendererIndex;
this.isDisabled = initialIsDisabled;
this.overrides =
initialOverride == null
? Collections.emptyList()
: Collections.singletonList(initialOverride);
this.trackGroupInfos = trackGroupInfos;
this.isDisabled = isDisabled;
this.allowAdaptiveSelections = allowAdaptiveSelections;
this.allowMultipleOverrides = allowMultipleOverrides;
// TrackSelectionView does this filtering internally, but we need to do it here as well to
// handle the case where the TrackSelectionView is never created.
this.overrides =
new HashMap<>(
TrackSelectionView.filterOverrides(
overrides, trackGroupInfos, allowMultipleOverrides));
}
@Override
@ -351,8 +343,7 @@ public final class TrackSelectionDialog extends DialogFragment {
trackSelectionView.setAllowMultipleOverrides(allowMultipleOverrides);
trackSelectionView.setAllowAdaptiveSelections(allowAdaptiveSelections);
trackSelectionView.init(
mappedTrackInfo,
rendererIndex,
trackGroupInfos,
isDisabled,
overrides,
/* trackFormatComparator= */ null,
@ -361,7 +352,8 @@ public final class TrackSelectionDialog extends DialogFragment {
}
@Override
public void onTrackSelectionChanged(boolean isDisabled, List<SelectionOverride> overrides) {
public void onTrackSelectionChanged(
boolean isDisabled, Map<TrackGroup, TrackSelectionOverride> overrides) {
this.isDisabled = isDisabled;
this.overrides = overrides;
}

View File

@ -16,7 +16,7 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
android.buildTypes.debug.testCoverageEnabled true
dependencies {
implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-common')
api 'androidx.media:media:' + androidxMediaVersion
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion

View File

@ -15,8 +15,6 @@
*/
package com.google.android.exoplayer2.ui;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
@ -25,16 +23,21 @@ import android.view.LayoutInflater;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelectionUtil;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.TracksInfo.TrackGroupInfo;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.trackselection.TrackSelectionOverride;
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/** Builder for a dialog with a {@link TrackSelectionView}. */
public final class TrackSelectionDialogBuilder {
@ -45,25 +48,24 @@ public final class TrackSelectionDialogBuilder {
/**
* Called when tracks are selected.
*
* @param isDisabled Whether the renderer is disabled.
* @param overrides List of selected track selection overrides for the renderer.
* @param isDisabled Whether the disabled option is selected.
* @param overrides The selected track overrides.
*/
void onTracksSelected(boolean isDisabled, List<SelectionOverride> overrides);
void onTracksSelected(boolean isDisabled, Map<TrackGroup, TrackSelectionOverride> overrides);
}
private final Context context;
@StyleRes private int themeResId;
private final CharSequence title;
private final MappedTrackInfo mappedTrackInfo;
private final int rendererIndex;
private final List<TrackGroupInfo> trackGroupInfos;
private final DialogCallback callback;
@StyleRes private int themeResId;
private boolean allowAdaptiveSelections;
private boolean allowMultipleOverrides;
private boolean showDisableOption;
@Nullable private TrackNameProvider trackNameProvider;
private boolean isDisabled;
private List<SelectionOverride> overrides;
private Map<TrackGroup, TrackSelectionOverride> overrides;
@Nullable private Comparator<Format> trackFormatComparator;
/**
@ -71,59 +73,53 @@ public final class TrackSelectionDialogBuilder {
*
* @param context The context of the dialog.
* @param title The title of the dialog.
* @param mappedTrackInfo The {@link MappedTrackInfo} containing the track information.
* @param rendererIndex The renderer index in the {@code mappedTrackInfo} for which the track
* selection is shown.
* @param trackGroupInfos The {@link TrackGroupInfo TrackGroupInfos} for the track groups.
* @param callback The {@link DialogCallback} invoked when a track selection has been made.
*/
public TrackSelectionDialogBuilder(
Context context,
CharSequence title,
MappedTrackInfo mappedTrackInfo,
int rendererIndex,
List<TrackGroupInfo> trackGroupInfos,
DialogCallback callback) {
this.context = context;
this.title = title;
this.mappedTrackInfo = mappedTrackInfo;
this.rendererIndex = rendererIndex;
this.trackGroupInfos = ImmutableList.copyOf(trackGroupInfos);
this.callback = callback;
overrides = Collections.emptyList();
overrides = Collections.emptyMap();
}
/**
* Creates a builder for a track selection dialog which automatically updates a {@link
* DefaultTrackSelector}.
* Creates a builder for a track selection dialog.
*
* @param context The context of the dialog.
* @param title The title of the dialog.
* @param trackSelector A {@link DefaultTrackSelector} whose current selection is used to set up
* the dialog and which is updated when new tracks are selected in the dialog.
* @param rendererIndex The renderer index in the {@code trackSelector} for which the track
* selection is shown.
* @param player The {@link Player} whose tracks should be selected.
* @param trackType The type of tracks to show for selection.
*/
public TrackSelectionDialogBuilder(
Context context, CharSequence title, DefaultTrackSelector trackSelector, int rendererIndex) {
Context context, CharSequence title, Player player, @C.TrackType int trackType) {
this.context = context;
this.title = title;
this.mappedTrackInfo = checkNotNull(trackSelector.getCurrentMappedTrackInfo());
this.rendererIndex = rendererIndex;
TrackGroupArray rendererTrackGroups = mappedTrackInfo.getTrackGroups(rendererIndex);
DefaultTrackSelector.Parameters selectionParameters = trackSelector.getParameters();
isDisabled = selectionParameters.getRendererDisabled(rendererIndex);
SelectionOverride override =
selectionParameters.getSelectionOverride(rendererIndex, rendererTrackGroups);
overrides = override == null ? Collections.emptyList() : Collections.singletonList(override);
this.callback =
(newIsDisabled, newOverrides) ->
trackSelector.setParameters(
TrackSelectionUtil.updateParametersWithOverride(
selectionParameters,
rendererIndex,
rendererTrackGroups,
newIsDisabled,
newOverrides.isEmpty() ? null : newOverrides.get(0)));
List<TrackGroupInfo> allTrackGroupInfos = player.getCurrentTracksInfo().getTrackGroupInfos();
trackGroupInfos = new ArrayList<>();
for (int i = 0; i < allTrackGroupInfos.size(); i++) {
TrackGroupInfo trackGroupInfo = allTrackGroupInfos.get(i);
if (trackGroupInfo.getTrackType() == trackType) {
trackGroupInfos.add(trackGroupInfo);
}
}
overrides = Collections.emptyMap();
callback =
(isDisabled, overrides) -> {
TrackSelectionParameters.Builder parametersBuilder =
player.getTrackSelectionParameters().buildUpon();
parametersBuilder.setTrackTypeDisabled(trackType, isDisabled);
parametersBuilder.clearOverridesOfType(trackType);
for (TrackSelectionOverride override : overrides.values()) {
parametersBuilder.addOverride(override);
}
player.setTrackSelectionParameters(parametersBuilder.build());
};
}
/**
@ -149,27 +145,28 @@ public final class TrackSelectionDialogBuilder {
}
/**
* Sets the initial selection override to show.
* Sets the single initial override.
*
* @param override The initial override to show, or null for no override.
* @param override The initial override, or {@code null} for no override.
* @return This builder, for convenience.
*/
public TrackSelectionDialogBuilder setOverride(@Nullable SelectionOverride override) {
public TrackSelectionDialogBuilder setOverride(@Nullable TrackSelectionOverride override) {
return setOverrides(
override == null ? Collections.emptyList() : Collections.singletonList(override));
override == null ? Collections.emptyMap() : ImmutableMap.of(override.trackGroup, override));
}
/**
* Sets the list of initial selection overrides to show.
* Sets the initial track overrides. Any overrides that do not correspond to track groups
* described by {@code trackGroupInfos} that have been given to this instance will be ignored. If
* {@link #setAllowMultipleOverrides(boolean)} hasn't been set to {@code true} then all but one
* override will be ignored. The retained override will be the one whose track group is described
* first in {@code trackGroupInfos}.
*
* <p>Note that only the first override will be used unless {@link
* #setAllowMultipleOverrides(boolean)} is set to {@code true}.
*
* @param overrides The list of initial overrides to show. There must be at most one override for
* each track group.
* @param overrides The initially selected track overrides.
* @return This builder, for convenience.
*/
public TrackSelectionDialogBuilder setOverrides(List<SelectionOverride> overrides) {
public TrackSelectionDialogBuilder setOverrides(
Map<TrackGroup, TrackSelectionOverride> overrides) {
this.overrides = overrides;
return this;
}
@ -299,12 +296,7 @@ public final class TrackSelectionDialogBuilder {
selectionView.setTrackNameProvider(trackNameProvider);
}
selectionView.init(
mappedTrackInfo,
rendererIndex,
isDisabled,
overrides,
trackFormatComparator,
/* listener= */ null);
trackGroupInfos, isDisabled, overrides, trackFormatComparator, /* listener= */ null);
return (dialog, which) ->
callback.onTracksSelected(selectionView.getIsDisabled(), selectionView.getOverrides());
}

View File

@ -18,28 +18,24 @@ package com.google.android.exoplayer2.ui;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckedTextView;
import android.widget.LinearLayout;
import androidx.annotation.AttrRes;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RendererCapabilities;
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;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelectionOverride;
import com.google.android.exoplayer2.util.Assertions;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
import java.util.Map;
/** A view for making track selections. */
public class TrackSelectionView extends LinearLayout {
@ -50,10 +46,36 @@ public class TrackSelectionView extends LinearLayout {
/**
* Called when the selected tracks changed.
*
* @param isDisabled Whether the renderer is disabled.
* @param overrides List of selected track selection overrides for the renderer.
* @param isDisabled Whether the disabled option is selected.
* @param overrides The selected track overrides.
*/
void onTrackSelectionChanged(boolean isDisabled, List<SelectionOverride> overrides);
void onTrackSelectionChanged(
boolean isDisabled, Map<TrackGroup, TrackSelectionOverride> overrides);
}
/**
* Returns the subset of {@code overrides} that apply to track groups in {@code trackGroupInfos}.
* If {@code allowMultipleOverrides} is {@code} then at most one override is retained, which will
* be the one whose track group is first in {@code trackGroupInfos}.
*
* @param overrides The overrides to filter.
* @param trackGroupInfos The track groups whose overrides should be retained.
* @param allowMultipleOverrides Whether more than one override can be retained.
* @return The filtered overrides.
*/
public static Map<TrackGroup, TrackSelectionOverride> filterOverrides(
Map<TrackGroup, TrackSelectionOverride> overrides,
List<TrackGroupInfo> trackGroupInfos,
boolean allowMultipleOverrides) {
HashMap<TrackGroup, TrackSelectionOverride> filteredOverrides = new HashMap<>();
for (int i = 0; i < trackGroupInfos.size(); i++) {
TrackGroupInfo trackGroupInfo = trackGroupInfos.get(i);
@Nullable TrackSelectionOverride override = overrides.get(trackGroupInfo.getTrackGroup());
if (override != null && (allowMultipleOverrides || filteredOverrides.isEmpty())) {
filteredOverrides.put(override.trackGroup, override);
}
}
return filteredOverrides;
}
private final int selectableItemBackgroundResourceId;
@ -61,7 +83,8 @@ public class TrackSelectionView extends LinearLayout {
private final CheckedTextView disableView;
private final CheckedTextView defaultView;
private final ComponentListener componentListener;
private final SparseArray<SelectionOverride> overrides;
private final List<TrackGroupInfo> trackGroupInfos;
private final Map<TrackGroup, TrackSelectionOverride> overrides;
private boolean allowAdaptiveSelections;
private boolean allowMultipleOverrides;
@ -69,9 +92,6 @@ public class TrackSelectionView extends LinearLayout {
private TrackNameProvider trackNameProvider;
private CheckedTextView[][] trackViews;
private @MonotonicNonNull MappedTrackInfo mappedTrackInfo;
private int rendererIndex;
private TrackGroupArray trackGroups;
private boolean isDisabled;
@Nullable private Comparator<TrackInfo> trackInfoComparator;
@Nullable private TrackSelectionListener listener;
@ -92,9 +112,6 @@ public class TrackSelectionView extends LinearLayout {
Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(LinearLayout.VERTICAL);
overrides = new SparseArray<>();
// Don't save view hierarchy as it needs to be reinitialized with a call to init.
setSaveFromParentEnabled(false);
@ -108,7 +125,8 @@ public class TrackSelectionView extends LinearLayout {
inflater = LayoutInflater.from(context);
componentListener = new ComponentListener();
trackNameProvider = new DefaultTrackNameProvider(getResources());
trackGroups = TrackGroupArray.EMPTY;
trackGroupInfos = new ArrayList<>();
overrides = new HashMap<>();
// View for disabling the renderer.
disableView =
@ -153,26 +171,28 @@ public class TrackSelectionView extends LinearLayout {
/**
* Sets whether tracks from multiple track groups can be selected. This results in multiple {@link
* SelectionOverride SelectionOverrides} to be returned by {@link #getOverrides()}.
* TrackSelectionOverride TrackSelectionOverrides} being returned by {@link #getOverrides()}.
*
* @param allowMultipleOverrides Whether multiple track selection overrides can be selected.
* @param allowMultipleOverrides Whether tracks from multiple track groups can be selected.
*/
public void setAllowMultipleOverrides(boolean allowMultipleOverrides) {
if (this.allowMultipleOverrides != allowMultipleOverrides) {
this.allowMultipleOverrides = allowMultipleOverrides;
if (!allowMultipleOverrides && overrides.size() > 1) {
for (int i = overrides.size() - 1; i > 0; i--) {
overrides.remove(i);
}
// Re-filter the overrides to retain only one of them.
Map<TrackGroup, TrackSelectionOverride> filteredOverrides =
filterOverrides(overrides, trackGroupInfos, /* allowMultipleOverrides= */ false);
overrides.clear();
overrides.putAll(filteredOverrides);
}
updateViews();
}
}
/**
* Sets whether an option is available for disabling the renderer.
* Sets whether the disabled option can be selected.
*
* @param showDisableOption Whether the disable option is shown.
* @param showDisableOption Whether the disabled option can be selected.
*/
public void setShowDisableOption(boolean showDisableOption) {
disableView.setVisibility(showDisableOption ? View.VISIBLE : View.GONE);
@ -190,57 +210,47 @@ public class TrackSelectionView extends LinearLayout {
}
/**
* Initialize the view to select tracks for a specified renderer using {@link MappedTrackInfo} and
* a set of {@link DefaultTrackSelector.Parameters}.
* Initialize the view to select tracks from a specified list of track groups.
*
* @param mappedTrackInfo The {@link MappedTrackInfo}.
* @param rendererIndex The index of the renderer.
* @param isDisabled Whether the renderer should be initially shown as disabled.
* @param overrides List of initial overrides to be shown for this renderer. There must be at most
* one override for each track group. If {@link #setAllowMultipleOverrides(boolean)} hasn't
* been set to {@code true}, only the first override is used.
* @param trackGroupInfos {@link TrackGroupInfo TrackGroupInfos} for the track groups.
* @param isDisabled Whether the disabled option should be initially selected.
* @param overrides The initially selected track overrides. Any overrides that do not correspond
* to track groups described in {@code trackGroupInfos} will be ignored. If {@link
* #setAllowMultipleOverrides(boolean)} hasn't been set to {@code true} then all but one
* override will be ignored. The retained override will be the one whose track group is
* described first in {@code trackGroupInfos}.
* @param trackFormatComparator An optional comparator used to determine the display order of the
* tracks within each track group.
* @param listener An optional listener for track selection updates.
* @param listener An optional listener to receive selection updates.
*/
public void init(
MappedTrackInfo mappedTrackInfo,
int rendererIndex,
List<TrackGroupInfo> trackGroupInfos,
boolean isDisabled,
List<SelectionOverride> overrides,
Map<TrackGroup, TrackSelectionOverride> overrides,
@Nullable Comparator<Format> trackFormatComparator,
@Nullable TrackSelectionListener listener) {
this.mappedTrackInfo = mappedTrackInfo;
this.rendererIndex = rendererIndex;
this.isDisabled = isDisabled;
this.trackInfoComparator =
trackFormatComparator == null
? null
: (o1, o2) -> trackFormatComparator.compare(o1.format, o2.format);
: (o1, o2) -> trackFormatComparator.compare(o1.getFormat(), o2.getFormat());
this.listener = listener;
int maxOverrides = allowMultipleOverrides ? overrides.size() : Math.min(overrides.size(), 1);
for (int i = 0; i < maxOverrides; i++) {
SelectionOverride override = overrides.get(i);
this.overrides.put(override.groupIndex, override);
}
this.trackGroupInfos.clear();
this.trackGroupInfos.addAll(trackGroupInfos);
this.overrides.clear();
this.overrides.putAll(filterOverrides(overrides, trackGroupInfos, allowMultipleOverrides));
updateViews();
}
/** Returns whether the renderer is disabled. */
/** Returns whether the disabled option is selected. */
public boolean getIsDisabled() {
return isDisabled;
}
/**
* Returns the list of selected track selection overrides. There will be at most one override for
* each track group.
*/
public List<SelectionOverride> getOverrides() {
List<SelectionOverride> overrideList = new ArrayList<>(overrides.size());
for (int i = 0; i < overrides.size(); i++) {
overrideList.add(overrides.valueAt(i));
}
return overrideList;
/** Returns the selected track overrides. */
public Map<TrackGroup, TrackSelectionOverride> getOverrides() {
return overrides;
}
// Private methods.
@ -251,7 +261,7 @@ public class TrackSelectionView extends LinearLayout {
removeViewAt(i);
}
if (mappedTrackInfo == null) {
if (trackGroupInfos.isEmpty()) {
// The view is not initialized.
disableView.setEnabled(false);
defaultView.setEnabled(false);
@ -260,19 +270,18 @@ public class TrackSelectionView extends LinearLayout {
disableView.setEnabled(true);
defaultView.setEnabled(true);
trackGroups = mappedTrackInfo.getTrackGroups(rendererIndex);
// Add per-track views.
trackViews = new CheckedTextView[trackGroups.length][];
trackViews = new CheckedTextView[trackGroupInfos.size()][];
boolean enableMultipleChoiceForMultipleOverrides = shouldEnableMultiGroupSelection();
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup group = trackGroups.get(groupIndex);
boolean enableMultipleChoiceForAdaptiveSelections = shouldEnableAdaptiveSelection(groupIndex);
trackViews[groupIndex] = new CheckedTextView[group.length];
for (int trackGroupIndex = 0; trackGroupIndex < trackGroupInfos.size(); trackGroupIndex++) {
TrackGroupInfo trackGroupInfo = trackGroupInfos.get(trackGroupIndex);
boolean enableMultipleChoiceForAdaptiveSelections =
shouldEnableAdaptiveSelection(trackGroupInfo);
trackViews[trackGroupIndex] = new CheckedTextView[trackGroupInfo.length];
TrackInfo[] trackInfos = new TrackInfo[group.length];
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
trackInfos[trackIndex] = new TrackInfo(groupIndex, trackIndex, group.getFormat(trackIndex));
TrackInfo[] trackInfos = new TrackInfo[trackGroupInfo.length];
for (int trackIndex = 0; trackIndex < trackGroupInfo.length; trackIndex++) {
trackInfos[trackIndex] = new TrackInfo(trackGroupInfo, trackIndex);
}
if (trackInfoComparator != null) {
Arrays.sort(trackInfos, trackInfoComparator);
@ -289,17 +298,16 @@ public class TrackSelectionView extends LinearLayout {
CheckedTextView trackView =
(CheckedTextView) inflater.inflate(trackViewLayoutId, this, false);
trackView.setBackgroundResource(selectableItemBackgroundResourceId);
trackView.setText(trackNameProvider.getTrackName(trackInfos[trackIndex].format));
trackView.setText(trackNameProvider.getTrackName(trackInfos[trackIndex].getFormat()));
trackView.setTag(trackInfos[trackIndex]);
if (mappedTrackInfo.getTrackSupport(rendererIndex, groupIndex, trackIndex)
== C.FORMAT_HANDLED) {
if (trackGroupInfo.isTrackSupported(trackIndex)) {
trackView.setFocusable(true);
trackView.setOnClickListener(componentListener);
} else {
trackView.setFocusable(false);
trackView.setEnabled(false);
}
trackViews[groupIndex][trackIndex] = trackView;
trackViews[trackGroupIndex][trackIndex] = trackView;
addView(trackView);
}
}
@ -311,11 +319,12 @@ public class TrackSelectionView extends LinearLayout {
disableView.setChecked(isDisabled);
defaultView.setChecked(!isDisabled && overrides.size() == 0);
for (int i = 0; i < trackViews.length; i++) {
SelectionOverride override = overrides.get(i);
@Nullable
TrackSelectionOverride override = overrides.get(trackGroupInfos.get(i).getTrackGroup());
for (int j = 0; j < trackViews[i].length; j++) {
if (override != null) {
TrackInfo trackInfo = (TrackInfo) Assertions.checkNotNull(trackViews[i][j].getTag());
trackViews[i][j].setChecked(override.containsTrack(trackInfo.trackIndex));
trackViews[i][j].setChecked(override.trackIndices.contains(trackInfo.trackIndex));
} else {
trackViews[i][j].setChecked(false);
}
@ -350,74 +359,52 @@ public class TrackSelectionView extends LinearLayout {
private void onTrackViewClicked(View view) {
isDisabled = false;
TrackInfo trackInfo = (TrackInfo) Assertions.checkNotNull(view.getTag());
int groupIndex = trackInfo.groupIndex;
TrackGroup trackGroup = trackInfo.trackGroupInfo.getTrackGroup();
int trackIndex = trackInfo.trackIndex;
SelectionOverride override = overrides.get(groupIndex);
Assertions.checkNotNull(mappedTrackInfo);
@Nullable TrackSelectionOverride override = overrides.get(trackGroup);
if (override == null) {
// Start new override.
if (!allowMultipleOverrides && overrides.size() > 0) {
// Removed other overrides if we don't allow multiple overrides.
overrides.clear();
}
overrides.put(groupIndex, new SelectionOverride(groupIndex, trackIndex));
overrides.put(
trackGroup, new TrackSelectionOverride(trackGroup, ImmutableList.of(trackIndex)));
} else {
// An existing override is being modified.
int overrideLength = override.length;
int[] overrideTracks = override.tracks;
ArrayList<Integer> trackIndices = new ArrayList<>(override.trackIndices);
boolean isCurrentlySelected = ((CheckedTextView) view).isChecked();
boolean isAdaptiveAllowed = shouldEnableAdaptiveSelection(groupIndex);
boolean isAdaptiveAllowed = shouldEnableAdaptiveSelection(trackInfo.trackGroupInfo);
boolean isUsingCheckBox = isAdaptiveAllowed || shouldEnableMultiGroupSelection();
if (isCurrentlySelected && isUsingCheckBox) {
// Remove the track from the override.
if (overrideLength == 1) {
// The last track is being removed, so the override becomes empty.
overrides.remove(groupIndex);
trackIndices.remove((Integer) trackIndex);
if (trackIndices.isEmpty()) {
// The last track has been removed, so remove the whole override.
overrides.remove(trackGroup);
} else {
int[] tracks = getTracksRemoving(overrideTracks, trackIndex);
overrides.put(groupIndex, new SelectionOverride(groupIndex, tracks));
overrides.put(trackGroup, new TrackSelectionOverride(trackGroup, trackIndices));
}
} else if (!isCurrentlySelected) {
if (isAdaptiveAllowed) {
// Add new track to adaptive override.
int[] tracks = getTracksAdding(overrideTracks, trackIndex);
overrides.put(groupIndex, new SelectionOverride(groupIndex, tracks));
trackIndices.add(trackIndex);
overrides.put(trackGroup, new TrackSelectionOverride(trackGroup, trackIndices));
} else {
// Replace existing track in override.
overrides.put(groupIndex, new SelectionOverride(groupIndex, trackIndex));
overrides.put(
trackGroup, new TrackSelectionOverride(trackGroup, ImmutableList.of(trackIndex)));
}
}
}
}
@RequiresNonNull("mappedTrackInfo")
private boolean shouldEnableAdaptiveSelection(int groupIndex) {
return allowAdaptiveSelections
&& trackGroups.get(groupIndex).length > 1
&& mappedTrackInfo.getAdaptiveSupport(
rendererIndex, groupIndex, /* includeCapabilitiesExceededTracks= */ false)
!= RendererCapabilities.ADAPTIVE_NOT_SUPPORTED;
private boolean shouldEnableAdaptiveSelection(TrackGroupInfo trackGroupInfo) {
return allowAdaptiveSelections && trackGroupInfo.isAdaptiveSupported();
}
private boolean shouldEnableMultiGroupSelection() {
return allowMultipleOverrides && trackGroups.length > 1;
}
private static int[] getTracksAdding(int[] tracks, int addedTrack) {
tracks = Arrays.copyOf(tracks, tracks.length + 1);
tracks[tracks.length - 1] = addedTrack;
return tracks;
}
private static int[] getTracksRemoving(int[] tracks, int removedTrack) {
int[] newTracks = new int[tracks.length - 1];
int trackCount = 0;
for (int track : tracks) {
if (track != removedTrack) {
newTracks[trackCount++] = track;
}
}
return newTracks;
return allowMultipleOverrides && trackGroupInfos.size() > 1;
}
// Internal classes.
@ -431,14 +418,16 @@ public class TrackSelectionView extends LinearLayout {
}
private static final class TrackInfo {
public final int groupIndex;
public final TrackGroupInfo trackGroupInfo;
public final int trackIndex;
public final Format format;
public TrackInfo(int groupIndex, int trackIndex, Format format) {
this.groupIndex = groupIndex;
public TrackInfo(TrackGroupInfo trackGroupInfo, int trackIndex) {
this.trackGroupInfo = trackGroupInfo;
this.trackIndex = trackIndex;
this.format = format;
}
public Format getFormat() {
return trackGroupInfo.getTrackFormat(trackIndex);
}
}
}