diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml index 25b87f5185..3bedefc60e 100644 --- a/demos/main/src/main/AndroidManifest.xml +++ b/demos/main/src/main/AndroidManifest.xml @@ -75,8 +75,6 @@ - - diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java index 2788684de5..b5c127d2e3 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoApplication.java @@ -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() { diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadActivity.java deleted file mode 100644 index 8546cf50e0..0000000000 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadActivity.java +++ /dev/null @@ -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 arrayAdapter; - private ArrayList 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 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 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 getSelectedTrackKeys() { - ArrayList 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 @@ - - - - - - -