diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0fd6f3c8ee..1d54fdaea2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -246,6 +246,7 @@ ([#3792](https://github.com/google/ExoPlayer/issues/3792). * Support 14-bit mode and little endianness in DTS PES packets ([#3340](https://github.com/google/ExoPlayer/issues/3340)). +* Demo app: Add ability to download not DRM protected content. ### 2.6.1 ### diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml index cde95300ab..fb91de4328 100644 --- a/demos/main/src/main/AndroidManifest.xml +++ b/demos/main/src/main/AndroidManifest.xml @@ -19,6 +19,9 @@ + + + @@ -73,6 +76,22 @@ + + + + + + + + + + + diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java new file mode 100644 index 0000000000..41fd165ebe --- /dev/null +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java @@ -0,0 +1,118 @@ +/* + * 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.Notification; +import android.app.NotificationManager; +import android.util.Pair; +import com.google.android.exoplayer2.offline.DownloadManager; +import com.google.android.exoplayer2.offline.DownloadManager.DownloadState; +import com.google.android.exoplayer2.offline.DownloadService; +import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; +import com.google.android.exoplayer2.offline.ProgressiveDownloadAction; +import com.google.android.exoplayer2.scheduler.PlatformScheduler; +import com.google.android.exoplayer2.scheduler.Requirements; +import com.google.android.exoplayer2.source.dash.offline.DashDownloadAction; +import com.google.android.exoplayer2.source.hls.offline.HlsDownloadAction; +import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadAction; +import com.google.android.exoplayer2.ui.DownloadNotificationUtil; +import com.google.android.exoplayer2.ui.NotificationUtil; +import com.google.android.exoplayer2.util.ErrorMessageProvider; +import java.io.File; + +/** Demo DownloadService implementation. */ +public class DemoDownloadService extends DownloadService { + + private static final String CHANNEL_ID = "my_channel_01"; + + private static final int JOB_ID = 1; + + private static final int FOREGROUND_NOTIFICATION_ID = 1; + + private static DownloadManager downloadManager; + + public DemoDownloadService() { + super(FOREGROUND_NOTIFICATION_ID); + } + + @Override + public void onCreate() { + NotificationUtil.createNotificationChannel( + this, + CHANNEL_ID, + R.string.download_notifications_channel_name, + NotificationManager.IMPORTANCE_LOW); + super.onCreate(); + } + + @Override + protected DownloadManager getDownloadManager() { + if (downloadManager == null) { + DemoApplication application = (DemoApplication) getApplication(); + DownloaderConstructorHelper constructorHelper = + new DownloaderConstructorHelper( + application.getDownloadCache(), application.buildHttpDataSourceFactory(null)); + String actionFilePath = new File(getExternalCacheDir(), "actionFile").getAbsolutePath(); + downloadManager = + new DownloadManager( + constructorHelper, + /*maxSimultaneousDownloads=*/ 2, + DownloadManager.DEFAULT_MIN_RETRY_COUNT, + actionFilePath, + DashDownloadAction.DESERIALIZER, + HlsDownloadAction.DESERIALIZER, + SsDownloadAction.DESERIALIZER, + ProgressiveDownloadAction.DESERIALIZER); + } + return downloadManager; + } + + @Override + protected PlatformScheduler getScheduler() { + return new PlatformScheduler( + getApplicationContext(), getRequirements(), JOB_ID, ACTION_INIT, getPackageName()); + } + + @Override + protected Requirements getRequirements() { + return new Requirements(Requirements.NETWORK_TYPE_UNMETERED, false, false); + } + + @Override + protected Notification getForegroundNotification(DownloadState[] downloadStates) { + return DownloadNotificationUtil.createProgressNotification( + downloadStates, this, R.drawable.exo_controls_play, CHANNEL_ID, null); + } + + @Override + protected void onStateChange(DownloadState downloadState) { + int notificationId = FOREGROUND_NOTIFICATION_ID + 1 + downloadState.taskId; + Notification downloadNotification = + DownloadNotificationUtil.createDownloadFinishedNotification( + downloadState, + this, + R.drawable.exo_controls_play, + CHANNEL_ID, + downloadState.downloadAction.getData(), + new ErrorMessageProvider() { + @Override + public Pair getErrorMessage(Throwable throwable) { + return new Pair<>(0, throwable.getLocalizedMessage()); + } + }); + NotificationUtil.setNotification(this, notificationId, downloadNotification); + } +} diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloaderActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloaderActivity.java new file mode 100644 index 0000000000..62bf3020bf --- /dev/null +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloaderActivity.java @@ -0,0 +1,491 @@ +/* + * 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.DRM_SCHEME_EXTRA; +import static com.google.android.exoplayer2.demo.PlayerActivity.EXTENSION_EXTRA; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.Log; +import android.util.SparseBooleanArray; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.offline.DownloadAction; +import com.google.android.exoplayer2.offline.DownloadService; +import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; +import com.google.android.exoplayer2.offline.ProgressiveDownloadAction; +import com.google.android.exoplayer2.offline.ProgressiveDownloader; +import com.google.android.exoplayer2.source.dash.manifest.Representation; +import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey; +import com.google.android.exoplayer2.source.dash.offline.DashDownloadAction; +import com.google.android.exoplayer2.source.dash.offline.DashDownloader; +import com.google.android.exoplayer2.source.hls.offline.HlsDownloadAction; +import com.google.android.exoplayer2.source.hls.offline.HlsDownloader; +import com.google.android.exoplayer2.source.smoothstreaming.manifest.TrackKey; +import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadAction; +import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloader; +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 that downloads streams. */ +public class DownloaderActivity extends Activity { + + public static final String PLAYER_INTENT = "player_intent"; + public static final String SAMPLE_NAME = "stream_name"; + + private static final String TAG = "DownloaderActivity"; + + private Intent playerIntent; + + @SuppressWarnings("rawtypes") + private AsyncTask manifestDownloaderTask; + + private DownloadUtilMethods downloadUtilMethods; + + private ListView representationList; + private ArrayAdapter arrayAdapter; + private AlertDialog cancelDialog; + private String sampleName; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.downloader_activity); + + Intent intent = getIntent(); + playerIntent = intent.getParcelableExtra(PLAYER_INTENT); + + TextView streamName = findViewById(R.id.sample_name); + sampleName = intent.getStringExtra(SAMPLE_NAME); + streamName.setText(sampleName); + + arrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_multiple_choice); + representationList = findViewById(R.id.representation_list); + representationList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + representationList.setAdapter(arrayAdapter); + + if (playerIntent.hasExtra(DRM_SCHEME_EXTRA)) { + showToastAndFinish(R.string.not_supported_content_type); + } + + Uri sampleUri = playerIntent.getData(); + DemoApplication application = (DemoApplication) getApplication(); + DownloaderConstructorHelper constructorHelper = + new DownloaderConstructorHelper( + application.getDownloadCache(), application.buildHttpDataSourceFactory(null)); + String extension = playerIntent.getStringExtra(EXTENSION_EXTRA); + int type = Util.inferContentType(sampleUri, extension); + switch (type) { + case C.TYPE_DASH: + downloadUtilMethods = new DashDownloadUtilMethods(sampleUri, constructorHelper); + break; + case C.TYPE_HLS: + downloadUtilMethods = new HlsDownloadUtilMethods(sampleUri, constructorHelper); + break; + case C.TYPE_SS: + downloadUtilMethods = new SsDownloadUtilMethods(sampleUri, constructorHelper); + break; + case C.TYPE_OTHER: + downloadUtilMethods = new ProgressiveDownloadUtilMethods(sampleUri, constructorHelper); + break; + default: + showToastAndFinish(R.string.not_supported_content_type); + break; + } + + updateRepresentationsList(); + } + + @Override + protected void onPause() { + stopAll(); + super.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + updateRepresentationsList(); + } + + // 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) { + removeDownloaded(); + } else if (id == R.id.refresh) { + updateRepresentationsList(); + } else if (id == R.id.play_button) { + playDownloaded(); + } + } + + private void startDownload() { + ArrayList representationKeys = getSelectedRepresentationKeys(true); + if (representationKeys == null) { + return; + } + DownloadService.addDownloadAction( + this, + DemoDownloadService.class, + downloadUtilMethods.getDownloadAction(sampleName, representationKeys)); + } + + private void removeDownloaded() { + DownloadService.addDownloadAction( + this, DemoDownloadService.class, downloadUtilMethods.getRemoveAction()); + showToastAndFinish(R.string.removing_all); + } + + @SuppressWarnings("SuspiciousToArrayCall") + private void playDownloaded() { + ArrayList selectedRepresentationKeys = getSelectedRepresentationKeys(false); + if (selectedRepresentationKeys.isEmpty()) { + playerIntent.removeExtra(PlayerActivity.MANIFEST_FILTER_EXTRA); + } else if (selectedRepresentationKeys.get(0) instanceof Parcelable) { + Parcelable[] parcelables = new Parcelable[selectedRepresentationKeys.size()]; + selectedRepresentationKeys.toArray(parcelables); + playerIntent.putExtra( + PlayerActivity.MANIFEST_FILTER_EXTRA, new ParcelableArray<>(parcelables)); + } else { + String[] strings = new String[selectedRepresentationKeys.size()]; + selectedRepresentationKeys.toArray(strings); + playerIntent.putExtra(PlayerActivity.MANIFEST_FILTER_EXTRA, strings); + } + startActivity(playerIntent); + } + + private void stopAll() { + if (cancelDialog != null) { + cancelDialog.dismiss(); + } + + if (manifestDownloaderTask != null) { + manifestDownloaderTask.cancel(true); + manifestDownloaderTask = null; + } + } + + private void updateRepresentationsList() { + if (cancelDialog == null) { + cancelDialog = + new AlertDialog.Builder(this) + .setMessage("Please wait") + .setOnCancelListener( + new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + stopAll(); + } + }) + .create(); + } + cancelDialog.setTitle("Updating representations"); + cancelDialog.show(); + manifestDownloaderTask = new ManifestDownloaderTask().execute(); + } + + private ArrayList getSelectedRepresentationKeys(boolean unselect) { + SparseBooleanArray checked = representationList.getCheckedItemPositions(); + ArrayList representations = new ArrayList<>(checked.size()); + for (int i = 0; i < checked.size(); i++) { + if (checked.valueAt(i)) { + int position = checked.keyAt(i); + RepresentationItem item = + (RepresentationItem) representationList.getItemAtPosition(position); + representations.add(item.key); + if (unselect) { + representationList.setItemChecked(position, false); + } + } + } + return representations; + } + + private void showToastAndFinish(int resId) { + showToast(resId); + finish(); + } + + private void showToast(int resId) { + Toast.makeText(getApplicationContext(), resId, Toast.LENGTH_LONG).show(); + } + + private static final class RepresentationItem { + public final Object key; + public final String title; + + public RepresentationItem(Object key, String title) { + this.key = key; + this.title = title; + } + + @Override + public String toString() { + return title; + } + } + + private final class ManifestDownloaderTask + extends AsyncTask> { + + @Override + protected List doInBackground(Void... ignore) { + List items; + try { + items = downloadUtilMethods.getRepresentationItems(); + } catch (IOException | InterruptedException e) { + Log.e(TAG, "Getting representations failed", e); + return null; + } + return items; + } + + @Override + protected void onPostExecute(List items) { + if (items == null) { + showToastAndFinish(R.string.manifest_download_error); + return; + } + arrayAdapter.clear(); + for (RepresentationItem representationItem : items) { + arrayAdapter.add(representationItem); + } + stopAll(); + } + } + + private abstract static class DownloadUtilMethods { + + protected final Uri manifestUri; + protected final DownloaderConstructorHelper constructorHelper; + + public DownloadUtilMethods(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { + this.manifestUri = manifestUri; + this.constructorHelper = constructorHelper; + } + + public abstract List getRepresentationItems() + throws IOException, InterruptedException; + + public abstract DownloadAction getDownloadAction( + String sampleName, ArrayList representationKeys); + + public abstract DownloadAction getRemoveAction(); + } + + private static final class DashDownloadUtilMethods extends DownloadUtilMethods { + + public DashDownloadUtilMethods(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { + super(manifestUri, constructorHelper); + } + + @Override + public List getRepresentationItems() + throws IOException, InterruptedException { + DashDownloader downloader = new DashDownloader(manifestUri, constructorHelper); + ArrayList items = new ArrayList<>(); + for (RepresentationKey key : downloader.getAllRepresentationKeys()) { + downloader.selectRepresentations(new RepresentationKey[] {key}); + try { + downloader.init(); + } catch (IOException e) { + continue; + } + Representation representation = + downloader + .getManifest() + .getPeriod(key.periodIndex) + .adaptationSets + .get(key.adaptationSetIndex) + .representations + .get(key.representationIndex); + String trackName = DemoUtil.buildTrackName(representation.format); + int totalSegments = downloader.getTotalSegments(); + int downloadedRatio = 0; + if (totalSegments != 0) { + downloadedRatio = (downloader.getDownloadedSegments() * 100) / totalSegments; + } + String name = key.toString() + ' ' + trackName + ' ' + downloadedRatio + '%'; + items.add(new RepresentationItem(key, name)); + } + return items; + } + + @Override + public DownloadAction getDownloadAction( + String sampleName, ArrayList representationKeys) { + StringBuilder sb = new StringBuilder(sampleName); + RepresentationKey[] keys = + representationKeys.toArray(new RepresentationKey[representationKeys.size()]); + for (RepresentationKey representationKey : keys) { + sb.append('-').append(representationKey); + } + return new DashDownloadAction(manifestUri, false, sb.toString(), keys); + } + + @Override + public DownloadAction getRemoveAction() { + return new DashDownloadAction(manifestUri, true, null); + } + } + + private static final class HlsDownloadUtilMethods extends DownloadUtilMethods { + + public HlsDownloadUtilMethods(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { + super(manifestUri, constructorHelper); + } + + @Override + public List getRepresentationItems() + throws IOException, InterruptedException { + HlsDownloader downloader = new HlsDownloader(manifestUri, constructorHelper); + ArrayList items = new ArrayList<>(); + for (String key : downloader.getAllRepresentationKeys()) { + downloader.selectRepresentations(new String[] {key}); + try { + downloader.init(); + } catch (IOException e) { + continue; + } + int totalSegments = downloader.getTotalSegments(); + int downloadedRatio = 0; + if (totalSegments != 0) { + downloadedRatio = (downloader.getDownloadedSegments() * 100) / totalSegments; + } + String name = key + ' ' /*+ trackName + ' '*/ + downloadedRatio + '%'; + items.add(new RepresentationItem(key, name)); + } + return items; + } + + @Override + public DownloadAction getDownloadAction( + String sampleName, ArrayList representationKeys) { + StringBuilder sb = new StringBuilder(sampleName); + String[] keys = representationKeys.toArray(new String[representationKeys.size()]); + for (String key : keys) { + sb.append('-').append(key); + } + return new HlsDownloadAction(manifestUri, false, sb.toString(), keys); + } + + @Override + public DownloadAction getRemoveAction() { + return new HlsDownloadAction(manifestUri, true, null); + } + } + + private static final class SsDownloadUtilMethods extends DownloadUtilMethods { + + public SsDownloadUtilMethods(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { + super(manifestUri, constructorHelper); + } + + @Override + public List getRepresentationItems() + throws IOException, InterruptedException { + SsDownloader downloader = new SsDownloader(manifestUri, constructorHelper); + ArrayList items = new ArrayList<>(); + for (TrackKey key : downloader.getAllRepresentationKeys()) { + downloader.selectRepresentations(new TrackKey[] {key}); + try { + downloader.init(); + } catch (IOException e) { + continue; + } + Format format = + downloader.getManifest().streamElements[key.streamElementIndex].formats[key.trackIndex]; + String trackName = DemoUtil.buildTrackName(format); + int totalSegments = downloader.getTotalSegments(); + int downloadedRatio = 0; + if (totalSegments != 0) { + downloadedRatio = (downloader.getDownloadedSegments() * 100) / totalSegments; + } + String name = key.toString() + ' ' + trackName + ' ' + downloadedRatio + '%'; + items.add(new RepresentationItem(key, name)); + } + return items; + } + + @Override + public DownloadAction getDownloadAction( + String sampleName, ArrayList representationKeys) { + StringBuilder sb = new StringBuilder(sampleName); + TrackKey[] keys = representationKeys.toArray(new TrackKey[representationKeys.size()]); + for (TrackKey trackKey : keys) { + sb.append('-').append(trackKey); + } + return new SsDownloadAction(manifestUri, false, sb.toString(), keys); + } + + @Override + public DownloadAction getRemoveAction() { + return new SsDownloadAction(manifestUri, true, null); + } + } + + private static final class ProgressiveDownloadUtilMethods extends DownloadUtilMethods { + + public ProgressiveDownloadUtilMethods( + Uri manifestUri, DownloaderConstructorHelper constructorHelper) { + super(manifestUri, constructorHelper); + } + + @Override + public List getRepresentationItems() { + ProgressiveDownloader downloader = + new ProgressiveDownloader(manifestUri.toString(), null, constructorHelper); + ArrayList items = new ArrayList<>(); + { + downloader.init(); + int downloadedRatio = (int) downloader.getDownloadPercentage(); + String name = "track 1 - " + downloadedRatio + '%'; + items.add(new RepresentationItem(null, name)); + } + return items; + } + + @Override + public DownloadAction getDownloadAction( + String sampleName, ArrayList representationKeys) { + return new ProgressiveDownloadAction(manifestUri.toString(), null, false, sampleName); + } + + @Override + public DownloadAction getRemoveAction() { + return new ProgressiveDownloadAction(manifestUri.toString(), null, true, null); + } + } +} 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 9375f9bba6..5f49998431 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.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.KeyEvent; @@ -84,6 +83,7 @@ import java.lang.reflect.Constructor; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; +import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -276,11 +276,15 @@ public class PlayerActivity extends Activity String action = intent.getAction(); Uri[] uris; String[] extensions; - Parcelable[] manifestFilters; + Object[] 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)}; + Object filter = intent.getParcelableExtra(MANIFEST_FILTER_EXTRA); + if (filter == null) { + filter = intent.getStringArrayExtra(MANIFEST_FILTER_EXTRA); + } + manifestFilters = new Object[] {filter}; } else if (ACTION_VIEW_LIST.equals(action)) { String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA); uris = new Uri[uriStrings.length]; @@ -293,7 +297,7 @@ public class PlayerActivity extends Activity } manifestFilters = intent.getParcelableArrayExtra(MANIFEST_FILTER_LIST_EXTRA); if (manifestFilters == null) { - manifestFilters = new Parcelable[uriStrings.length]; + manifestFilters = new Object[uriStrings.length]; } } else { showToast(getString(R.string.unexpected_intent_action, action)); @@ -385,8 +389,15 @@ 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; + List filter; + Object manifestFilter = manifestFilters[i]; + if (manifestFilter instanceof ParcelableArray) { + filter = ((ParcelableArray) manifestFilter).asList(); + } else if (manifestFilter instanceof String[]) { + filter = Arrays.asList((String[]) manifestFilter); + } else { + filter = null; + } mediaSources[i] = buildMediaSource(uris[i], extensions[i], filter); } mediaSource = 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 5febb949f1..46faebc7fb 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 @@ -26,13 +26,16 @@ 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; import android.widget.BaseExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.ExpandableListView.OnChildClickListener; +import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.offline.DownloadService; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSourceInputStream; import com.google.android.exoplayer2.upstream.DataSpec; @@ -81,6 +84,18 @@ public class SampleChooserActivity extends Activity { } SampleListLoader loaderTask = new SampleListLoader(); loaderTask.execute(uris); + + startDownloadServiceForeground(); + } + + private void startDownloadServiceForeground() { + Intent serviceIntent = + new Intent(DownloadService.ACTION_INIT).setPackage("com.google.android.exoplayer2.demo"); + if (Util.SDK_INT >= 26) { + startForegroundService(serviceIntent); + } else { + startService(serviceIntent); + } } private void onSampleGroups(final List groups, boolean sawError) { @@ -104,6 +119,21 @@ public class SampleChooserActivity extends Activity { startActivity(sample.buildIntent(this)); } + private void onSampleDownloadButtonClicked(Sample sample) { + if (!(sample instanceof UriSample) || sample.drmInfo != null) { + Toast.makeText( + getApplicationContext(), + R.string.supports_downloading_only_single_not_drm_protected, + Toast.LENGTH_SHORT) + .show(); + return; + } + Intent intent = new Intent(this, DownloaderActivity.class); + intent.putExtra(DownloaderActivity.SAMPLE_NAME, sample.name); + intent.putExtra(DownloaderActivity.PLAYER_INTENT, sample.buildIntent(this)); + startActivity(intent); + } + private final class SampleListLoader extends AsyncTask> { private boolean sawError; @@ -278,10 +308,11 @@ public class SampleChooserActivity extends Activity { } - private static final class SampleAdapter extends BaseExpandableListAdapter { + private final class SampleAdapter extends BaseExpandableListAdapter { private final Context context; private final List sampleGroups; + private OnClickListener onClickListener; public SampleAdapter(Context context, List sampleGroups) { this.context = context; @@ -303,10 +334,9 @@ public class SampleChooserActivity extends Activity { View convertView, ViewGroup parent) { View view = convertView; if (view == null) { - view = LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, parent, - false); + view = LayoutInflater.from(context).inflate(R.layout.sample_list_item, parent, false); } - ((TextView) view).setText(getChild(groupPosition, childPosition).name); + initializeChildView(view, getChild(groupPosition, childPosition)); return view; } @@ -352,6 +382,28 @@ public class SampleChooserActivity extends Activity { return true; } + private void initializeChildView(View view, Sample sample) { + TextView sampleTitle = view.findViewById(R.id.sample_title); + sampleTitle.setText(sample.name); + + ImageButton downloadButton = view.findViewById(R.id.download_button); + downloadButton.setFocusable(false); + downloadButton.setOnClickListener(getOnClickListener()); + downloadButton.setTag(sample); + } + + private OnClickListener getOnClickListener() { + if (onClickListener == null) { + onClickListener = + new OnClickListener() { + @Override + public void onClick(View v) { + onSampleDownloadButtonClicked((Sample) v.getTag()); + } + }; + } + return onClickListener; + } } private static final class SampleGroup { diff --git a/demos/main/src/main/res/layout/downloader_activity.xml b/demos/main/src/main/res/layout/downloader_activity.xml new file mode 100644 index 0000000000..5b39e9c7fd --- /dev/null +++ b/demos/main/src/main/res/layout/downloader_activity.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + +