Update StartDownloadDialogHelper to use TrackSelectionView.

This is now possible as the download helper uses a track selector.

PiperOrigin-RevId: 225014517
This commit is contained in:
tonihei 2018-12-11 17:13:57 +00:00 committed by Oliver Woodman
parent 8d137c2e61
commit defbd04675
10 changed files with 276 additions and 89 deletions

View File

@ -19,13 +19,17 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.Nullable;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.RenderersFactory;
@ -37,20 +41,21 @@ import com.google.android.exoplayer2.offline.DownloadManager.TaskState;
import com.google.android.exoplayer2.offline.DownloadService;
import com.google.android.exoplayer2.offline.ProgressiveDownloadHelper;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.offline.TrackKey;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.dash.offline.DashDownloadHelper;
import com.google.android.exoplayer2.source.hls.offline.HlsDownloadHelper;
import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadHelper;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.ui.DefaultTrackNameProvider;
import com.google.android.exoplayer2.ui.TrackNameProvider;
import com.google.android.exoplayer2.ui.TrackSelectionView;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -126,10 +131,8 @@ public class DownloadTracker implements DownloadManager.Listener {
getDownloadHelper(uri, extension, renderersFactory).getRemoveAction();
startServiceWithAction(removeAction);
} else {
StartDownloadDialogHelper helper =
new StartDownloadDialogHelper(
activity, getDownloadHelper(uri, extension, renderersFactory), name);
helper.prepare();
new StartDownloadDialogHelper(
activity, getDownloadHelper(uri, extension, renderersFactory), name);
}
}
@ -217,61 +220,56 @@ public class DownloadTracker implements DownloadManager.Listener {
}
}
@SuppressWarnings("UngroupedOverloads")
private final class StartDownloadDialogHelper
implements DownloadHelper.Callback, DialogInterface.OnClickListener {
implements DownloadHelper.Callback,
DialogInterface.OnClickListener,
View.OnClickListener,
TrackSelectionView.DialogCallback {
private final DownloadHelper<?> downloadHelper;
private final String name;
private final LayoutInflater dialogInflater;
private final AlertDialog dialog;
private final LinearLayout selectionList;
private final AlertDialog.Builder builder;
private final View dialogView;
private final List<TrackKey> trackKeys;
private final ArrayAdapter<String> trackTitles;
private final ListView representationList;
private MappedTrackInfo mappedTrackInfo;
private DefaultTrackSelector.Parameters parameters;
public StartDownloadDialogHelper(
private StartDownloadDialogHelper(
Activity activity, DownloadHelper<?> downloadHelper, String name) {
this.downloadHelper = downloadHelper;
this.name = name;
builder =
AlertDialog.Builder builder =
new AlertDialog.Builder(activity)
.setTitle(R.string.exo_download_description)
.setTitle(R.string.download_preparing)
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, null);
// Inflate with the builder's context to ensure the correct style is used.
LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
dialogView = dialogInflater.inflate(R.layout.start_download_dialog, null);
dialogInflater = LayoutInflater.from(builder.getContext());
selectionList = (LinearLayout) dialogInflater.inflate(R.layout.start_download_dialog, null);
builder.setView(selectionList);
dialog = builder.create();
dialog.show();
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
trackKeys = new ArrayList<>();
trackTitles =
new ArrayAdapter<>(
builder.getContext(), android.R.layout.simple_list_item_multiple_choice);
representationList = dialogView.findViewById(R.id.representation_list);
representationList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
representationList.setAdapter(trackTitles);
}
public void prepare() {
parameters = DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS;
downloadHelper.prepare(this);
}
// DownloadHelper.Callback implementation.
@Override
public void onPrepared(DownloadHelper<?> helper) {
for (int i = 0; i < downloadHelper.getPeriodCount(); i++) {
TrackGroupArray trackGroups = downloadHelper.getTrackGroups(i);
for (int j = 0; j < trackGroups.length; j++) {
TrackGroup trackGroup = trackGroups.get(j);
for (int k = 0; k < trackGroup.length; k++) {
trackKeys.add(new TrackKey(i, j, k));
trackTitles.add(trackNameProvider.getTrackName(trackGroup.getFormat(k)));
}
}
if (helper.getPeriodCount() < 1) {
onPrepareError(downloadHelper, new IOException("Content is empty."));
return;
}
if (!trackKeys.isEmpty()) {
builder.setView(dialogView);
}
builder.create().show();
mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
updateSelectionList();
dialog.setTitle(R.string.exo_download_description);
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
}
@Override
@ -280,21 +278,107 @@ public class DownloadTracker implements DownloadManager.Listener {
context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG)
.show();
Log.e(TAG, "Failed to start download", e);
dialog.cancel();
}
// View.OnClickListener implementation.
@Override
public void onClick(View v) {
Integer rendererIndex = (Integer) v.getTag();
String dialogTitle = getTrackTypeString(mappedTrackInfo.getRendererType(rendererIndex));
Pair<AlertDialog, TrackSelectionView> dialogPair =
TrackSelectionView.getDialog(
dialog.getContext(),
dialogTitle,
mappedTrackInfo,
rendererIndex,
parameters,
/* callback= */ this);
dialogPair.second.setShowDisableOption(true);
dialogPair.second.setAllowAdaptiveSelections(false);
dialogPair.first.show();
}
// TrackSelectionView.DialogCallback implementation.
@Override
public void onTracksSelected(DefaultTrackSelector.Parameters parameters) {
for (int i = 0; i < downloadHelper.getPeriodCount(); i++) {
downloadHelper.replaceTrackSelections(/* periodIndex= */ i, parameters);
}
this.parameters = parameters;
updateSelectionList();
}
// DialogInterface.OnClickListener implementation.
@Override
public void onClick(DialogInterface dialog, int which) {
ArrayList<TrackKey> selectedTrackKeys = new ArrayList<>();
for (int i = 0; i < representationList.getChildCount(); i++) {
if (representationList.isItemChecked(i)) {
selectedTrackKeys.add(trackKeys.get(i));
DownloadAction downloadAction = downloadHelper.getDownloadAction(Util.getUtf8Bytes(name));
startDownload(downloadAction);
}
// Internal methods.
private void updateSelectionList() {
selectionList.removeAllViews();
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(i);
if (trackGroupArray.length == 0) {
continue;
}
String trackTypeString =
getTrackTypeString(mappedTrackInfo.getRendererType(/* rendererIndex= */ i));
if (trackTypeString == null) {
return;
}
String trackSelectionsString = getTrackSelectionString(/* rendererIndex= */ i);
View view = dialogInflater.inflate(R.layout.download_track_item, selectionList, false);
TextView trackTitleView = view.findViewById(R.id.track_title);
TextView trackDescView = view.findViewById(R.id.track_desc);
ImageButton editButton = view.findViewById(R.id.edit_button);
trackTitleView.setText(trackTypeString);
trackDescView.setText(trackSelectionsString);
editButton.setTag(i);
editButton.setOnClickListener(this);
selectionList.addView(view);
}
}
private String getTrackSelectionString(int rendererIndex) {
List<TrackSelection> trackSelections =
downloadHelper.getTrackSelections(/* periodIndex= */ 0, rendererIndex);
String selectedTracks = "";
Resources resources = selectionList.getResources();
for (int i = 0; i < trackSelections.size(); i++) {
TrackSelection selection = trackSelections.get(i);
for (int j = 0; j < selection.length(); j++) {
String trackName = trackNameProvider.getTrackName(selection.getFormat(j));
if (i == 0 && j == 0) {
selectedTracks = trackName;
} else {
selectedTracks = resources.getString(R.string.exo_item_list, selectedTracks, trackName);
}
}
}
if (!selectedTrackKeys.isEmpty() || trackKeys.isEmpty()) {
// We have selected keys, or we're dealing with single stream content.
DownloadAction downloadAction =
downloadHelper.getDownloadAction(Util.getUtf8Bytes(name), selectedTrackKeys);
startDownload(downloadAction);
return selectedTracks.isEmpty()
? resources.getString(R.string.exo_track_selection_none)
: selectedTracks;
}
@Nullable
private String getTrackTypeString(int trackType) {
Resources resources = selectionList.getResources();
switch (trackType) {
case C.TRACK_TYPE_VIDEO:
return resources.getString(R.string.exo_track_selection_title_video);
case C.TRACK_TYPE_AUDIO:
return resources.getString(R.string.exo_track_selection_title_audio);
case C.TRACK_TYPE_TEXT:
return resources.getString(R.string.exo_track_selection_title_text);
default:
return null;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/track_title"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="4dp"/>
<TextView
android:id="@+id/track_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="4dp"/>
</LinearLayout>
<ImageButton
android:id="@+id/edit_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:contentDescription="@string/download_edit_track"
android:src="@drawable/ic_edit"/>
</LinearLayout>

View File

@ -13,7 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/representation_list"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/selection_list"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@ -51,6 +51,10 @@
<string name="ima_not_loaded">Playing sample without ads, as the IMA extension was not loaded</string>
<string name="download_edit_track">Edit selection</string>
<string name="download_preparing">Preparing download…</string>
<string name="download_start_error">Failed to start download</string>
<string name="download_playlist_unsupported">This demo app does not support downloading playlists</string>

View File

@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
@ -33,13 +32,25 @@ 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;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.util.Assertions;
import java.util.Arrays;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** A view for making track selections. */
public class TrackSelectionView extends LinearLayout {
/** Callback which is invoked when a track selection has been made. */
public interface DialogCallback {
/**
* Called when track are selected.
*
* @param parameters The {@link DefaultTrackSelector.Parameters} for the selected tracks.
*/
void onTracksSelected(DefaultTrackSelector.Parameters parameters);
}
private final int selectableItemBackgroundResourceId;
private final LayoutInflater inflater;
private final CheckedTextView disableView;
@ -51,35 +62,64 @@ public class TrackSelectionView extends LinearLayout {
private TrackNameProvider trackNameProvider;
private CheckedTextView[][] trackViews;
private DefaultTrackSelector trackSelector;
private @MonotonicNonNull MappedTrackInfo mappedTrackInfo;
private int rendererIndex;
private DefaultTrackSelector.Parameters parameters;
private TrackGroupArray trackGroups;
private boolean isDisabled;
private @Nullable SelectionOverride override;
@Nullable private SelectionOverride override;
/**
* Gets a pair consisting of a dialog and the {@link TrackSelectionView} that will be shown by it.
*
* @param activity The parent activity.
* <p>The dialog shows the current configuration of the provided {@code TrackSelector} and updates
* the parameters when closing the dialog.
*
* @param context The parent context.
* @param title The dialog's title.
* @param trackSelector The track selector.
* @param rendererIndex The index of the renderer.
* @return The dialog and the {@link TrackSelectionView} that will be shown by it.
*/
public static Pair<AlertDialog, TrackSelectionView> getDialog(
Activity activity,
Context context, CharSequence title, DefaultTrackSelector trackSelector, int rendererIndex) {
return getDialog(
context,
title,
Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo()),
rendererIndex,
trackSelector.getParameters(),
trackSelector::setParameters);
}
/**
* Gets a pair consisting of a dialog and the {@link TrackSelectionView} that will be shown by it.
*
* @param context The parent context.
* @param title The dialog's title.
* @param mappedTrackInfo The {@link MappedTrackInfo}.
* @param rendererIndex The index of the renderer.
* @param parameters The {@link DefaultTrackSelector.Parameters}.
* @param callback The {@link DialogCallback} invoked when the dialog is closed successfully.
* @return The dialog and the {@link TrackSelectionView} that will be shown by it.
*/
public static Pair<AlertDialog, TrackSelectionView> getDialog(
Context context,
CharSequence title,
DefaultTrackSelector trackSelector,
int rendererIndex) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
MappedTrackInfo mappedTrackInfo,
int rendererIndex,
DefaultTrackSelector.Parameters parameters,
DialogCallback callback) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
// Inflate with the builder's context to ensure the correct style is used.
LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
View dialogView = dialogInflater.inflate(R.layout.exo_track_selection_dialog, null);
final TrackSelectionView selectionView = dialogView.findViewById(R.id.exo_track_selection_view);
selectionView.init(trackSelector, rendererIndex);
Dialog.OnClickListener okClickListener = (dialog, which) -> selectionView.applySelection();
TrackSelectionView selectionView = dialogView.findViewById(R.id.exo_track_selection_view);
selectionView.init(mappedTrackInfo, rendererIndex, parameters);
Dialog.OnClickListener okClickListener =
(dialog, which) -> callback.onTracksSelected(selectionView.getSelectionParameters());
AlertDialog dialog =
builder
@ -113,6 +153,8 @@ public class TrackSelectionView extends LinearLayout {
inflater = LayoutInflater.from(context);
componentListener = new ComponentListener();
trackNameProvider = new DefaultTrackNameProvider(getResources());
parameters = DefaultTrackSelector.Parameters.DEFAULT;
trackGroups = TrackGroupArray.EMPTY;
// View for disabling the renderer.
disableView =
@ -176,18 +218,35 @@ public class TrackSelectionView extends LinearLayout {
}
/**
* Initialize the view to select tracks for a specified renderer using a {@link
* DefaultTrackSelector}.
* Initialize the view to select tracks for a specified renderer using {@link MappedTrackInfo} and
* a set of {@link DefaultTrackSelector.Parameters}.
*
* @param trackSelector The {@link DefaultTrackSelector}.
* @param mappedTrackInfo The {@link MappedTrackInfo}.
* @param rendererIndex The index of the renderer.
* @param parameters The {@link DefaultTrackSelector.Parameters}.
*/
public void init(DefaultTrackSelector trackSelector, int rendererIndex) {
this.trackSelector = trackSelector;
public void init(
MappedTrackInfo mappedTrackInfo,
int rendererIndex,
DefaultTrackSelector.Parameters parameters) {
this.mappedTrackInfo = mappedTrackInfo;
this.rendererIndex = rendererIndex;
this.parameters = parameters;
updateViews();
}
/** Returns the {@link DefaultTrackSelector.Parameters} for the current selection. */
public DefaultTrackSelector.Parameters getSelectionParameters() {
DefaultTrackSelector.ParametersBuilder parametersBuilder = parameters.buildUpon();
parametersBuilder.setRendererDisabled(rendererIndex, isDisabled);
if (override != null) {
parametersBuilder.setSelectionOverride(rendererIndex, trackGroups, override);
} else {
parametersBuilder.clearSelectionOverrides(rendererIndex);
}
return parametersBuilder.build();
}
// Private methods.
private void updateViews() {
@ -196,9 +255,7 @@ public class TrackSelectionView extends LinearLayout {
removeViewAt(i);
}
MappingTrackSelector.MappedTrackInfo trackInfo =
trackSelector == null ? null : trackSelector.getCurrentMappedTrackInfo();
if (trackSelector == null || trackInfo == null) {
if (mappedTrackInfo == null) {
// The view is not initialized.
disableView.setEnabled(false);
defaultView.setEnabled(false);
@ -207,9 +264,8 @@ public class TrackSelectionView extends LinearLayout {
disableView.setEnabled(true);
defaultView.setEnabled(true);
trackGroups = trackInfo.getTrackGroups(rendererIndex);
trackGroups = mappedTrackInfo.getTrackGroups(rendererIndex);
DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();
isDisabled = parameters.getRendererDisabled(rendererIndex);
override = parameters.getSelectionOverride(rendererIndex, trackGroups);
@ -220,7 +276,7 @@ public class TrackSelectionView extends LinearLayout {
boolean enableAdaptiveSelections =
allowAdaptiveSelections
&& trackGroups.get(groupIndex).length > 1
&& trackInfo.getAdaptiveSupport(rendererIndex, groupIndex, false)
&& mappedTrackInfo.getAdaptiveSupport(rendererIndex, groupIndex, false)
!= RendererCapabilities.ADAPTIVE_NOT_SUPPORTED;
trackViews[groupIndex] = new CheckedTextView[group.length];
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
@ -235,7 +291,7 @@ public class TrackSelectionView extends LinearLayout {
(CheckedTextView) inflater.inflate(trackViewLayoutId, this, false);
trackView.setBackgroundResource(selectableItemBackgroundResourceId);
trackView.setText(trackNameProvider.getTrackName(group.getFormat(trackIndex)));
if (trackInfo.getTrackSupport(rendererIndex, groupIndex, trackIndex)
if (mappedTrackInfo.getTrackSupport(rendererIndex, groupIndex, trackIndex)
== RendererCapabilities.FORMAT_HANDLED) {
trackView.setFocusable(true);
trackView.setTag(Pair.create(groupIndex, trackIndex));
@ -263,17 +319,6 @@ public class TrackSelectionView extends LinearLayout {
}
}
private void applySelection() {
DefaultTrackSelector.ParametersBuilder parametersBuilder = trackSelector.buildUponParameters();
parametersBuilder.setRendererDisabled(rendererIndex, isDisabled);
if (override != null) {
parametersBuilder.setSelectionOverride(rendererIndex, trackGroups, override);
} else {
parametersBuilder.clearSelectionOverrides(rendererIndex);
}
trackSelector.setParameters(parametersBuilder);
}
private void onClick(View view) {
if (view == disableView) {
onDisableViewClicked();