selectedTrackKeys = new ArrayList<>();
- for (int i = 0; i < trackList.getChildCount(); i++) {
- if (trackList.isItemChecked(i)) {
- selectedTrackKeys.add(trackKeys.get(i));
- }
- }
- return selectedTrackKeys;
- }
-}
diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
new file mode 100644
index 0000000000..29310f8571
--- /dev/null
+++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
@@ -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.
+ *
+ * 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 listeners;
+ private final HashMap 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 List 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 trackKeys;
+ private final ArrayAdapter 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 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);
+ }
+ }
+ }
+}
diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
index 0427de05a8..091e483155 100644
--- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
+++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
@@ -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) manifestFilter))
+ new DashManifestParser(), (List) 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) manifestFilter))
+ new SsManifestParser(), (List) getOfflineStreamKeys(uri)))
.createMediaSource(uri);
case C.TYPE_HLS:
return new HlsMediaSource.Factory(mediaDataSourceFactory)
.setPlaylistParser(
new FilteringManifestParser<>(
- new HlsPlaylistParser(), (List) manifestFilter))
+ new HlsPlaylistParser(), (List) 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 buildDrmSessionManagerV18(
UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, boolean multiSession)
throws UnsupportedDrmException {
diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
index 04795f4abe..fb0b20f0e4 100644
--- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
+++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
@@ -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() {
- @Override
- public boolean onChildClick(
- ExpandableListView parent, View view, int groupPosition, int childPosition, long id) {
- onSampleClicked(groups.get(groupPosition).samples.get(childPosition));
- return true;
- }
- });
+ sampleAdapter.setSampleGroups(groups);
}
- private void onSampleClicked(Sample sample) {
+ @Override
+ public boolean onChildClick(
+ ExpandableListView parent, View view, int groupPosition, int childPosition, long id) {
+ 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> {
@@ -296,12 +341,15 @@ public class SampleChooserActivity extends Activity {
private final class SampleAdapter extends BaseExpandableListAdapter implements OnClickListener {
- private final Context context;
- private final List sampleGroups;
+ private List sampleGroups;
- public SampleAdapter(Context context, List sampleGroups) {
- this.context = context;
+ public SampleAdapter() {
+ sampleGroups = Collections.emptyList();
+ }
+
+ public void setSampleGroups(List 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);
}
}
diff --git a/demos/main/src/main/res/drawable-hdpi/ic_download.png b/demos/main/src/main/res/drawable-hdpi/ic_download.png
new file mode 100644
index 0000000000..fa3ebbb310
Binary files /dev/null and b/demos/main/src/main/res/drawable-hdpi/ic_download.png differ
diff --git a/demos/main/src/main/res/drawable-hdpi/ic_download_done.png b/demos/main/src/main/res/drawable-hdpi/ic_download_done.png
new file mode 100644
index 0000000000..fa0ec9dd68
Binary files /dev/null and b/demos/main/src/main/res/drawable-hdpi/ic_download_done.png differ
diff --git a/demos/main/src/main/res/drawable-hdpi/ic_offline_pin_white_36dp.png b/demos/main/src/main/res/drawable-hdpi/ic_offline_pin_white_36dp.png
deleted file mode 100644
index 4a19766f39..0000000000
Binary files a/demos/main/src/main/res/drawable-hdpi/ic_offline_pin_white_36dp.png and /dev/null differ
diff --git a/demos/main/src/main/res/drawable-mdpi/ic_download.png b/demos/main/src/main/res/drawable-mdpi/ic_download.png
new file mode 100644
index 0000000000..c8a2039c58
Binary files /dev/null and b/demos/main/src/main/res/drawable-mdpi/ic_download.png differ
diff --git a/demos/main/src/main/res/drawable-mdpi/ic_download_done.png b/demos/main/src/main/res/drawable-mdpi/ic_download_done.png
new file mode 100644
index 0000000000..08073a2a6d
Binary files /dev/null and b/demos/main/src/main/res/drawable-mdpi/ic_download_done.png differ
diff --git a/demos/main/src/main/res/drawable-mdpi/ic_offline_pin_white_36dp.png b/demos/main/src/main/res/drawable-mdpi/ic_offline_pin_white_36dp.png
deleted file mode 100644
index 4c3f844ede..0000000000
Binary files a/demos/main/src/main/res/drawable-mdpi/ic_offline_pin_white_36dp.png and /dev/null differ
diff --git a/demos/main/src/main/res/drawable-xhdpi/ic_download.png b/demos/main/src/main/res/drawable-xhdpi/ic_download.png
new file mode 100644
index 0000000000..671e0b3ece
Binary files /dev/null and b/demos/main/src/main/res/drawable-xhdpi/ic_download.png differ
diff --git a/demos/main/src/main/res/drawable-xhdpi/ic_download_done.png b/demos/main/src/main/res/drawable-xhdpi/ic_download_done.png
new file mode 100644
index 0000000000..2339c0bf16
Binary files /dev/null and b/demos/main/src/main/res/drawable-xhdpi/ic_download_done.png differ
diff --git a/demos/main/src/main/res/drawable-xhdpi/ic_offline_pin_white_36dp.png b/demos/main/src/main/res/drawable-xhdpi/ic_offline_pin_white_36dp.png
deleted file mode 100644
index 81caa4a1f6..0000000000
Binary files a/demos/main/src/main/res/drawable-xhdpi/ic_offline_pin_white_36dp.png and /dev/null differ
diff --git a/demos/main/src/main/res/drawable-xxhdpi/ic_download.png b/demos/main/src/main/res/drawable-xxhdpi/ic_download.png
new file mode 100644
index 0000000000..f02715177a
Binary files /dev/null and b/demos/main/src/main/res/drawable-xxhdpi/ic_download.png differ
diff --git a/demos/main/src/main/res/drawable-xxhdpi/ic_download_done.png b/demos/main/src/main/res/drawable-xxhdpi/ic_download_done.png
new file mode 100644
index 0000000000..b631a00088
Binary files /dev/null and b/demos/main/src/main/res/drawable-xxhdpi/ic_download_done.png differ
diff --git a/demos/main/src/main/res/drawable-xxhdpi/ic_offline_pin_white_36dp.png b/demos/main/src/main/res/drawable-xxhdpi/ic_offline_pin_white_36dp.png
deleted file mode 100644
index c711b9134f..0000000000
Binary files a/demos/main/src/main/res/drawable-xxhdpi/ic_offline_pin_white_36dp.png and /dev/null differ
diff --git a/demos/main/src/main/res/drawable-xxxhdpi/ic_download.png b/demos/main/src/main/res/drawable-xxxhdpi/ic_download.png
new file mode 100644
index 0000000000..6602791545
Binary files /dev/null and b/demos/main/src/main/res/drawable-xxxhdpi/ic_download.png differ
diff --git a/demos/main/src/main/res/drawable-xxxhdpi/ic_download_done.png b/demos/main/src/main/res/drawable-xxxhdpi/ic_download_done.png
new file mode 100644
index 0000000000..52fe8f6990
Binary files /dev/null and b/demos/main/src/main/res/drawable-xxxhdpi/ic_download_done.png differ
diff --git a/demos/main/src/main/res/drawable-xxxhdpi/ic_offline_pin_white_36dp.png b/demos/main/src/main/res/drawable-xxxhdpi/ic_offline_pin_white_36dp.png
deleted file mode 100644
index a81673a02c..0000000000
Binary files a/demos/main/src/main/res/drawable-xxxhdpi/ic_offline_pin_white_36dp.png and /dev/null differ
diff --git a/demos/main/src/main/res/layout/downloader_activity.xml b/demos/main/src/main/res/layout/downloader_activity.xml
deleted file mode 100644
index 64975e8b98..0000000000
--- a/demos/main/src/main/res/layout/downloader_activity.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/demos/main/src/main/res/layout/sample_list_item.xml b/demos/main/src/main/res/layout/sample_list_item.xml
index ab20770e25..cdb0058688 100644
--- a/demos/main/src/main/res/layout/sample_list_item.xml
+++ b/demos/main/src/main/res/layout/sample_list_item.xml
@@ -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"/>
diff --git a/demos/main/src/main/res/layout/start_download_dialog.xml b/demos/main/src/main/res/layout/start_download_dialog.xml
new file mode 100644
index 0000000000..acb9af5d97
--- /dev/null
+++ b/demos/main/src/main/res/layout/start_download_dialog.xml
@@ -0,0 +1,19 @@
+
+
+
diff --git a/demos/main/src/main/res/values/strings.xml b/demos/main/src/main/res/values/strings.xml
index a474c95369..eb260e6ffc 100644
--- a/demos/main/src/main/res/values/strings.xml
+++ b/demos/main/src/main/res/values/strings.xml
@@ -47,8 +47,14 @@
Playing sample without ads, as the IMA extension was not loaded
- Failed to download manifest
+ Failed to start download
- Remove all
+ This demo app does not support downloading playlists
+
+ This demo app does not support downloading protected content
+
+ This demo app only supports downloading http streams
+
+ IMA does not support offline ads
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ParcelableArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParcelableArray.java
deleted file mode 100644
index 3463f8f813..0000000000
--- a/library/core/src/main/java/com/google/android/exoplayer2/util/ParcelableArray.java
+++ /dev/null
@@ -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 implements Parcelable {
-
- @SuppressWarnings("rawtypes") // V cannot be obtained from static context
- public static final Creator CREATOR =
- new Creator() {
- @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 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);
- }
-}
diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java
index 8e64f69656..fd9488af55 100644
--- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java
+++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java
@@ -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 {
+/** Uniquely identifies a {@link Representation} in a {@link DashManifest}. */
+public final class RepresentationKey implements Comparable {
public final int periodIndex;
public final int adaptationSetIndex;
@@ -77,31 +73,4 @@ public final class RepresentationKey implements Parcelable, Comparable CREATOR =
- new Creator() {
- @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];
- }
- };
-
}
diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/RenditionKey.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/RenditionKey.java
index 84b0ab65ec..dec5882efb 100644
--- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/RenditionKey.java
+++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/RenditionKey.java
@@ -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 {
+public final class RenditionKey implements Comparable {
/** Types of rendition. */
@Retention(RetentionPolicy.SOURCE)
@@ -78,30 +76,4 @@ public final class RenditionKey implements Parcelable, Comparable
}
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 CREATOR =
- new Creator() {
- @Override
- public RenditionKey createFromParcel(Parcel in) {
- return new RenditionKey(in.readInt(), in.readInt());
- }
-
- @Override
- public RenditionKey[] newArray(int size) {
- return new RenditionKey[size];
- }
- };
}
diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/StreamKey.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/StreamKey.java
index 767c2702f8..6667a3df27 100644
--- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/StreamKey.java
+++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/StreamKey.java
@@ -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 {
+public final class StreamKey implements Comparable {
public final int streamElementIndex;
public final int trackIndex;
@@ -66,30 +64,4 @@ public final class StreamKey implements Parcelable, Comparable {
}
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 CREATOR =
- new Creator() {
- @Override
- public StreamKey createFromParcel(Parcel in) {
- return new StreamKey(in.readInt(), in.readInt());
- }
-
- @Override
- public StreamKey[] newArray(int size) {
- return new StreamKey[size];
- }
- };
}
diff --git a/library/ui/src/main/res/values-hi/strings.xml b/library/ui/src/main/res/values-hi/strings.xml
index 9a190d1bb2..9df542ef52 100644
--- a/library/ui/src/main/res/values-hi/strings.xml
+++ b/library/ui/src/main/res/values-hi/strings.xml
@@ -23,13 +23,13 @@
लेख
कोई नहीं
अपने आप
- Unknown
+ अज्ञात
%1$d × %2$d
- Mono
- Stereo
- Surround sound
- 5.1 surround sound
- 7.1 surround sound
- %1$.2f Mbps
+ मोनो साउंड
+ स्टीरियो साउंड
+ सराउंड साउंड
+ 5.1 सराउंड साउंड
+ 7.1 सराउंड साउंड
+ %1$.2f एमबीपीएस
%1$s, %2$s