From 1323dd63d52162ebe604de5505ce392b90458658 Mon Sep 17 00:00:00 2001 From: bachinger Date: Thu, 23 Apr 2020 23:23:17 +0100 Subject: [PATCH] Remove Sample from SampleChooserActivity Unmarshal from json to MediaItem instead of Sample. Further the playlist of MediaItems is converted to Intent extras which are read by the PlayerActivity. PiperOrigin-RevId: 308141231 --- .../exoplayer2/demo/DownloadTracker.java | 45 ++-- .../android/exoplayer2/demo/IntentUtil.java | 132 ++++++++-- .../android/exoplayer2/demo/Sample.java | 191 --------------- .../demo/SampleChooserActivity.java | 229 ++++++++++-------- .../google/android/exoplayer2/util/Util.java | 23 ++ .../source/DefaultMediaSourceFactory.java | 18 +- 6 files changed, 289 insertions(+), 349 deletions(-) delete mode 100644 demos/main/src/main/java/com/google/android/exoplayer2/demo/Sample.java 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 index 2b79071393..5199e1d358 100644 --- 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 @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.demo; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + import android.content.Context; import android.content.DialogInterface; import android.net.Uri; @@ -23,8 +25,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentManager; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.RenderersFactory; -import com.google.android.exoplayer2.demo.Sample.UriSample; import com.google.android.exoplayer2.offline.Download; import com.google.android.exoplayer2.offline.DownloadCursor; import com.google.android.exoplayer2.offline.DownloadHelper; @@ -82,8 +84,8 @@ public class DownloadTracker { listeners.remove(listener); } - public boolean isDownloaded(Uri uri) { - Download download = downloads.get(uri); + public boolean isDownloaded(MediaItem mediaItem) { + Download download = downloads.get(checkNotNull(mediaItem.playbackProperties).sourceUri); return download != null && download.state != Download.STATE_FAILED; } @@ -93,8 +95,8 @@ public class DownloadTracker { } public void toggleDownload( - FragmentManager fragmentManager, UriSample sample, RenderersFactory renderersFactory) { - Download download = downloads.get(sample.uri); + FragmentManager fragmentManager, MediaItem mediaItem, RenderersFactory renderersFactory) { + Download download = downloads.get(checkNotNull(mediaItem.playbackProperties).sourceUri); if (download != null) { DownloadService.sendRemoveDownload( context, DemoDownloadService.class, download.request.id, /* foreground= */ false); @@ -104,9 +106,7 @@ public class DownloadTracker { } startDownloadDialogHelper = new StartDownloadDialogHelper( - fragmentManager, - getDownloadHelper(sample.uri, sample.extension, renderersFactory), - sample); + fragmentManager, getDownloadHelper(mediaItem, renderersFactory), mediaItem); } } @@ -121,18 +121,24 @@ public class DownloadTracker { } } - private DownloadHelper getDownloadHelper( - Uri uri, String extension, RenderersFactory renderersFactory) { - int type = Util.inferContentType(uri, extension); + private DownloadHelper getDownloadHelper(MediaItem mediaItem, RenderersFactory renderersFactory) { + MediaItem.PlaybackProperties playbackProperties = checkNotNull(mediaItem.playbackProperties); + @C.ContentType + int type = + Util.inferContentTypeWithMimeType( + playbackProperties.sourceUri, playbackProperties.mimeType); switch (type) { case C.TYPE_DASH: - return DownloadHelper.forDash(context, uri, dataSourceFactory, renderersFactory); + return DownloadHelper.forDash( + context, playbackProperties.sourceUri, dataSourceFactory, renderersFactory); case C.TYPE_SS: - return DownloadHelper.forSmoothStreaming(context, uri, dataSourceFactory, renderersFactory); + return DownloadHelper.forSmoothStreaming( + context, playbackProperties.sourceUri, dataSourceFactory, renderersFactory); case C.TYPE_HLS: - return DownloadHelper.forHls(context, uri, dataSourceFactory, renderersFactory); + return DownloadHelper.forHls( + context, playbackProperties.sourceUri, dataSourceFactory, renderersFactory); case C.TYPE_OTHER: - return DownloadHelper.forProgressive(context, uri); + return DownloadHelper.forProgressive(context, playbackProperties.sourceUri); default: throw new IllegalStateException("Unsupported type: " + type); } @@ -166,16 +172,16 @@ public class DownloadTracker { private final FragmentManager fragmentManager; private final DownloadHelper downloadHelper; - private final UriSample sample; + private final MediaItem mediaItem; private TrackSelectionDialog trackSelectionDialog; private MappedTrackInfo mappedTrackInfo; public StartDownloadDialogHelper( - FragmentManager fragmentManager, DownloadHelper downloadHelper, UriSample sample) { + FragmentManager fragmentManager, DownloadHelper downloadHelper, MediaItem mediaItem) { this.fragmentManager = fragmentManager; this.downloadHelper = downloadHelper; - this.sample = sample; + this.mediaItem = mediaItem; downloadHelper.prepare(this); } @@ -270,7 +276,8 @@ public class DownloadTracker { } private DownloadRequest buildDownloadRequest() { - return downloadHelper.getDownloadRequest(Util.getUtf8Bytes(sample.name)); + return downloadHelper.getDownloadRequest( + Util.getUtf8Bytes(checkNotNull(mediaItem.mediaMetadata.title))); } } } diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/IntentUtil.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/IntentUtil.java index f3d442ee8d..b4c0e2aa7a 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/IntentUtil.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/IntentUtil.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.demo; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Assertions.checkState; import android.content.Intent; import android.net.Uri; @@ -23,6 +24,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.offline.DownloadRequest; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; @@ -32,9 +34,24 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -/** Util to read from an intent. */ +/** Util to read from and populate an intent. */ public class IntentUtil { + /** A tag to hold custom playback configuration attributes. */ + public static class Tag { + + /** Whether the stream is a live stream. */ + public final boolean isLive; + /** The spherical stereo mode or null. */ + @Nullable public final String sphericalStereoMode; + + /** Creates an instance. */ + public Tag(boolean isLive, @Nullable String sphericalStereoMode) { + this.isLive = isLive; + this.sphericalStereoMode = sphericalStereoMode; + } + } + // Actions. public static final String ACTION_VIEW = "com.google.android.exoplayer.demo.action.VIEW"; @@ -57,8 +74,10 @@ public class IntentUtil { // Media item configuration extras. public static final String URI_EXTRA = "uri"; - public static final String EXTENSION_EXTRA = "extension"; public static final String IS_LIVE_EXTRA = "is_live"; + public static final String MIME_TYPE_EXTRA = "mime_type"; + // For backwards compatibility only. + public static final String EXTENSION_EXTRA = "extension"; public static final String DRM_SCHEME_EXTRA = "drm_scheme"; public static final String DRM_LICENSE_URL_EXTRA = "drm_license_url"; @@ -100,15 +119,56 @@ public class IntentUtil { return mediaItems; } + /** Populates the intent with the given list of {@link MediaItem media items}. */ + public static void addToIntent(List mediaItems, Intent intent) { + Assertions.checkArgument(!mediaItems.isEmpty()); + if (mediaItems.size() == 1) { + MediaItem.PlaybackProperties playbackProperties = + checkNotNull(mediaItems.get(0).playbackProperties); + intent.setAction(IntentUtil.ACTION_VIEW).setData(playbackProperties.sourceUri); + addPlaybackPropertiesToIntent(playbackProperties, intent, /* extrasKeySuffix= */ ""); + } else { + intent.setAction(IntentUtil.ACTION_VIEW_LIST); + for (int i = 0; i < mediaItems.size(); i++) { + MediaItem.PlaybackProperties playbackProperties = + checkNotNull(mediaItems.get(i).playbackProperties); + intent.putExtra(IntentUtil.URI_EXTRA + ("_" + i), playbackProperties.sourceUri.toString()); + addPlaybackPropertiesToIntent(playbackProperties, intent, /* extrasKeySuffix= */ "_" + i); + } + } + } + + /** Makes a best guess to infer the MIME type from a {@link Uri} and an optional extension. */ + @Nullable + public static String inferAdaptiveStreamMimeType(Uri uri, @Nullable String extension) { + @C.ContentType int contentType = Util.inferContentType(uri, extension); + switch (contentType) { + case C.TYPE_DASH: + return MimeTypes.APPLICATION_MPD; + case C.TYPE_HLS: + return MimeTypes.APPLICATION_M3U8; + case C.TYPE_SS: + return MimeTypes.APPLICATION_SS; + case C.TYPE_OTHER: + default: + return null; + } + } + private static MediaItem createMediaItemFromIntent( Uri uri, Intent intent, String extrasKeySuffix, @Nullable DownloadRequest downloadRequest) { - String extension = intent.getStringExtra(EXTENSION_EXTRA + extrasKeySuffix); + String mimeType = intent.getStringExtra(MIME_TYPE_EXTRA + extrasKeySuffix); + if (mimeType == null) { + // Try to use extension for backwards compatibility. + String extension = intent.getStringExtra(EXTENSION_EXTRA + extrasKeySuffix); + mimeType = inferAdaptiveStreamMimeType(uri, extension); + } MediaItem.Builder builder = new MediaItem.Builder() .setSourceUri(uri) .setStreamKeys(downloadRequest != null ? downloadRequest.streamKeys : null) .setCustomCacheKey(downloadRequest != null ? downloadRequest.customCacheKey : null) - .setMimeType(inferAdaptiveStreamMimeType(uri, extension)) + .setMimeType(mimeType) .setAdTagUri(intent.getStringExtra(AD_TAG_URI_EXTRA + extrasKeySuffix)) .setSubtitles(createSubtitlesFromIntent(intent, extrasKeySuffix)); return populateDrmPropertiesFromIntent(builder, intent, extrasKeySuffix).build(); @@ -178,19 +238,57 @@ public class IntentUtil { return new ArrayList<>(trackTypes); } - @Nullable - private static String inferAdaptiveStreamMimeType(Uri uri, @Nullable String extension) { - @C.ContentType int contentType = Util.inferContentType(uri, extension); - switch (contentType) { - case C.TYPE_DASH: - return MimeTypes.APPLICATION_MPD; - case C.TYPE_HLS: - return MimeTypes.APPLICATION_M3U8; - case C.TYPE_SS: - return MimeTypes.APPLICATION_SS; - case C.TYPE_OTHER: - default: - return null; + private static void addPlaybackPropertiesToIntent( + MediaItem.PlaybackProperties playbackProperties, Intent intent, String extrasKeySuffix) { + boolean isLive = false; + String sphericalStereoMode = null; + if (playbackProperties.tag instanceof Tag) { + Tag tag = (Tag) playbackProperties.tag; + isLive = tag.isLive; + sphericalStereoMode = tag.sphericalStereoMode; + } + intent + .putExtra(MIME_TYPE_EXTRA + extrasKeySuffix, playbackProperties.mimeType) + .putExtra( + AD_TAG_URI_EXTRA + extrasKeySuffix, + playbackProperties.adTagUri != null ? playbackProperties.adTagUri.toString() : null) + .putExtra(IS_LIVE_EXTRA + extrasKeySuffix, isLive) + .putExtra(SPHERICAL_STEREO_MODE_EXTRA, sphericalStereoMode); + if (playbackProperties.drmConfiguration != null) { + addDrmConfigurationToIntent(playbackProperties.drmConfiguration, intent, extrasKeySuffix); + } + if (!playbackProperties.subtitles.isEmpty()) { + checkState(playbackProperties.subtitles.size() == 1); + MediaItem.Subtitle subtitle = playbackProperties.subtitles.get(0); + intent.putExtra(SUBTITLE_URI_EXTRA + extrasKeySuffix, subtitle.uri.toString()); + intent.putExtra(SUBTITLE_MIME_TYPE_EXTRA + extrasKeySuffix, subtitle.mimeType); + intent.putExtra(SUBTITLE_LANGUAGE_EXTRA + extrasKeySuffix, subtitle.language); } } + + private static void addDrmConfigurationToIntent( + MediaItem.DrmConfiguration drmConfiguration, Intent intent, String extrasKeySuffix) { + intent.putExtra(DRM_SCHEME_EXTRA + extrasKeySuffix, drmConfiguration.uuid.toString()); + intent.putExtra( + DRM_LICENSE_URL_EXTRA + extrasKeySuffix, + checkNotNull(drmConfiguration.licenseUri).toString()); + intent.putExtra(DRM_MULTI_SESSION_EXTRA + extrasKeySuffix, drmConfiguration.multiSession); + + String[] drmKeyRequestProperties = new String[drmConfiguration.requestHeaders.size() * 2]; + int index = 0; + for (Map.Entry entry : drmConfiguration.requestHeaders.entrySet()) { + drmKeyRequestProperties[index++] = entry.getKey(); + drmKeyRequestProperties[index++] = entry.getValue(); + } + intent.putExtra(DRM_KEY_REQUEST_PROPERTIES_EXTRA + extrasKeySuffix, drmKeyRequestProperties); + + ArrayList typeStrings = new ArrayList<>(); + for (int type : drmConfiguration.sessionForClearTypes) { + // Only audio and video are supported. + Assertions.checkState(type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO); + typeStrings.add(type == C.TRACK_TYPE_AUDIO ? "audio" : "video"); + } + intent.putExtra( + DRM_SESSION_FOR_CLEAR_TYPES_EXTRA + extrasKeySuffix, typeStrings.toArray(new String[0])); + } } diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/Sample.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/Sample.java deleted file mode 100644 index fe64a74b4d..0000000000 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/Sample.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2019 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.content.Intent; -import android.net.Uri; -import androidx.annotation.Nullable; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.Util; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.UUID; - -/* package */ abstract class Sample { - - public static final class UriSample extends Sample { - - public final Uri uri; - public final String extension; - public final boolean isLive; - public final DrmInfo drmInfo; - public final Uri adTagUri; - @Nullable public final String sphericalStereoMode; - @Nullable SubtitleInfo subtitleInfo; - - public UriSample( - String name, - Uri uri, - String extension, - boolean isLive, - DrmInfo drmInfo, - Uri adTagUri, - @Nullable String sphericalStereoMode, - @Nullable SubtitleInfo subtitleInfo) { - super(name); - this.uri = uri; - this.extension = extension; - this.isLive = isLive; - this.drmInfo = drmInfo; - this.adTagUri = adTagUri; - this.sphericalStereoMode = sphericalStereoMode; - this.subtitleInfo = subtitleInfo; - } - - @Override - public void addToIntent(Intent intent) { - intent.setAction(IntentUtil.ACTION_VIEW).setData(uri); - intent.putExtra(IntentUtil.IS_LIVE_EXTRA, isLive); - intent.putExtra(IntentUtil.SPHERICAL_STEREO_MODE_EXTRA, sphericalStereoMode); - addPlayerConfigToIntent(intent, /* extrasKeySuffix= */ ""); - } - - public void addToPlaylistIntent(Intent intent, String extrasKeySuffix) { - intent.putExtra(IntentUtil.URI_EXTRA + extrasKeySuffix, uri.toString()); - intent.putExtra(IntentUtil.IS_LIVE_EXTRA + extrasKeySuffix, isLive); - addPlayerConfigToIntent(intent, extrasKeySuffix); - } - - private void addPlayerConfigToIntent(Intent intent, String extrasKeySuffix) { - intent - .putExtra(IntentUtil.EXTENSION_EXTRA + extrasKeySuffix, extension) - .putExtra( - IntentUtil.AD_TAG_URI_EXTRA + extrasKeySuffix, - adTagUri != null ? adTagUri.toString() : null); - if (drmInfo != null) { - drmInfo.addToIntent(intent, extrasKeySuffix); - } - if (subtitleInfo != null) { - subtitleInfo.addToIntent(intent, extrasKeySuffix); - } - } - } - - public static final class PlaylistSample extends Sample { - - public final UriSample[] children; - - public PlaylistSample(String name, UriSample... children) { - super(name); - this.children = children; - } - - @Override - public void addToIntent(Intent intent) { - intent.setAction(IntentUtil.ACTION_VIEW_LIST); - for (int i = 0; i < children.length; i++) { - children[i].addToPlaylistIntent(intent, /* extrasKeySuffix= */ "_" + i); - } - } - } - - public static final class DrmInfo { - - public final UUID drmScheme; - public final String drmLicenseUrl; - public final String[] drmKeyRequestProperties; - public final int[] drmSessionForClearTypes; - public final boolean drmMultiSession; - - public DrmInfo( - UUID drmScheme, - String drmLicenseUrl, - String[] drmKeyRequestProperties, - int[] drmSessionForClearTypes, - boolean drmMultiSession) { - this.drmScheme = drmScheme; - this.drmLicenseUrl = drmLicenseUrl; - this.drmKeyRequestProperties = drmKeyRequestProperties; - this.drmSessionForClearTypes = drmSessionForClearTypes; - this.drmMultiSession = drmMultiSession; - } - - public void addToIntent(Intent intent, String extrasKeySuffix) { - Assertions.checkNotNull(intent); - intent.putExtra(IntentUtil.DRM_SCHEME_EXTRA + extrasKeySuffix, drmScheme.toString()); - intent.putExtra(IntentUtil.DRM_LICENSE_URL_EXTRA + extrasKeySuffix, drmLicenseUrl); - intent.putExtra( - IntentUtil.DRM_KEY_REQUEST_PROPERTIES_EXTRA + extrasKeySuffix, drmKeyRequestProperties); - ArrayList typeStrings = new ArrayList<>(); - for (int type : drmSessionForClearTypes) { - // Only audio and video are supported. - typeStrings.add(type == C.TRACK_TYPE_AUDIO ? "audio" : "video"); - } - intent.putExtra( - IntentUtil.DRM_SESSION_FOR_CLEAR_TYPES_EXTRA + extrasKeySuffix, - typeStrings.toArray(new String[0])); - intent.putExtra(IntentUtil.DRM_MULTI_SESSION_EXTRA + extrasKeySuffix, drmMultiSession); - } - } - - public static final class SubtitleInfo { - - public final Uri uri; - public final String mimeType; - @Nullable public final String language; - - public SubtitleInfo(Uri uri, String mimeType, @Nullable String language) { - this.uri = Assertions.checkNotNull(uri); - this.mimeType = Assertions.checkNotNull(mimeType); - this.language = language; - } - - public void addToIntent(Intent intent, String extrasKeySuffix) { - intent.putExtra(IntentUtil.SUBTITLE_URI_EXTRA + extrasKeySuffix, uri.toString()); - intent.putExtra(IntentUtil.SUBTITLE_MIME_TYPE_EXTRA + extrasKeySuffix, mimeType); - intent.putExtra(IntentUtil.SUBTITLE_LANGUAGE_EXTRA + extrasKeySuffix, language); - } - } - - public static int[] toTrackTypeArray(@Nullable String[] trackTypeStringsArray) { - if (trackTypeStringsArray == null) { - return new int[0]; - } - HashSet trackTypes = new HashSet<>(); - for (String trackTypeString : trackTypeStringsArray) { - switch (Util.toLowerInvariant(trackTypeString)) { - case "audio": - trackTypes.add(C.TRACK_TYPE_AUDIO); - break; - case "video": - trackTypes.add(C.TRACK_TYPE_VIDEO); - break; - default: - throw new IllegalArgumentException("Invalid track type: " + trackTypeString); - } - } - return Util.toArray(new ArrayList<>(trackTypes)); - } - - public final String name; - - public Sample(String name) { - this.name = name; - } - - public abstract void addToIntent(Intent intent); -} 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 f867c15782..bfc476f340 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 @@ -15,10 +15,13 @@ */ package com.google.android.exoplayer2.demo; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.AssetManager; import android.net.Uri; import android.os.AsyncTask; @@ -39,11 +42,11 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.RenderersFactory; -import com.google.android.exoplayer2.demo.Sample.DrmInfo; -import com.google.android.exoplayer2.demo.Sample.PlaylistSample; -import com.google.android.exoplayer2.demo.Sample.UriSample; import com.google.android.exoplayer2.offline.DownloadService; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSourceInputStream; @@ -58,7 +61,10 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; /** An activity for selecting from a list of media samples. */ public class SampleChooserActivity extends AppCompatActivity @@ -182,7 +188,7 @@ public class SampleChooserActivity extends AppCompatActivity } private void loadSample() { - Assertions.checkNotNull(uris); + checkNotNull(uris); for (int i = 0; i < uris.length; i++) { Uri uri = Uri.parse(uris[i]); @@ -195,12 +201,12 @@ public class SampleChooserActivity extends AppCompatActivity loaderTask.execute(uris); } - private void onSampleGroups(final List groups, boolean sawError) { + private void onPlaylistGroups(final List groups, boolean sawError) { if (sawError) { Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG) .show(); } - sampleAdapter.setSampleGroups(groups); + sampleAdapter.setPlaylistGroups(groups); SharedPreferences preferences = getPreferences(MODE_PRIVATE); @@ -227,7 +233,7 @@ public class SampleChooserActivity extends AppCompatActivity prefEditor.putInt(CHILD_POSITION_PREFERENCE_KEY, childPosition); prefEditor.apply(); - Sample sample = (Sample) view.getTag(); + PlaylistHolder playlistHolder = (PlaylistHolder) view.getTag(); Intent intent = new Intent(this, PlayerActivity.class); intent.putExtra( IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, @@ -238,13 +244,13 @@ public class SampleChooserActivity extends AppCompatActivity : IntentUtil.ABR_ALGORITHM_DEFAULT; intent.putExtra(IntentUtil.ABR_ALGORITHM_EXTRA, abrAlgorithm); intent.putExtra(IntentUtil.TUNNELING_EXTRA, isNonNullAndChecked(tunnelingMenuItem)); - sample.addToIntent(intent); + IntentUtil.addToIntent(playlistHolder.mediaItems, intent); startActivity(intent); return true; } - private void onSampleDownloadButtonClicked(Sample sample) { - int downloadUnsupportedStringId = getDownloadUnsupportedStringId(sample); + private void onSampleDownloadButtonClicked(PlaylistHolder playlistHolder) { + int downloadUnsupportedStringId = getDownloadUnsupportedStringId(playlistHolder); if (downloadUnsupportedStringId != 0) { Toast.makeText(getApplicationContext(), downloadUnsupportedStringId, Toast.LENGTH_LONG) .show(); @@ -253,25 +259,26 @@ public class SampleChooserActivity extends AppCompatActivity ((DemoApplication) getApplication()) .buildRenderersFactory(isNonNullAndChecked(preferExtensionDecodersMenuItem)); downloadTracker.toggleDownload( - getSupportFragmentManager(), (UriSample) sample, renderersFactory); + getSupportFragmentManager(), playlistHolder.mediaItems.get(0), renderersFactory); } } - private int getDownloadUnsupportedStringId(Sample sample) { - if (sample instanceof PlaylistSample) { + private int getDownloadUnsupportedStringId(PlaylistHolder playlistHolder) { + if (playlistHolder.mediaItems.size() > 1) { return R.string.download_playlist_unsupported; } - UriSample uriSample = (UriSample) sample; - if (uriSample.drmInfo != null) { + MediaItem.PlaybackProperties playbackProperties = + checkNotNull(playlistHolder.mediaItems.get(0).playbackProperties); + if (playbackProperties.drmConfiguration != null) { return R.string.download_drm_unsupported; } - if (uriSample.isLive) { + if (((IntentUtil.Tag) checkNotNull(playbackProperties.tag)).isLive) { return R.string.download_live_unsupported; } - if (uriSample.adTagUri != null) { + if (playbackProperties.adTagUri != null) { return R.string.download_ads_unsupported; } - String scheme = uriSample.uri.getScheme(); + String scheme = playbackProperties.sourceUri.getScheme(); if (!("http".equals(scheme) || "https".equals(scheme))) { return R.string.download_scheme_unsupported; } @@ -283,13 +290,13 @@ public class SampleChooserActivity extends AppCompatActivity return menuItem != null && menuItem.isChecked(); } - private final class SampleListLoader extends AsyncTask> { + private final class SampleListLoader extends AsyncTask> { private boolean sawError; @Override - protected List doInBackground(String... uris) { - List result = new ArrayList<>(); + protected List doInBackground(String... uris) { + List result = new ArrayList<>(); Context context = getApplicationContext(); String userAgent = Util.getUserAgent(context, "ExoPlayerDemo"); DataSource dataSource = @@ -298,7 +305,7 @@ public class SampleChooserActivity extends AppCompatActivity DataSpec dataSpec = new DataSpec(Uri.parse(uri)); InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); try { - readSampleGroups(new JsonReader(new InputStreamReader(inputStream, "UTF-8")), result); + readPlaylistGroups(new JsonReader(new InputStreamReader(inputStream, "UTF-8")), result); } catch (Exception e) { Log.e(TAG, "Error loading sample list: " + uri, e); sawError = true; @@ -310,21 +317,23 @@ public class SampleChooserActivity extends AppCompatActivity } @Override - protected void onPostExecute(List result) { - onSampleGroups(result, sawError); + protected void onPostExecute(List result) { + onPlaylistGroups(result, sawError); } - private void readSampleGroups(JsonReader reader, List groups) throws IOException { + private void readPlaylistGroups(JsonReader reader, List groups) + throws IOException { reader.beginArray(); while (reader.hasNext()) { - readSampleGroup(reader, groups); + readPlaylistGroup(reader, groups); } reader.endArray(); } - private void readSampleGroup(JsonReader reader, List groups) throws IOException { + private void readPlaylistGroup(JsonReader reader, List groups) + throws IOException { String groupName = ""; - ArrayList samples = new ArrayList<>(); + ArrayList playlistHolders = new ArrayList<>(); reader.beginObject(); while (reader.hasNext()) { @@ -336,7 +345,7 @@ public class SampleChooserActivity extends AppCompatActivity case "samples": reader.beginArray(); while (reader.hasNext()) { - samples.add(readEntry(reader, false)); + playlistHolders.add(readEntry(reader, false)); } reader.endArray(); break; @@ -349,34 +358,28 @@ public class SampleChooserActivity extends AppCompatActivity } reader.endObject(); - SampleGroup group = getGroup(groupName, groups); - group.samples.addAll(samples); + PlaylistGroup group = getGroup(groupName, groups); + group.playlists.addAll(playlistHolders); } - private Sample readEntry(JsonReader reader, boolean insidePlaylist) throws IOException { - String sampleName = null; + private PlaylistHolder readEntry(JsonReader reader, boolean insidePlaylist) throws IOException { Uri uri = null; String extension = null; + String title = null; boolean isLive = false; - String drmScheme = null; - String drmLicenseUrl = null; - String[] drmKeyRequestProperties = null; - String[] drmSessionForClearTypes = null; - boolean drmMultiSession = false; - ArrayList playlistSamples = null; - String adTagUri = null; String sphericalStereoMode = null; - List subtitleInfos = new ArrayList<>(); + ArrayList children = null; Uri subtitleUri = null; String subtitleMimeType = null; String subtitleLanguage = null; + MediaItem.Builder mediaItem = new MediaItem.Builder(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); switch (name) { case "name": - sampleName = reader.nextString(); + title = reader.nextString(); break; case "uri": uri = Uri.parse(reader.nextString()); @@ -385,47 +388,46 @@ public class SampleChooserActivity extends AppCompatActivity extension = reader.nextString(); break; case "drm_scheme": - drmScheme = reader.nextString(); + mediaItem.setDrmUuid(Util.getDrmUuid(reader.nextString())); break; case "is_live": isLive = reader.nextBoolean(); break; case "drm_license_url": - drmLicenseUrl = reader.nextString(); + mediaItem.setDrmLicenseUri(reader.nextString()); break; case "drm_key_request_properties": - ArrayList drmKeyRequestPropertiesList = new ArrayList<>(); + Map requestHeaders = new HashMap<>(); reader.beginObject(); while (reader.hasNext()) { - drmKeyRequestPropertiesList.add(reader.nextName()); - drmKeyRequestPropertiesList.add(reader.nextString()); + requestHeaders.put(reader.nextName(), reader.nextString()); } reader.endObject(); - drmKeyRequestProperties = drmKeyRequestPropertiesList.toArray(new String[0]); + mediaItem.setDrmLicenseRequestHeaders(requestHeaders); break; case "drm_session_for_clear_types": - ArrayList drmSessionForClearTypesList = new ArrayList<>(); + HashSet drmSessionForClearTypes = new HashSet<>(); reader.beginArray(); while (reader.hasNext()) { - drmSessionForClearTypesList.add(reader.nextString()); + drmSessionForClearTypes.add(toTrackType(reader.nextString())); } reader.endArray(); - drmSessionForClearTypes = drmSessionForClearTypesList.toArray(new String[0]); + mediaItem.setDrmSessionForClearTypes(new ArrayList<>(drmSessionForClearTypes)); break; case "drm_multi_session": - drmMultiSession = reader.nextBoolean(); + mediaItem.setDrmMultiSession(reader.nextBoolean()); break; case "playlist": Assertions.checkState(!insidePlaylist, "Invalid nesting of playlists"); - playlistSamples = new ArrayList<>(); + children = new ArrayList<>(); reader.beginArray(); while (reader.hasNext()) { - playlistSamples.add((UriSample) readEntry(reader, /* insidePlaylist= */ true)); + children.add(readEntry(reader, /* insidePlaylist= */ true)); } reader.endArray(); break; case "ad_tag_uri": - adTagUri = reader.nextString(); + mediaItem.setAdTagUri(reader.nextString()); break; case "spherical_stereo_mode": Assertions.checkState( @@ -446,67 +448,71 @@ public class SampleChooserActivity extends AppCompatActivity } } reader.endObject(); - DrmInfo drmInfo = - drmScheme == null - ? null - : new DrmInfo( - Util.getDrmUuid(drmScheme), - drmLicenseUrl, - drmKeyRequestProperties, - Sample.toTrackTypeArray(drmSessionForClearTypes), - drmMultiSession); - Sample.SubtitleInfo subtitleInfo = - subtitleUri == null - ? null - : new Sample.SubtitleInfo( + + if (children != null) { + List mediaItems = new ArrayList<>(); + for (int i = 0; i < children.size(); i++) { + mediaItems.addAll(children.get(i).mediaItems); + } + return new PlaylistHolder(title, mediaItems); + } else { + mediaItem + .setSourceUri(uri) + .setMediaMetadata(new MediaMetadata.Builder().setTitle(title).build()) + .setMimeType(IntentUtil.inferAdaptiveStreamMimeType(uri, extension)) + .setTag(new IntentUtil.Tag(isLive, sphericalStereoMode)); + if (subtitleUri != null) { + MediaItem.Subtitle subtitle = + new MediaItem.Subtitle( subtitleUri, - Assertions.checkNotNull( + checkNotNull( subtitleMimeType, "subtitle_mime_type is required if subtitle_uri is set."), subtitleLanguage); - if (playlistSamples != null) { - UriSample[] playlistSamplesArray = playlistSamples.toArray(new UriSample[0]); - return new PlaylistSample(sampleName, playlistSamplesArray); - } else { - return new UriSample( - sampleName, - uri, - extension, - isLive, - drmInfo, - adTagUri != null ? Uri.parse(adTagUri) : null, - sphericalStereoMode, - subtitleInfo); + mediaItem.setSubtitles(Collections.singletonList(subtitle)); + } + return new PlaylistHolder(title, Collections.singletonList(mediaItem.build())); } } - private SampleGroup getGroup(String groupName, List groups) { + private PlaylistGroup getGroup(String groupName, List groups) { for (int i = 0; i < groups.size(); i++) { if (Util.areEqual(groupName, groups.get(i).title)) { return groups.get(i); } } - SampleGroup group = new SampleGroup(groupName); + PlaylistGroup group = new PlaylistGroup(groupName); groups.add(group); return group; } + + private int toTrackType(String trackTypeString) { + switch (Util.toLowerInvariant(trackTypeString)) { + case "audio": + return C.TRACK_TYPE_AUDIO; + case "video": + return C.TRACK_TYPE_VIDEO; + default: + throw new IllegalArgumentException("Invalid track type: " + trackTypeString); + } + } } private final class SampleAdapter extends BaseExpandableListAdapter implements OnClickListener { - private List sampleGroups; + private List playlistGroups; public SampleAdapter() { - sampleGroups = Collections.emptyList(); + playlistGroups = Collections.emptyList(); } - public void setSampleGroups(List sampleGroups) { - this.sampleGroups = sampleGroups; + public void setPlaylistGroups(List playlistGroups) { + this.playlistGroups = playlistGroups; notifyDataSetChanged(); } @Override - public Sample getChild(int groupPosition, int childPosition) { - return getGroup(groupPosition).samples.get(childPosition); + public PlaylistHolder getChild(int groupPosition, int childPosition) { + return getGroup(groupPosition).playlists.get(childPosition); } @Override @@ -534,12 +540,12 @@ public class SampleChooserActivity extends AppCompatActivity @Override public int getChildrenCount(int groupPosition) { - return getGroup(groupPosition).samples.size(); + return getGroup(groupPosition).playlists.size(); } @Override - public SampleGroup getGroup(int groupPosition) { - return sampleGroups.get(groupPosition); + public PlaylistGroup getGroup(int groupPosition) { + return playlistGroups.get(groupPosition); } @Override @@ -562,7 +568,7 @@ public class SampleChooserActivity extends AppCompatActivity @Override public int getGroupCount() { - return sampleGroups.size(); + return playlistGroups.size(); } @Override @@ -577,18 +583,19 @@ public class SampleChooserActivity extends AppCompatActivity @Override public void onClick(View view) { - onSampleDownloadButtonClicked((Sample) view.getTag()); + onSampleDownloadButtonClicked((PlaylistHolder) view.getTag()); } - private void initializeChildView(View view, Sample sample) { - view.setTag(sample); + private void initializeChildView(View view, PlaylistHolder playlistHolder) { + view.setTag(playlistHolder); TextView sampleTitle = view.findViewById(R.id.sample_title); - sampleTitle.setText(sample.name); + sampleTitle.setText(playlistHolder.title); - boolean canDownload = getDownloadUnsupportedStringId(sample) == 0; - boolean isDownloaded = canDownload && downloadTracker.isDownloaded(((UriSample) sample).uri); + boolean canDownload = getDownloadUnsupportedStringId(playlistHolder) == 0; + boolean isDownloaded = + canDownload && downloadTracker.isDownloaded(playlistHolder.mediaItems.get(0)); ImageButton downloadButton = view.findViewById(R.id.download_button); - downloadButton.setTag(sample); + downloadButton.setTag(playlistHolder); downloadButton.setColorFilter( canDownload ? (isDownloaded ? 0xFF42A5F5 : 0xFFBDBDBD) : 0xFF666666); downloadButton.setImageResource( @@ -596,14 +603,26 @@ public class SampleChooserActivity extends AppCompatActivity } } - private static final class SampleGroup { + private static final class PlaylistHolder { public final String title; - public final List samples; + public final List mediaItems; - public SampleGroup(String title) { + private PlaylistHolder(String title, List mediaItems) { + Assertions.checkArgument(!mediaItems.isEmpty()); this.title = title; - this.samples = new ArrayList<>(); + this.mediaItems = Collections.unmodifiableList(new ArrayList<>(mediaItems)); + } + } + + private static final class PlaylistGroup { + + public final String title; + public final List playlists; + + public PlaylistGroup(String title) { + this.title = title; + this.playlists = new ArrayList<>(); } } } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java index 6d8ca9e4b5..a074324ad6 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -1668,6 +1668,29 @@ public final class Util { } } + /** + * Makes a best guess to infer the type from a {@link Uri} and MIME type. + * + * @param uri The {@link Uri}. + * @param mimeType If not null, used to infer the type. + * @return The content type. + */ + public static int inferContentTypeWithMimeType(Uri uri, @Nullable String mimeType) { + if (mimeType == null) { + return Util.inferContentType(uri); + } + switch (mimeType) { + case MimeTypes.APPLICATION_MPD: + return C.TYPE_DASH; + case MimeTypes.APPLICATION_M3U8: + return C.TYPE_HLS; + case MimeTypes.APPLICATION_SS: + return C.TYPE_SS; + default: + return Util.inferContentType(uri); + } + } + /** * Returns the specified millisecond time formatted as a string. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java index fc9ffab139..e6cff597cd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java @@ -248,7 +248,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { Assertions.checkNotNull(mediaItem.playbackProperties); @C.ContentType int type = - inferContentType( + Util.inferContentTypeWithMimeType( mediaItem.playbackProperties.sourceUri, mediaItem.playbackProperties.mimeType); @Nullable MediaSourceFactory mediaSourceFactory = mediaSourceFactories.get(type); Assertions.checkNotNull( @@ -396,20 +396,4 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { factories.put(C.TYPE_OTHER, new ProgressiveMediaSource.Factory(dataSourceFactory)); return factories; } - - private static int inferContentType(Uri sourceUri, @Nullable String mimeType) { - if (mimeType == null) { - return Util.inferContentType(sourceUri); - } - switch (mimeType) { - case MimeTypes.APPLICATION_MPD: - return C.TYPE_DASH; - case MimeTypes.APPLICATION_M3U8: - return C.TYPE_HLS; - case MimeTypes.APPLICATION_SS: - return C.TYPE_SS; - default: - return Util.inferContentType(sourceUri); - } - } }