Commit b2a5298e by olly Committed by Ian Baker

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
parent 661d7dde
......@@ -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)).
......
......@@ -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);
}
......
......@@ -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() {
......
......@@ -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
......
......@@ -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.
*
* <p>Note that only the first override will be used unless {@link
* #setAllowMultipleOverrides(boolean)} is set to {@code true}.
* 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}.
*
* @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());
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment