Improve offline support in the demo app

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=195593235
This commit is contained in:
olly 2018-05-06 11:03:31 -07:00 committed by Oliver Woodman
parent f2cef12367
commit b9aaf1ebab
30 changed files with 454 additions and 480 deletions

View File

@ -75,8 +75,6 @@
</intent-filter>
</activity>
<activity android:name="com.google.android.exoplayer2.demo.DownloadActivity"/>
<service android:name="com.google.android.exoplayer2.demo.DemoDownloadService"
android:exported="false">
<intent-filter>

View File

@ -43,6 +43,7 @@ import java.io.File;
public class DemoApplication extends Application {
private static final String DOWNLOAD_ACTION_FILE = "actions";
private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions";
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
private static final int MAX_SIMULTANEOUS_DOWNLOADS = 2;
private static final Deserializer[] DOWNLOAD_DESERIALIZERS =
@ -58,6 +59,7 @@ public class DemoApplication extends Application {
private File downloadDirectory;
private Cache downloadCache;
private DownloadManager downloadManager;
private DownloadTracker downloadTracker;
@Override
public void onCreate() {
@ -83,20 +85,36 @@ public class DemoApplication extends Application {
return "withExtensions".equals(BuildConfig.FLAVOR);
}
/** Returns the download manager used by the application. */
public synchronized DownloadManager getDownloadManager() {
public DownloadManager getDownloadManager() {
initDownloadManager();
return downloadManager;
}
public DownloadTracker getDownloadTracker() {
initDownloadManager();
return downloadTracker;
}
private synchronized void initDownloadManager() {
if (downloadManager == null) {
DownloaderConstructorHelper constructorHelper =
new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory(null));
DownloaderConstructorHelper downloaderConstructorHelper =
new DownloaderConstructorHelper(
getDownloadCache(), buildHttpDataSourceFactory(/* listener= */ null));
downloadManager =
new DownloadManager(
constructorHelper,
downloaderConstructorHelper,
MAX_SIMULTANEOUS_DOWNLOADS,
DownloadManager.DEFAULT_MIN_RETRY_COUNT,
new File(getDownloadDirectory(), DOWNLOAD_ACTION_FILE),
DOWNLOAD_DESERIALIZERS);
downloadTracker =
new DownloadTracker(
/* context= */ this,
buildDataSourceFactory(/* listener= */ null),
new File(getDownloadDirectory(), DOWNLOAD_TRACKER_ACTION_FILE),
DOWNLOAD_DESERIALIZERS);
downloadManager.addListener(downloadTracker);
}
return downloadManager;
}
private synchronized Cache getDownloadCache() {

View File

@ -1,196 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.demo;
import static com.google.android.exoplayer2.demo.PlayerActivity.EXTENSION_EXTRA;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper;
import com.google.android.exoplayer2.offline.DownloadService;
import com.google.android.exoplayer2.offline.ProgressiveDownloadHelper;
import com.google.android.exoplayer2.offline.SegmentDownloadAction;
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.ui.DefaultTrackNameProvider;
import com.google.android.exoplayer2.ui.TrackNameProvider;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.ParcelableArray;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/** An activity for downloading media. */
public class DownloadActivity extends Activity {
public static final String PLAYER_INTENT = "player_intent";
public static final String SAMPLE_NAME = "sample_name";
private Intent playerIntent;
private String sampleName;
private TrackNameProvider trackNameProvider;
private DownloadHelper downloadHelper;
private ListView trackList;
private ArrayAdapter<String> arrayAdapter;
private ArrayList<TrackKey> trackKeys;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.downloader_activity);
trackNameProvider = new DefaultTrackNameProvider(getResources());
Intent intent = getIntent();
playerIntent = intent.getParcelableExtra(PLAYER_INTENT);
Uri sampleUri = playerIntent.getData();
sampleName = intent.getStringExtra(SAMPLE_NAME);
getActionBar().setTitle(sampleName);
arrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_multiple_choice);
trackList = findViewById(R.id.representation_list);
trackList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
trackList.setAdapter(arrayAdapter);
trackKeys = new ArrayList<>();
DemoApplication application = (DemoApplication) getApplication();
DataSource.Factory manifestDataSourceFactory =
application.buildDataSourceFactory(/* listener= */ null);
String extension = playerIntent.getStringExtra(EXTENSION_EXTRA);
int type = Util.inferContentType(sampleUri, extension);
switch (type) {
case C.TYPE_DASH:
downloadHelper = new DashDownloadHelper(sampleUri, manifestDataSourceFactory);
break;
case C.TYPE_SS:
downloadHelper = new SsDownloadHelper(sampleUri, manifestDataSourceFactory);
break;
case C.TYPE_HLS:
downloadHelper = new HlsDownloadHelper(sampleUri, manifestDataSourceFactory);
break;
case C.TYPE_OTHER:
downloadHelper = new ProgressiveDownloadHelper(sampleUri);
break;
default:
throw new IllegalStateException("Unsupported type: " + type);
}
downloadHelper.prepare(
new DownloadHelper.Callback() {
@Override
public void onPrepared(DownloadHelper helper) {
DownloadActivity.this.onPrepared();
}
@Override
public void onPrepareError(DownloadHelper helper, IOException e) {
DownloadActivity.this.onPrepareError();
}
});
}
private void onPrepared() {
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++) {
arrayAdapter.add(trackNameProvider.getTrackName(trackGroup.getFormat(k)));
trackKeys.add(new TrackKey(i, j, k));
}
}
}
}
private void onPrepareError() {
Toast.makeText(
getApplicationContext(), R.string.download_manifest_load_error, Toast.LENGTH_LONG)
.show();
}
// This method is referenced in the layout file
public void onClick(View v) {
// switch-case doesn't work as in some compile configurations id definitions aren't constant
int id = v.getId();
if (id == R.id.download_button) {
startDownload();
} else if (id == R.id.remove_all_button) {
removeDownload();
} else if (id == R.id.play_button) {
playDownload();
}
}
private void startDownload() {
List<TrackKey> selectedTrackKeys = getSelectedTrackKeys();
if (trackKeys.isEmpty() || !selectedTrackKeys.isEmpty()) {
DownloadService.addDownloadAction(
this,
DemoDownloadService.class,
downloadHelper.getDownloadAction(Util.getUtf8Bytes(sampleName), selectedTrackKeys));
}
}
private void removeDownload() {
DownloadService.addDownloadAction(
this,
DemoDownloadService.class,
downloadHelper.getRemoveAction(Util.getUtf8Bytes(sampleName)));
for (int i = 0; i < trackList.getChildCount(); i++) {
trackList.setItemChecked(i, false);
}
}
private void playDownload() {
DownloadAction action = downloadHelper.getDownloadAction(null, getSelectedTrackKeys());
List<? extends ParcelableArray> keys = null;
if (action instanceof SegmentDownloadAction) {
keys = ((SegmentDownloadAction) action).keys;
}
if (keys.isEmpty()) {
playerIntent.removeExtra(PlayerActivity.MANIFEST_FILTER_EXTRA);
} else {
playerIntent.putExtra(
PlayerActivity.MANIFEST_FILTER_EXTRA,
new ParcelableArray(keys.toArray(new Parcelable[0])));
}
startActivity(playerIntent);
}
private List<TrackKey> getSelectedTrackKeys() {
ArrayList<TrackKey> selectedTrackKeys = new ArrayList<>();
for (int i = 0; i < trackList.getChildCount(); i++) {
if (trackList.isItemChecked(i)) {
selectedTrackKeys.add(trackKeys.get(i));
}
}
return selectedTrackKeys;
}
}

View File

@ -0,0 +1,298 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.demo;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.offline.ActionFile;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadHelper;
import com.google.android.exoplayer2.offline.DownloadManager;
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.SegmentDownloadAction;
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.ui.DefaultTrackNameProvider;
import com.google.android.exoplayer2.ui.TrackNameProvider;
import com.google.android.exoplayer2.upstream.DataSource;
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;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Tracks media that has been downloaded.
*
* <p>Tracked downloads are persisted using an {@link ActionFile}, however in a real application
* it's expected that state will be stored directly in the application's media database, so that it
* can be queried efficiently together with other information about the media.
*/
public class DownloadTracker implements DownloadManager.Listener {
/** Listens for changes in the tracked downloads. */
public interface Listener {
/** Called when the tracked downloads changed. */
void onDownloadsChanged();
}
private static final String TAG = "DownloadTracker";
private final Context context;
private final DataSource.Factory dataSourceFactory;
private final TrackNameProvider trackNameProvider;
private final CopyOnWriteArraySet<Listener> listeners;
private final HashMap<Uri, DownloadAction> trackedDownloadStates;
private final ActionFile actionFile;
private final Handler actionFileWriteHandler;
public DownloadTracker(
Context context,
DataSource.Factory dataSourceFactory,
File actionFile,
DownloadAction.Deserializer[] deserializers) {
this.context = context.getApplicationContext();
this.dataSourceFactory = dataSourceFactory;
this.actionFile = new ActionFile(actionFile);
trackNameProvider = new DefaultTrackNameProvider(context.getResources());
listeners = new CopyOnWriteArraySet<>();
trackedDownloadStates = new HashMap<>();
HandlerThread actionFileWriteThread = new HandlerThread("DownloadTracker");
actionFileWriteThread.start();
actionFileWriteHandler = new Handler(actionFileWriteThread.getLooper());
loadTrackedActions(deserializers);
}
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
public boolean isDownloaded(Uri uri) {
return trackedDownloadStates.containsKey(uri);
}
@SuppressWarnings("unchecked")
public <K> List<K> getOfflineStreamKeys(Uri uri) {
if (!trackedDownloadStates.containsKey(uri)) {
return Collections.emptyList();
}
DownloadAction action = trackedDownloadStates.get(uri);
if (action instanceof SegmentDownloadAction) {
return ((SegmentDownloadAction) action).keys;
}
return Collections.emptyList();
}
public void toggleDownload(Activity activity, String name, Uri uri, String extension) {
if (isDownloaded(uri)) {
DownloadAction removeAction =
getDownloadHelper(uri, extension).getRemoveAction(Util.getUtf8Bytes(name));
addDownloadAction(removeAction);
} else {
StartDownloadDialogHelper helper =
new StartDownloadDialogHelper(activity, getDownloadHelper(uri, extension), name);
helper.prepare();
}
}
// DownloadManager.Listener
@Override
public void onTaskStateChanged(DownloadManager downloadManager, TaskState taskState) {
DownloadAction action = taskState.action;
Uri uri = action.uri;
if ((action.isRemoveAction && taskState.state == TaskState.STATE_COMPLETED)
|| (!action.isRemoveAction && taskState.state == TaskState.STATE_FAILED)) {
// A download has been removed, or has failed. Stop tracking it.
if (trackedDownloadStates.remove(uri) != null) {
handleTrackedDownloadStatesChanged();
}
}
}
@Override
public void onIdle(DownloadManager downloadManager) {
// Do nothing.
}
// Internal methods
private void loadTrackedActions(DownloadAction.Deserializer[] deserializers) {
try {
DownloadAction[] allActions = actionFile.load(deserializers);
for (DownloadAction action : allActions) {
trackedDownloadStates.put(action.uri, action);
}
} catch (IOException e) {
Log.e(TAG, "Failed to load tracked actions", e);
}
}
private void handleTrackedDownloadStatesChanged() {
for (Listener listener : listeners) {
listener.onDownloadsChanged();
}
final DownloadAction[] actions = trackedDownloadStates.values().toArray(new DownloadAction[0]);
actionFileWriteHandler.post(
new Runnable() {
@Override
public void run() {
try {
actionFile.store(actions);
} catch (IOException e) {
Log.e(TAG, "Failed to store tracked actions", e);
}
}
});
}
private void startDownload(DownloadAction action) {
if (trackedDownloadStates.containsKey(action.uri)) {
// This content is already being downloaded. Do nothing.
return;
}
trackedDownloadStates.put(action.uri, action);
handleTrackedDownloadStatesChanged();
addDownloadAction(action);
}
private void addDownloadAction(DownloadAction action) {
DownloadService.addDownloadAction(context, DemoDownloadService.class, action);
}
private DownloadHelper getDownloadHelper(Uri uri, String extension) {
int type = Util.inferContentType(uri, extension);
switch (type) {
case C.TYPE_DASH:
return new DashDownloadHelper(uri, dataSourceFactory);
case C.TYPE_SS:
return new SsDownloadHelper(uri, dataSourceFactory);
case C.TYPE_HLS:
return new HlsDownloadHelper(uri, dataSourceFactory);
case C.TYPE_OTHER:
return new ProgressiveDownloadHelper(uri);
default:
throw new IllegalStateException("Unsupported type: " + type);
}
}
private final class StartDownloadDialogHelper
implements DownloadHelper.Callback, DialogInterface.OnClickListener {
private final DownloadHelper downloadHelper;
private final String name;
private final AlertDialog.Builder builder;
private final View dialogView;
private final List<TrackKey> trackKeys;
private final ArrayAdapter<String> trackTitles;
private final ListView representationList;
public StartDownloadDialogHelper(
Activity activity, DownloadHelper downloadHelper, String name) {
this.downloadHelper = downloadHelper;
this.name = name;
builder =
new AlertDialog.Builder(activity)
.setTitle(R.string.exo_download_description)
.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);
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() {
downloadHelper.prepare(this);
}
@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 (!trackKeys.isEmpty()) {
builder.setView(dialogView);
}
builder.create().show();
}
}
@Override
public void onPrepareError(DownloadHelper helper, IOException e) {
Toast.makeText(
context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG)
.show();
}
@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));
}
}
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);
}
}
}
}

View File

@ -21,7 +21,6 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Pair;
@ -83,7 +82,6 @@ import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.ErrorMessageProvider;
import com.google.android.exoplayer2.util.EventLogger;
import com.google.android.exoplayer2.util.ParcelableArray;
import com.google.android.exoplayer2.util.Util;
import java.lang.reflect.Constructor;
import java.net.CookieHandler;
@ -104,13 +102,11 @@ public class PlayerActivity extends Activity
public static final String ACTION_VIEW = "com.google.android.exoplayer.demo.action.VIEW";
public static final String EXTENSION_EXTRA = "extension";
public static final String MANIFEST_FILTER_EXTRA = "manifest_filter";
public static final String ACTION_VIEW_LIST =
"com.google.android.exoplayer.demo.action.VIEW_LIST";
public static final String URI_LIST_EXTRA = "uri_list";
public static final String EXTENSION_LIST_EXTRA = "extension_list";
public static final String MANIFEST_FILTER_LIST_EXTRA = "manifest_filter_list";
public static final String AD_TAG_URI_EXTRA = "ad_tag_uri";
@ -313,11 +309,9 @@ public class PlayerActivity extends Activity
String action = intent.getAction();
Uri[] uris;
String[] extensions;
Parcelable[] manifestFilters;
if (ACTION_VIEW.equals(action)) {
uris = new Uri[] {intent.getData()};
extensions = new String[] {intent.getStringExtra(EXTENSION_EXTRA)};
manifestFilters = new Parcelable[] {intent.getParcelableExtra(MANIFEST_FILTER_EXTRA)};
} else if (ACTION_VIEW_LIST.equals(action)) {
String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA);
uris = new Uri[uriStrings.length];
@ -328,10 +322,6 @@ public class PlayerActivity extends Activity
if (extensions == null) {
extensions = new String[uriStrings.length];
}
manifestFilters = intent.getParcelableArrayExtra(MANIFEST_FILTER_LIST_EXTRA);
if (manifestFilters == null) {
manifestFilters = new Parcelable[uriStrings.length];
}
} else {
showToast(getString(R.string.unexpected_intent_action, action));
finish();
@ -413,9 +403,7 @@ public class PlayerActivity extends Activity
MediaSource[] mediaSources = new MediaSource[uris.length];
for (int i = 0; i < uris.length; i++) {
ParcelableArray<?> manifestFilter = (ParcelableArray<?>) manifestFilters[i];
List<?> filter = manifestFilter != null ? manifestFilter.asList() : null;
mediaSources[i] = buildMediaSource(uris[i], extensions[i], filter);
mediaSources[i] = buildMediaSource(uris[i], extensions[i]);
}
mediaSource =
mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources);
@ -445,12 +433,11 @@ public class PlayerActivity extends Activity
}
private MediaSource buildMediaSource(Uri uri) {
return buildMediaSource(uri, null, null);
return buildMediaSource(uri, null);
}
@SuppressWarnings("unchecked")
private MediaSource buildMediaSource(
Uri uri, @Nullable String overrideExtension, @Nullable List<?> manifestFilter) {
private MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) {
@ContentType int type = Util.inferContentType(uri, overrideExtension);
switch (type) {
case C.TYPE_DASH:
@ -459,7 +446,7 @@ public class PlayerActivity extends Activity
buildDataSourceFactory(false))
.setManifestParser(
new FilteringManifestParser<>(
new DashManifestParser(), (List<RepresentationKey>) manifestFilter))
new DashManifestParser(), (List<RepresentationKey>) getOfflineStreamKeys(uri)))
.createMediaSource(uri);
case C.TYPE_SS:
return new SsMediaSource.Factory(
@ -467,13 +454,13 @@ public class PlayerActivity extends Activity
buildDataSourceFactory(false))
.setManifestParser(
new FilteringManifestParser<>(
new SsManifestParser(), (List<StreamKey>) manifestFilter))
new SsManifestParser(), (List<StreamKey>) getOfflineStreamKeys(uri)))
.createMediaSource(uri);
case C.TYPE_HLS:
return new HlsMediaSource.Factory(mediaDataSourceFactory)
.setPlaylistParser(
new FilteringManifestParser<>(
new HlsPlaylistParser(), (List<RenditionKey>) manifestFilter))
new HlsPlaylistParser(), (List<RenditionKey>) getOfflineStreamKeys(uri)))
.createMediaSource(uri);
case C.TYPE_OTHER:
return new ExtractorMediaSource.Factory(mediaDataSourceFactory).createMediaSource(uri);
@ -483,6 +470,10 @@ public class PlayerActivity extends Activity
}
}
private List<?> getOfflineStreamKeys(Uri uri) {
return ((DemoApplication) getApplication()).getDownloadTracker().getOfflineStreamKeys(uri);
}
private DefaultDrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManagerV18(
UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, boolean multiSession)
throws UnsupportedDrmException {

View File

@ -24,7 +24,6 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.util.JsonReader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@ -47,17 +46,27 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** An activity for selecting from a list of media samples. */
public class SampleChooserActivity extends Activity {
public class SampleChooserActivity extends Activity
implements DownloadTracker.Listener, OnChildClickListener {
private static final String TAG = "SampleChooserActivity";
private DownloadTracker downloadTracker;
private SampleAdapter sampleAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_chooser_activity);
sampleAdapter = new SampleAdapter();
ExpandableListView sampleListView = findViewById(R.id.sample_list);
sampleListView.setAdapter(sampleAdapter);
sampleListView.setOnChildClickListener(this);
Intent intent = getIntent();
String dataUri = intent.getDataString();
String[] uris;
@ -80,10 +89,30 @@ public class SampleChooserActivity extends Activity {
uriList.toArray(uris);
Arrays.sort(uris);
}
downloadTracker = ((DemoApplication) getApplication()).getDownloadTracker();
startDownloadServiceForeground();
SampleListLoader loaderTask = new SampleListLoader();
loaderTask.execute(uris);
}
startDownloadServiceForeground();
@Override
public void onStart() {
super.onStart();
downloadTracker.addListener(this);
sampleAdapter.notifyDataSetChanged();
}
@Override
public void onStop() {
downloadTracker.removeListener(this);
super.onStop();
}
@Override
public void onDownloadsChanged() {
sampleAdapter.notifyDataSetChanged();
}
private void startDownloadServiceForeground() {
@ -96,28 +125,44 @@ public class SampleChooserActivity extends Activity {
Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
.show();
}
ExpandableListView sampleList = findViewById(R.id.sample_list);
sampleList.setAdapter(new SampleAdapter(this, groups));
sampleList.setOnChildClickListener(
new OnChildClickListener() {
sampleAdapter.setSampleGroups(groups);
}
@Override
public boolean onChildClick(
ExpandableListView parent, View view, int groupPosition, int childPosition, long id) {
onSampleClicked(groups.get(groupPosition).samples.get(childPosition));
return true;
}
});
}
private void onSampleClicked(Sample sample) {
Sample sample = (Sample) view.getTag();
startActivity(sample.buildIntent(this));
return true;
}
private void onSampleDownloadButtonClicked(Sample sample) {
Intent intent = new Intent(this, DownloadActivity.class);
intent.putExtra(DownloadActivity.SAMPLE_NAME, sample.name);
intent.putExtra(DownloadActivity.PLAYER_INTENT, sample.buildIntent(this));
startActivity(intent);
int downloadUnsupportedStringId = getDownloadUnsupportedStringId(sample);
if (downloadUnsupportedStringId != 0) {
Toast.makeText(getApplicationContext(), downloadUnsupportedStringId, Toast.LENGTH_LONG)
.show();
} else {
UriSample uriSample = (UriSample) sample;
downloadTracker.toggleDownload(this, sample.name, uriSample.uri, uriSample.extension);
}
}
private int getDownloadUnsupportedStringId(Sample sample) {
if (sample instanceof PlaylistSample) {
return R.string.download_playlist_unsupported;
}
UriSample uriSample = (UriSample) sample;
if (uriSample.drmInfo != null) {
return R.string.download_drm_unsupported;
}
if (uriSample.adTagUri != null) {
return R.string.download_ads_unsupported;
}
String scheme = uriSample.uri.getScheme();
if (!("http".equals(scheme) || "https".equals(scheme))) {
return R.string.download_scheme_unsupported;
}
return 0;
}
private final class SampleListLoader extends AsyncTask<String, Void, List<SampleGroup>> {
@ -296,12 +341,15 @@ public class SampleChooserActivity extends Activity {
private final class SampleAdapter extends BaseExpandableListAdapter implements OnClickListener {
private final Context context;
private final List<SampleGroup> sampleGroups;
private List<SampleGroup> sampleGroups;
public SampleAdapter(Context context, List<SampleGroup> sampleGroups) {
this.context = context;
public SampleAdapter() {
sampleGroups = Collections.emptyList();
}
public void setSampleGroups(List<SampleGroup> sampleGroups) {
this.sampleGroups = sampleGroups;
notifyDataSetChanged();
}
@Override
@ -319,7 +367,7 @@ public class SampleChooserActivity extends Activity {
View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
view = LayoutInflater.from(context).inflate(R.layout.sample_list_item, parent, false);
view = getLayoutInflater().inflate(R.layout.sample_list_item, parent, false);
View downloadButton = view.findViewById(R.id.download_button);
downloadButton.setOnClickListener(this);
downloadButton.setFocusable(false);
@ -348,8 +396,9 @@ public class SampleChooserActivity extends Activity {
ViewGroup parent) {
View view = convertView;
if (view == null) {
view = LayoutInflater.from(context).inflate(android.R.layout.simple_expandable_list_item_1,
parent, false);
view =
getLayoutInflater()
.inflate(android.R.layout.simple_expandable_list_item_1, parent, false);
}
((TextView) view).setText(getGroup(groupPosition).title);
return view;
@ -376,24 +425,18 @@ public class SampleChooserActivity extends Activity {
}
private void initializeChildView(View view, Sample sample) {
view.setTag(sample);
TextView sampleTitle = view.findViewById(R.id.sample_title);
sampleTitle.setText(sample.name);
boolean canDownload = getDownloadUnsupportedStringId(sample) == 0;
boolean isDownloaded = canDownload && downloadTracker.isDownloaded(((UriSample) sample).uri);
ImageButton downloadButton = view.findViewById(R.id.download_button);
downloadButton.setTag(sample);
downloadButton.setColorFilter(0xFFBBBBBB);
downloadButton.setVisibility(canDownload(sample) ? View.VISIBLE : View.GONE);
}
private boolean canDownload(Sample sample) {
if (!(sample instanceof UriSample)) {
return false;
}
UriSample uriSample = (UriSample) sample;
if (uriSample.drmInfo != null || uriSample.adTagUri != null) {
return false;
}
String scheme = uriSample.uri.getScheme();
return "http".equals(scheme) || "https".equals(scheme);
downloadButton.setColorFilter(
canDownload ? (isDownloaded ? 0xFF42A5F5 : 0xFFBDBDBD) : 0xFFEEEEEE);
downloadButton.setImageResource(
isDownloaded ? R.drawable.ic_download_done : R.drawable.ic_download);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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:id="@+id/activity_downloader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button android:id="@+id/download_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="@string/exo_download_description"/>
<Button android:id="@+id/remove_all_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="@string/download_remove_all"/>
<Button android:id="@+id/play_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="@string/exo_controls_play_description"/>
</LinearLayout>
<ListView android:id="@+id/representation_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View File

@ -33,7 +33,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/exo_download_description"
android:background="@android:color/transparent"
android:src="@drawable/ic_offline_pin_white_36dp"/>
android:background="@android:color/transparent"/>
</LinearLayout>

View File

@ -0,0 +1,19 @@
<?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.
-->
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/representation_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@ -47,8 +47,14 @@
<string name="ima_not_loaded">Playing sample without ads, as the IMA extension was not loaded</string>
<string name="download_manifest_load_error">Failed to download manifest</string>
<string name="download_start_error">Failed to start download</string>
<string name="download_remove_all">Remove all</string>
<string name="download_playlist_unsupported">This demo app does not support downloading playlists</string>
<string name="download_drm_unsupported">This demo app does not support downloading protected content</string>
<string name="download_scheme_unsupported">This demo app only supports downloading http streams</string>
<string name="download_ads_unsupported">IMA does not support offline ads</string>
</resources>

View File

@ -1,64 +0,0 @@
/*
* 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.
*/
package com.google.android.exoplayer2.util;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** A {@link android.os.Parcelable} wrapper around an array. */
public final class ParcelableArray<V extends Parcelable> implements Parcelable {
@SuppressWarnings("rawtypes") // V cannot be obtained from static context
public static final Creator<ParcelableArray> CREATOR =
new Creator<ParcelableArray>() {
@SuppressWarnings("unchecked")
@Override
public ParcelableArray createFromParcel(Parcel in) {
ClassLoader classLoader = ParcelableArray.class.getClassLoader();
Parcelable[] elements = in.readParcelableArray(classLoader);
return new ParcelableArray(elements);
}
@Override
public ParcelableArray[] newArray(int size) {
return new ParcelableArray[size];
}
};
private final V[] elements;
public ParcelableArray(V[] elements) {
this.elements = elements;
}
/** Returns an unmodifiable list containing all elements. */
public List<V> asList() {
return Collections.unmodifiableList(Arrays.asList(elements));
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelableArray(elements, flags);
}
}

View File

@ -15,15 +15,11 @@
*/
package com.google.android.exoplayer2.source.dash.manifest;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
* Uniquely identifies a {@link Representation} in a {@link DashManifest}.
*/
public final class RepresentationKey implements Parcelable, Comparable<RepresentationKey> {
/** Uniquely identifies a {@link Representation} in a {@link DashManifest}. */
public final class RepresentationKey implements Comparable<RepresentationKey> {
public final int periodIndex;
public final int adaptationSetIndex;
@ -77,31 +73,4 @@ public final class RepresentationKey implements Parcelable, Comparable<Represent
return result;
}
// Parcelable implementation.
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(periodIndex);
dest.writeInt(adaptationSetIndex);
dest.writeInt(representationIndex);
}
public static final Creator<RepresentationKey> CREATOR =
new Creator<RepresentationKey>() {
@Override
public RepresentationKey createFromParcel(Parcel in) {
return new RepresentationKey(in.readInt(), in.readInt(), in.readInt());
}
@Override
public RepresentationKey[] newArray(int size) {
return new RepresentationKey[size];
}
};
}

View File

@ -15,8 +15,6 @@
*/
package com.google.android.exoplayer2.source.hls.playlist;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -24,7 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** Uniquely identifies a rendition in an {@link HlsMasterPlaylist}. */
public final class RenditionKey implements Parcelable, Comparable<RenditionKey> {
public final class RenditionKey implements Comparable<RenditionKey> {
/** Types of rendition. */
@Retention(RetentionPolicy.SOURCE)
@ -78,30 +76,4 @@ public final class RenditionKey implements Parcelable, Comparable<RenditionKey>
}
return result;
}
// Parcelable implementation.
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(type);
dest.writeInt(trackIndex);
}
public static final Creator<RenditionKey> CREATOR =
new Creator<RenditionKey>() {
@Override
public RenditionKey createFromParcel(Parcel in) {
return new RenditionKey(in.readInt(), in.readInt());
}
@Override
public RenditionKey[] newArray(int size) {
return new RenditionKey[size];
}
};
}

View File

@ -15,13 +15,11 @@
*/
package com.google.android.exoplayer2.source.smoothstreaming.manifest;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/** Uniquely identifies a track in a {@link SsManifest}. */
public final class StreamKey implements Parcelable, Comparable<StreamKey> {
public final class StreamKey implements Comparable<StreamKey> {
public final int streamElementIndex;
public final int trackIndex;
@ -66,30 +64,4 @@ public final class StreamKey implements Parcelable, Comparable<StreamKey> {
}
return result;
}
// Parcelable implementation.
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(streamElementIndex);
dest.writeInt(trackIndex);
}
public static final Creator<StreamKey> CREATOR =
new Creator<StreamKey>() {
@Override
public StreamKey createFromParcel(Parcel in) {
return new StreamKey(in.readInt(), in.readInt());
}
@Override
public StreamKey[] newArray(int size) {
return new StreamKey[size];
}
};
}

View File

@ -23,13 +23,13 @@
<string name="exo_track_selection_title_text">लेख</string>
<string name="exo_track_selection_none">कोई नहीं</string>
<string name="exo_track_selection_auto">अपने आप</string>
<string name="exo_track_unknown">Unknown</string>
<string name="exo_track_unknown">अज्ञात</string>
<string name="exo_track_resolution">%1$d × %2$d</string>
<string name="exo_track_mono">Mono</string>
<string name="exo_track_stereo">Stereo</string>
<string name="exo_track_surround">Surround sound</string>
<string name="exo_track_surround_5_point_1">5.1 surround sound</string>
<string name="exo_track_surround_7_point_1">7.1 surround sound</string>
<string name="exo_track_bitrate">%1$.2f Mbps</string>
<string name="exo_track_mono">मोनो साउंड</string>
<string name="exo_track_stereo">स्टीरियो साउंड</string>
<string name="exo_track_surround">सराउंड साउंड</string>
<string name="exo_track_surround_5_point_1">5.1 सराउंड साउंड</string>
<string name="exo_track_surround_7_point_1">7.1 सराउंड साउंड</string>
<string name="exo_track_bitrate">%1$.2f एमबीपीएस</string>
<string name="exo_item_list">%1$s, %2$s</string>
</resources>