mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Support different drm schemes in playlists in the demo app
This CL changes PlayerActivity's VIEW_LIST action intent contract: Each media item configuration is provided by indexing the entries. For example, the URI of the first item is passed as "uri_0", the second one is "uri_1", etc. Optionally, the extra parameters, like the extensions, are passed as "extension_1", where the intent extras with matching indices, refer to the same media sample. The VIEW action's contract remains unchanged. PiperOrigin-RevId: 260518118
This commit is contained in:
parent
3051e5e9ad
commit
06f9481505
4 changed files with 360 additions and 252 deletions
|
|
@ -38,6 +38,7 @@ import com.google.android.exoplayer2.PlaybackPreparer;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.RenderersFactory;
|
import com.google.android.exoplayer2.RenderersFactory;
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.demo.Sample.UriSample;
|
||||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||||
|
|
@ -78,41 +79,48 @@ import java.lang.reflect.Constructor;
|
||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
import java.net.CookiePolicy;
|
import java.net.CookiePolicy;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/** An activity that plays media using {@link SimpleExoPlayer}. */
|
/** An activity that plays media using {@link SimpleExoPlayer}. */
|
||||||
public class PlayerActivity extends AppCompatActivity
|
public class PlayerActivity extends AppCompatActivity
|
||||||
implements OnClickListener, PlaybackPreparer, PlayerControlView.VisibilityListener {
|
implements OnClickListener, PlaybackPreparer, PlayerControlView.VisibilityListener {
|
||||||
|
|
||||||
public static final String DRM_SCHEME_EXTRA = "drm_scheme";
|
// Activity extras.
|
||||||
public static final String DRM_LICENSE_URL_EXTRA = "drm_license_url";
|
|
||||||
public static final String DRM_KEY_REQUEST_PROPERTIES_EXTRA = "drm_key_request_properties";
|
|
||||||
public static final String DRM_MULTI_SESSION_EXTRA = "drm_multi_session";
|
|
||||||
public static final String PREFER_EXTENSION_DECODERS_EXTRA = "prefer_extension_decoders";
|
|
||||||
|
|
||||||
public static final String ACTION_VIEW = "com.google.android.exoplayer.demo.action.VIEW";
|
|
||||||
public static final String EXTENSION_EXTRA = "extension";
|
|
||||||
|
|
||||||
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 AD_TAG_URI_EXTRA = "ad_tag_uri";
|
|
||||||
|
|
||||||
public static final String ABR_ALGORITHM_EXTRA = "abr_algorithm";
|
|
||||||
public static final String ABR_ALGORITHM_DEFAULT = "default";
|
|
||||||
public static final String ABR_ALGORITHM_RANDOM = "random";
|
|
||||||
|
|
||||||
public static final String SPHERICAL_STEREO_MODE_EXTRA = "spherical_stereo_mode";
|
public static final String SPHERICAL_STEREO_MODE_EXTRA = "spherical_stereo_mode";
|
||||||
public static final String SPHERICAL_STEREO_MODE_MONO = "mono";
|
public static final String SPHERICAL_STEREO_MODE_MONO = "mono";
|
||||||
public static final String SPHERICAL_STEREO_MODE_TOP_BOTTOM = "top_bottom";
|
public static final String SPHERICAL_STEREO_MODE_TOP_BOTTOM = "top_bottom";
|
||||||
public static final String SPHERICAL_STEREO_MODE_LEFT_RIGHT = "left_right";
|
public static final String SPHERICAL_STEREO_MODE_LEFT_RIGHT = "left_right";
|
||||||
|
|
||||||
|
// Actions.
|
||||||
|
|
||||||
|
public static final String ACTION_VIEW = "com.google.android.exoplayer.demo.action.VIEW";
|
||||||
|
public static final String ACTION_VIEW_LIST =
|
||||||
|
"com.google.android.exoplayer.demo.action.VIEW_LIST";
|
||||||
|
|
||||||
|
// Player configuration extras.
|
||||||
|
|
||||||
|
public static final String ABR_ALGORITHM_EXTRA = "abr_algorithm";
|
||||||
|
public static final String ABR_ALGORITHM_DEFAULT = "default";
|
||||||
|
public static final String ABR_ALGORITHM_RANDOM = "random";
|
||||||
|
|
||||||
|
// Media item configuration extras.
|
||||||
|
|
||||||
|
public static final String URI_EXTRA = "uri";
|
||||||
|
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";
|
||||||
|
public static final String DRM_KEY_REQUEST_PROPERTIES_EXTRA = "drm_key_request_properties";
|
||||||
|
public static final String DRM_MULTI_SESSION_EXTRA = "drm_multi_session";
|
||||||
|
public static final String PREFER_EXTENSION_DECODERS_EXTRA = "prefer_extension_decoders";
|
||||||
|
public static final String AD_TAG_URI_EXTRA = "ad_tag_uri";
|
||||||
// For backwards compatibility only.
|
// For backwards compatibility only.
|
||||||
private static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid";
|
public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid";
|
||||||
|
|
||||||
// Saved instance state keys.
|
// Saved instance state keys.
|
||||||
|
|
||||||
private static final String KEY_TRACK_SELECTOR_PARAMETERS = "track_selector_parameters";
|
private static final String KEY_TRACK_SELECTOR_PARAMETERS = "track_selector_parameters";
|
||||||
private static final String KEY_WINDOW = "window";
|
private static final String KEY_WINDOW = "window";
|
||||||
private static final String KEY_POSITION = "position";
|
private static final String KEY_POSITION = "position";
|
||||||
|
|
@ -124,6 +132,8 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final ArrayList<FrameworkMediaDrm> mediaDrms;
|
||||||
|
|
||||||
private PlayerView playerView;
|
private PlayerView playerView;
|
||||||
private LinearLayout debugRootView;
|
private LinearLayout debugRootView;
|
||||||
private Button selectTracksButton;
|
private Button selectTracksButton;
|
||||||
|
|
@ -132,7 +142,6 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
|
|
||||||
private DataSource.Factory dataSourceFactory;
|
private DataSource.Factory dataSourceFactory;
|
||||||
private SimpleExoPlayer player;
|
private SimpleExoPlayer player;
|
||||||
private FrameworkMediaDrm mediaDrm;
|
|
||||||
private MediaSource mediaSource;
|
private MediaSource mediaSource;
|
||||||
private DefaultTrackSelector trackSelector;
|
private DefaultTrackSelector trackSelector;
|
||||||
private DefaultTrackSelector.Parameters trackSelectorParameters;
|
private DefaultTrackSelector.Parameters trackSelectorParameters;
|
||||||
|
|
@ -148,6 +157,10 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
private AdsLoader adsLoader;
|
private AdsLoader adsLoader;
|
||||||
private Uri loadedAdTagUri;
|
private Uri loadedAdTagUri;
|
||||||
|
|
||||||
|
public PlayerActivity() {
|
||||||
|
mediaDrms = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
// Activity lifecycle
|
// Activity lifecycle
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -329,69 +342,11 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
private void initializePlayer() {
|
private void initializePlayer() {
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
String action = intent.getAction();
|
|
||||||
Uri[] uris;
|
|
||||||
String[] extensions;
|
|
||||||
if (ACTION_VIEW.equals(action)) {
|
|
||||||
uris = new Uri[] {intent.getData()};
|
|
||||||
extensions = new String[] {intent.getStringExtra(EXTENSION_EXTRA)};
|
|
||||||
} else if (ACTION_VIEW_LIST.equals(action)) {
|
|
||||||
String[] uriStrings = intent.getStringArrayExtra(URI_LIST_EXTRA);
|
|
||||||
uris = new Uri[uriStrings.length];
|
|
||||||
for (int i = 0; i < uriStrings.length; i++) {
|
|
||||||
uris[i] = Uri.parse(uriStrings[i]);
|
|
||||||
}
|
|
||||||
extensions = intent.getStringArrayExtra(EXTENSION_LIST_EXTRA);
|
|
||||||
if (extensions == null) {
|
|
||||||
extensions = new String[uriStrings.length];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showToast(getString(R.string.unexpected_intent_action, action));
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!Util.checkCleartextTrafficPermitted(uris)) {
|
|
||||||
showToast(R.string.error_cleartext_not_permitted);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Util.maybeRequestReadExternalStoragePermission(/* activity= */ this, uris)) {
|
|
||||||
// The player will be reinitialized if the permission is granted.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
|
releaseMediaDrms();
|
||||||
if (intent.hasExtra(DRM_SCHEME_EXTRA) || intent.hasExtra(DRM_SCHEME_UUID_EXTRA)) {
|
mediaSource = createTopLevelMediaSource(intent);
|
||||||
String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL_EXTRA);
|
if (mediaSource == null) {
|
||||||
String[] keyRequestPropertiesArray =
|
return;
|
||||||
intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES_EXTRA);
|
|
||||||
boolean multiSession = intent.getBooleanExtra(DRM_MULTI_SESSION_EXTRA, false);
|
|
||||||
int errorStringId = R.string.error_drm_unknown;
|
|
||||||
if (Util.SDK_INT < 18) {
|
|
||||||
errorStringId = R.string.error_drm_not_supported;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
String drmSchemeExtra = intent.hasExtra(DRM_SCHEME_EXTRA) ? DRM_SCHEME_EXTRA
|
|
||||||
: DRM_SCHEME_UUID_EXTRA;
|
|
||||||
UUID drmSchemeUuid = Util.getDrmUuid(intent.getStringExtra(drmSchemeExtra));
|
|
||||||
if (drmSchemeUuid == null) {
|
|
||||||
errorStringId = R.string.error_drm_unsupported_scheme;
|
|
||||||
} else {
|
|
||||||
drmSessionManager =
|
|
||||||
buildDrmSessionManagerV18(
|
|
||||||
drmSchemeUuid, drmLicenseUrl, keyRequestPropertiesArray, multiSession);
|
|
||||||
}
|
|
||||||
} catch (UnsupportedDrmException e) {
|
|
||||||
errorStringId = e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
|
|
||||||
? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (drmSessionManager == null) {
|
|
||||||
showToast(errorStringId);
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackSelection.Factory trackSelectionFactory;
|
TrackSelection.Factory trackSelectionFactory;
|
||||||
|
|
@ -424,28 +379,8 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
playerView.setPlaybackPreparer(this);
|
playerView.setPlaybackPreparer(this);
|
||||||
debugViewHelper = new DebugTextViewHelper(player, debugTextView);
|
debugViewHelper = new DebugTextViewHelper(player, debugTextView);
|
||||||
debugViewHelper.start();
|
debugViewHelper.start();
|
||||||
|
if (adsLoader != null) {
|
||||||
MediaSource[] mediaSources = new MediaSource[uris.length];
|
adsLoader.setPlayer(player);
|
||||||
for (int i = 0; i < uris.length; i++) {
|
|
||||||
mediaSources[i] = buildMediaSource(uris[i], extensions[i], drmSessionManager);
|
|
||||||
}
|
|
||||||
mediaSource =
|
|
||||||
mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources);
|
|
||||||
String adTagUriString = intent.getStringExtra(AD_TAG_URI_EXTRA);
|
|
||||||
if (adTagUriString != null) {
|
|
||||||
Uri adTagUri = Uri.parse(adTagUriString);
|
|
||||||
if (!adTagUri.equals(loadedAdTagUri)) {
|
|
||||||
releaseAdsLoader();
|
|
||||||
loadedAdTagUri = adTagUri;
|
|
||||||
}
|
|
||||||
MediaSource adsMediaSource = createAdsMediaSource(mediaSource, Uri.parse(adTagUriString));
|
|
||||||
if (adsMediaSource != null) {
|
|
||||||
mediaSource = adsMediaSource;
|
|
||||||
} else {
|
|
||||||
showToast(R.string.ima_not_loaded);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
releaseAdsLoader();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean haveStartPosition = startWindow != C.INDEX_UNSET;
|
boolean haveStartPosition = startWindow != C.INDEX_UNSET;
|
||||||
|
|
@ -456,23 +391,113 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
updateButtonVisibility();
|
updateButtonVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaSource buildMediaSource(Uri uri) {
|
@Nullable
|
||||||
return buildMediaSource(
|
private MediaSource createTopLevelMediaSource(Intent intent) {
|
||||||
uri,
|
String action = intent.getAction();
|
||||||
/* overrideExtension= */ null,
|
boolean actionIsListView = ACTION_VIEW_LIST.equals(action);
|
||||||
/* drmSessionManager= */ DrmSessionManager.getDummyDrmSessionManager());
|
if (!actionIsListView && !ACTION_VIEW.equals(action)) {
|
||||||
|
showToast(getString(R.string.unexpected_intent_action, action));
|
||||||
|
finish();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sample intentAsSample = Sample.createFromIntent(intent);
|
||||||
|
UriSample[] samples =
|
||||||
|
intentAsSample instanceof Sample.PlaylistSample
|
||||||
|
? ((Sample.PlaylistSample) intentAsSample).children
|
||||||
|
: new UriSample[] {(UriSample) intentAsSample};
|
||||||
|
|
||||||
|
boolean seenAdsTagUri = false;
|
||||||
|
for (UriSample sample : samples) {
|
||||||
|
seenAdsTagUri |= sample.adTagUri != null;
|
||||||
|
if (!Util.checkCleartextTrafficPermitted(sample.uri)) {
|
||||||
|
showToast(R.string.error_cleartext_not_permitted);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (Util.maybeRequestReadExternalStoragePermission(/* activity= */ this, sample.uri)) {
|
||||||
|
// The player will be reinitialized if the permission is granted.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaSource[] mediaSources = new MediaSource[samples.length];
|
||||||
|
for (int i = 0; i < samples.length; i++) {
|
||||||
|
mediaSources[i] = createLeafMediaSource(samples[i]);
|
||||||
|
}
|
||||||
|
MediaSource mediaSource =
|
||||||
|
mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources);
|
||||||
|
|
||||||
|
if (seenAdsTagUri) {
|
||||||
|
Uri adTagUri = samples[0].adTagUri;
|
||||||
|
if (actionIsListView) {
|
||||||
|
showToast(R.string.unsupported_ads_in_concatenation);
|
||||||
|
} else {
|
||||||
|
if (!adTagUri.equals(loadedAdTagUri)) {
|
||||||
|
releaseAdsLoader();
|
||||||
|
loadedAdTagUri = adTagUri;
|
||||||
|
}
|
||||||
|
MediaSource adsMediaSource = createAdsMediaSource(mediaSource, adTagUri);
|
||||||
|
if (adsMediaSource != null) {
|
||||||
|
mediaSource = adsMediaSource;
|
||||||
|
} else {
|
||||||
|
showToast(R.string.ima_not_loaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
releaseAdsLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mediaSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaSource buildMediaSource(
|
private MediaSource createLeafMediaSource(UriSample parameters) {
|
||||||
Uri uri,
|
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
|
||||||
@Nullable String overrideExtension,
|
Sample.DrmInfo drmInfo = parameters.drmInfo;
|
||||||
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
|
if (drmInfo != null) {
|
||||||
|
int errorStringId = R.string.error_drm_unknown;
|
||||||
|
if (Util.SDK_INT < 18) {
|
||||||
|
errorStringId = R.string.error_drm_not_supported;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
if (drmInfo.drmScheme == null) {
|
||||||
|
errorStringId = R.string.error_drm_unsupported_scheme;
|
||||||
|
} else {
|
||||||
|
drmSessionManager =
|
||||||
|
buildDrmSessionManagerV18(
|
||||||
|
drmInfo.drmScheme,
|
||||||
|
drmInfo.drmLicenseUrl,
|
||||||
|
drmInfo.drmKeyRequestProperties,
|
||||||
|
drmInfo.drmMultiSession);
|
||||||
|
}
|
||||||
|
} catch (UnsupportedDrmException e) {
|
||||||
|
errorStringId =
|
||||||
|
e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
|
||||||
|
? R.string.error_drm_unsupported_scheme
|
||||||
|
: R.string.error_drm_unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (drmSessionManager == null) {
|
||||||
|
showToast(errorStringId);
|
||||||
|
finish();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
||||||
|
}
|
||||||
|
|
||||||
DownloadRequest downloadRequest =
|
DownloadRequest downloadRequest =
|
||||||
((DemoApplication) getApplication()).getDownloadTracker().getDownloadRequest(uri);
|
((DemoApplication) getApplication())
|
||||||
|
.getDownloadTracker()
|
||||||
|
.getDownloadRequest(parameters.uri);
|
||||||
if (downloadRequest != null) {
|
if (downloadRequest != null) {
|
||||||
return DownloadHelper.createMediaSource(downloadRequest, dataSourceFactory);
|
return DownloadHelper.createMediaSource(downloadRequest, dataSourceFactory);
|
||||||
}
|
}
|
||||||
@ContentType int type = Util.inferContentType(uri, overrideExtension);
|
return createLeafMediaSource(parameters.uri, parameters.extension, drmSessionManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaSource createLeafMediaSource(
|
||||||
|
Uri uri, String extension, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
|
||||||
|
@ContentType int type = Util.inferContentType(uri, extension);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case C.TYPE_DASH:
|
case C.TYPE_DASH:
|
||||||
return new DashMediaSource.Factory(dataSourceFactory)
|
return new DashMediaSource.Factory(dataSourceFactory)
|
||||||
|
|
@ -508,8 +533,9 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
keyRequestPropertiesArray[i + 1]);
|
keyRequestPropertiesArray[i + 1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
releaseMediaDrm();
|
|
||||||
mediaDrm = FrameworkMediaDrm.newInstance(uuid);
|
FrameworkMediaDrm mediaDrm = FrameworkMediaDrm.newInstance(uuid);
|
||||||
|
mediaDrms.add(mediaDrm);
|
||||||
return new DefaultDrmSessionManager<>(uuid, mediaDrm, drmCallback, null, multiSession);
|
return new DefaultDrmSessionManager<>(uuid, mediaDrm, drmCallback, null, multiSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -527,14 +553,14 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
if (adsLoader != null) {
|
if (adsLoader != null) {
|
||||||
adsLoader.setPlayer(null);
|
adsLoader.setPlayer(null);
|
||||||
}
|
}
|
||||||
releaseMediaDrm();
|
releaseMediaDrms();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releaseMediaDrm() {
|
private void releaseMediaDrms() {
|
||||||
if (mediaDrm != null) {
|
for (FrameworkMediaDrm mediaDrm : mediaDrms) {
|
||||||
mediaDrm.release();
|
mediaDrm.release();
|
||||||
mediaDrm = null;
|
|
||||||
}
|
}
|
||||||
|
mediaDrms.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releaseAdsLoader() {
|
private void releaseAdsLoader() {
|
||||||
|
|
@ -588,12 +614,12 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
// LINT.ThenChange(../../../../../../../../proguard-rules.txt)
|
// LINT.ThenChange(../../../../../../../../proguard-rules.txt)
|
||||||
adsLoader = loaderConstructor.newInstance(this, adTagUri);
|
adsLoader = loaderConstructor.newInstance(this, adTagUri);
|
||||||
}
|
}
|
||||||
adsLoader.setPlayer(player);
|
|
||||||
MediaSourceFactory adMediaSourceFactory =
|
MediaSourceFactory adMediaSourceFactory =
|
||||||
new MediaSourceFactory() {
|
new MediaSourceFactory() {
|
||||||
@Override
|
@Override
|
||||||
public MediaSource createMediaSource(Uri uri) {
|
public MediaSource createMediaSource(Uri uri) {
|
||||||
return PlayerActivity.this.buildMediaSource(uri);
|
return PlayerActivity.this.createLeafMediaSource(
|
||||||
|
uri, /* extension=*/ null, DrmSessionManager.getDummyDrmSessionManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -718,5 +744,4 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
return Pair.create(0, errorString);
|
return Pair.create(0, errorString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
/*
|
||||||
|
* 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 static com.google.android.exoplayer2.demo.PlayerActivity.ACTION_VIEW_LIST;
|
||||||
|
import static com.google.android.exoplayer2.demo.PlayerActivity.AD_TAG_URI_EXTRA;
|
||||||
|
import static com.google.android.exoplayer2.demo.PlayerActivity.DRM_KEY_REQUEST_PROPERTIES_EXTRA;
|
||||||
|
import static com.google.android.exoplayer2.demo.PlayerActivity.DRM_LICENSE_URL_EXTRA;
|
||||||
|
import static com.google.android.exoplayer2.demo.PlayerActivity.DRM_MULTI_SESSION_EXTRA;
|
||||||
|
import static com.google.android.exoplayer2.demo.PlayerActivity.DRM_SCHEME_EXTRA;
|
||||||
|
import static com.google.android.exoplayer2.demo.PlayerActivity.DRM_SCHEME_UUID_EXTRA;
|
||||||
|
import static com.google.android.exoplayer2.demo.PlayerActivity.EXTENSION_EXTRA;
|
||||||
|
import static com.google.android.exoplayer2.demo.PlayerActivity.URI_EXTRA;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/* package */ abstract class Sample {
|
||||||
|
|
||||||
|
public static final class UriSample extends Sample {
|
||||||
|
|
||||||
|
public static UriSample createFromIntent(Uri uri, Intent intent, String extrasKeySuffix) {
|
||||||
|
String extension = intent.getStringExtra(EXTENSION_EXTRA + extrasKeySuffix);
|
||||||
|
String adsTagUriString = intent.getStringExtra(AD_TAG_URI_EXTRA + extrasKeySuffix);
|
||||||
|
Uri adTagUri = adsTagUriString != null ? Uri.parse(adsTagUriString) : null;
|
||||||
|
return new UriSample(
|
||||||
|
/* name= */ null,
|
||||||
|
DrmInfo.createFromIntent(intent, extrasKeySuffix),
|
||||||
|
uri,
|
||||||
|
extension,
|
||||||
|
adTagUri,
|
||||||
|
/* sphericalStereoMode= */ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Uri uri;
|
||||||
|
public final String extension;
|
||||||
|
public final DrmInfo drmInfo;
|
||||||
|
public final Uri adTagUri;
|
||||||
|
public final String sphericalStereoMode;
|
||||||
|
|
||||||
|
public UriSample(
|
||||||
|
String name,
|
||||||
|
DrmInfo drmInfo,
|
||||||
|
Uri uri,
|
||||||
|
String extension,
|
||||||
|
Uri adTagUri,
|
||||||
|
String sphericalStereoMode) {
|
||||||
|
super(name);
|
||||||
|
this.uri = uri;
|
||||||
|
this.extension = extension;
|
||||||
|
this.drmInfo = drmInfo;
|
||||||
|
this.adTagUri = adTagUri;
|
||||||
|
this.sphericalStereoMode = sphericalStereoMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addToIntent(Intent intent) {
|
||||||
|
intent.setAction(PlayerActivity.ACTION_VIEW).setData(uri);
|
||||||
|
intent.putExtra(PlayerActivity.SPHERICAL_STEREO_MODE_EXTRA, sphericalStereoMode);
|
||||||
|
addPlayerConfigToIntent(intent, /* extrasKeySuffix= */ "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addToPlaylistIntent(Intent intent, String extrasKeySuffix) {
|
||||||
|
intent.putExtra(PlayerActivity.URI_EXTRA + extrasKeySuffix, uri.toString());
|
||||||
|
addPlayerConfigToIntent(intent, extrasKeySuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addPlayerConfigToIntent(Intent intent, String extrasKeySuffix) {
|
||||||
|
intent
|
||||||
|
.putExtra(EXTENSION_EXTRA + extrasKeySuffix, extension)
|
||||||
|
.putExtra(
|
||||||
|
AD_TAG_URI_EXTRA + extrasKeySuffix, adTagUri != null ? adTagUri.toString() : null);
|
||||||
|
if (drmInfo != null) {
|
||||||
|
drmInfo.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(PlayerActivity.ACTION_VIEW_LIST);
|
||||||
|
for (int i = 0; i < children.length; i++) {
|
||||||
|
children[i].addToPlaylistIntent(intent, /* extrasKeySuffix= */ "_" + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class DrmInfo {
|
||||||
|
|
||||||
|
public static DrmInfo createFromIntent(Intent intent, String extrasKeySuffix) {
|
||||||
|
String schemeKey = DRM_SCHEME_EXTRA + extrasKeySuffix;
|
||||||
|
String schemeUuidKey = DRM_SCHEME_UUID_EXTRA + extrasKeySuffix;
|
||||||
|
if (!intent.hasExtra(schemeKey) && !intent.hasExtra(schemeUuidKey)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String drmSchemeExtra =
|
||||||
|
intent.hasExtra(schemeKey)
|
||||||
|
? intent.getStringExtra(schemeKey)
|
||||||
|
: intent.getStringExtra(schemeUuidKey);
|
||||||
|
UUID drmScheme = Util.getDrmUuid(drmSchemeExtra);
|
||||||
|
String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL_EXTRA + extrasKeySuffix);
|
||||||
|
String[] keyRequestPropertiesArray =
|
||||||
|
intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES_EXTRA + extrasKeySuffix);
|
||||||
|
boolean drmMultiSession =
|
||||||
|
intent.getBooleanExtra(DRM_MULTI_SESSION_EXTRA + extrasKeySuffix, false);
|
||||||
|
return new DrmInfo(drmScheme, drmLicenseUrl, keyRequestPropertiesArray, drmMultiSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final UUID drmScheme;
|
||||||
|
public final String drmLicenseUrl;
|
||||||
|
public final String[] drmKeyRequestProperties;
|
||||||
|
public final boolean drmMultiSession;
|
||||||
|
|
||||||
|
public DrmInfo(
|
||||||
|
UUID drmScheme,
|
||||||
|
String drmLicenseUrl,
|
||||||
|
String[] drmKeyRequestProperties,
|
||||||
|
boolean drmMultiSession) {
|
||||||
|
this.drmScheme = drmScheme;
|
||||||
|
this.drmLicenseUrl = drmLicenseUrl;
|
||||||
|
this.drmKeyRequestProperties = drmKeyRequestProperties;
|
||||||
|
this.drmMultiSession = drmMultiSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addToIntent(Intent intent, String extrasKeySuffix) {
|
||||||
|
Assertions.checkNotNull(intent);
|
||||||
|
intent.putExtra(DRM_SCHEME_EXTRA + extrasKeySuffix, drmScheme.toString());
|
||||||
|
intent.putExtra(DRM_LICENSE_URL_EXTRA + extrasKeySuffix, drmLicenseUrl);
|
||||||
|
intent.putExtra(DRM_KEY_REQUEST_PROPERTIES_EXTRA + extrasKeySuffix, drmKeyRequestProperties);
|
||||||
|
intent.putExtra(DRM_MULTI_SESSION_EXTRA + extrasKeySuffix, drmMultiSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Sample createFromIntent(Intent intent) {
|
||||||
|
if (ACTION_VIEW_LIST.equals(intent.getAction())) {
|
||||||
|
ArrayList<String> intentUris = new ArrayList<>();
|
||||||
|
int index = 0;
|
||||||
|
while (intent.hasExtra(URI_EXTRA + "_" + index)) {
|
||||||
|
intentUris.add(intent.getStringExtra(URI_EXTRA + "_" + index));
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
UriSample[] children = new UriSample[intentUris.size()];
|
||||||
|
for (int i = 0; i < children.length; i++) {
|
||||||
|
Uri uri = Uri.parse(intentUris.get(i));
|
||||||
|
children[i] = UriSample.createFromIntent(uri, intent, /* extrasKeySuffix= */ "_" + i);
|
||||||
|
}
|
||||||
|
return new PlaylistSample(/* name= */ null, children);
|
||||||
|
} else {
|
||||||
|
return UriSample.createFromIntent(intent.getData(), intent, /* extrasKeySuffix= */ "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public final String name;
|
||||||
|
|
||||||
|
public Sample(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void addToIntent(Intent intent);
|
||||||
|
}
|
||||||
|
|
@ -38,6 +38,9 @@ import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
import com.google.android.exoplayer2.RenderersFactory;
|
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.offline.DownloadService;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DataSourceInputStream;
|
import com.google.android.exoplayer2.upstream.DataSourceInputStream;
|
||||||
|
|
@ -161,13 +164,17 @@ public class SampleChooserActivity extends AppCompatActivity
|
||||||
public boolean onChildClick(
|
public boolean onChildClick(
|
||||||
ExpandableListView parent, View view, int groupPosition, int childPosition, long id) {
|
ExpandableListView parent, View view, int groupPosition, int childPosition, long id) {
|
||||||
Sample sample = (Sample) view.getTag();
|
Sample sample = (Sample) view.getTag();
|
||||||
startActivity(
|
Intent intent = new Intent(this, PlayerActivity.class);
|
||||||
sample.buildIntent(
|
intent.putExtra(
|
||||||
/* context= */ this,
|
PlayerActivity.PREFER_EXTENSION_DECODERS_EXTRA,
|
||||||
isNonNullAndChecked(preferExtensionDecodersMenuItem),
|
isNonNullAndChecked(preferExtensionDecodersMenuItem));
|
||||||
isNonNullAndChecked(randomAbrMenuItem)
|
String abrAlgorithm =
|
||||||
? PlayerActivity.ABR_ALGORITHM_RANDOM
|
isNonNullAndChecked(randomAbrMenuItem)
|
||||||
: PlayerActivity.ABR_ALGORITHM_DEFAULT));
|
? PlayerActivity.ABR_ALGORITHM_RANDOM
|
||||||
|
: PlayerActivity.ABR_ALGORITHM_DEFAULT;
|
||||||
|
intent.putExtra(PlayerActivity.ABR_ALGORITHM_EXTRA, abrAlgorithm);
|
||||||
|
sample.addToIntent(intent);
|
||||||
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -309,17 +316,12 @@ public class SampleChooserActivity extends AppCompatActivity
|
||||||
extension = reader.nextString();
|
extension = reader.nextString();
|
||||||
break;
|
break;
|
||||||
case "drm_scheme":
|
case "drm_scheme":
|
||||||
Assertions.checkState(!insidePlaylist, "Invalid attribute on nested item: drm_scheme");
|
|
||||||
drmScheme = reader.nextString();
|
drmScheme = reader.nextString();
|
||||||
break;
|
break;
|
||||||
case "drm_license_url":
|
case "drm_license_url":
|
||||||
Assertions.checkState(!insidePlaylist,
|
|
||||||
"Invalid attribute on nested item: drm_license_url");
|
|
||||||
drmLicenseUrl = reader.nextString();
|
drmLicenseUrl = reader.nextString();
|
||||||
break;
|
break;
|
||||||
case "drm_key_request_properties":
|
case "drm_key_request_properties":
|
||||||
Assertions.checkState(!insidePlaylist,
|
|
||||||
"Invalid attribute on nested item: drm_key_request_properties");
|
|
||||||
ArrayList<String> drmKeyRequestPropertiesList = new ArrayList<>();
|
ArrayList<String> drmKeyRequestPropertiesList = new ArrayList<>();
|
||||||
reader.beginObject();
|
reader.beginObject();
|
||||||
while (reader.hasNext()) {
|
while (reader.hasNext()) {
|
||||||
|
|
@ -357,17 +359,21 @@ public class SampleChooserActivity extends AppCompatActivity
|
||||||
DrmInfo drmInfo =
|
DrmInfo drmInfo =
|
||||||
drmScheme == null
|
drmScheme == null
|
||||||
? null
|
? null
|
||||||
: new DrmInfo(drmScheme, drmLicenseUrl, drmKeyRequestProperties, drmMultiSession);
|
: new DrmInfo(
|
||||||
|
Util.getDrmUuid(drmScheme),
|
||||||
|
drmLicenseUrl,
|
||||||
|
drmKeyRequestProperties,
|
||||||
|
drmMultiSession);
|
||||||
if (playlistSamples != null) {
|
if (playlistSamples != null) {
|
||||||
UriSample[] playlistSamplesArray = playlistSamples.toArray(new UriSample[0]);
|
UriSample[] playlistSamplesArray = playlistSamples.toArray(new UriSample[0]);
|
||||||
return new PlaylistSample(sampleName, drmInfo, playlistSamplesArray);
|
return new PlaylistSample(sampleName, playlistSamplesArray);
|
||||||
} else {
|
} else {
|
||||||
return new UriSample(
|
return new UriSample(
|
||||||
sampleName,
|
sampleName,
|
||||||
drmInfo,
|
drmInfo,
|
||||||
uri,
|
uri,
|
||||||
extension,
|
extension,
|
||||||
adTagUri,
|
adTagUri != null ? Uri.parse(adTagUri) : null,
|
||||||
sphericalStereoMode);
|
sphericalStereoMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -497,116 +503,4 @@ public class SampleChooserActivity extends AppCompatActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class DrmInfo {
|
|
||||||
public final String drmScheme;
|
|
||||||
public final String drmLicenseUrl;
|
|
||||||
public final String[] drmKeyRequestProperties;
|
|
||||||
public final boolean drmMultiSession;
|
|
||||||
|
|
||||||
public DrmInfo(
|
|
||||||
String drmScheme,
|
|
||||||
String drmLicenseUrl,
|
|
||||||
String[] drmKeyRequestProperties,
|
|
||||||
boolean drmMultiSession) {
|
|
||||||
this.drmScheme = drmScheme;
|
|
||||||
this.drmLicenseUrl = drmLicenseUrl;
|
|
||||||
this.drmKeyRequestProperties = drmKeyRequestProperties;
|
|
||||||
this.drmMultiSession = drmMultiSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateIntent(Intent intent) {
|
|
||||||
Assertions.checkNotNull(intent);
|
|
||||||
intent.putExtra(PlayerActivity.DRM_SCHEME_EXTRA, drmScheme);
|
|
||||||
intent.putExtra(PlayerActivity.DRM_LICENSE_URL_EXTRA, drmLicenseUrl);
|
|
||||||
intent.putExtra(PlayerActivity.DRM_KEY_REQUEST_PROPERTIES_EXTRA, drmKeyRequestProperties);
|
|
||||||
intent.putExtra(PlayerActivity.DRM_MULTI_SESSION_EXTRA, drmMultiSession);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract static class Sample {
|
|
||||||
public final String name;
|
|
||||||
public final DrmInfo drmInfo;
|
|
||||||
|
|
||||||
public Sample(String name, DrmInfo drmInfo) {
|
|
||||||
this.name = name;
|
|
||||||
this.drmInfo = drmInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Intent buildIntent(
|
|
||||||
Context context, boolean preferExtensionDecoders, String abrAlgorithm) {
|
|
||||||
Intent intent = new Intent(context, PlayerActivity.class);
|
|
||||||
intent.putExtra(PlayerActivity.PREFER_EXTENSION_DECODERS_EXTRA, preferExtensionDecoders);
|
|
||||||
intent.putExtra(PlayerActivity.ABR_ALGORITHM_EXTRA, abrAlgorithm);
|
|
||||||
if (drmInfo != null) {
|
|
||||||
drmInfo.updateIntent(intent);
|
|
||||||
}
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class UriSample extends Sample {
|
|
||||||
|
|
||||||
public final Uri uri;
|
|
||||||
public final String extension;
|
|
||||||
public final String adTagUri;
|
|
||||||
public final String sphericalStereoMode;
|
|
||||||
|
|
||||||
public UriSample(
|
|
||||||
String name,
|
|
||||||
DrmInfo drmInfo,
|
|
||||||
Uri uri,
|
|
||||||
String extension,
|
|
||||||
String adTagUri,
|
|
||||||
String sphericalStereoMode) {
|
|
||||||
super(name, drmInfo);
|
|
||||||
this.uri = uri;
|
|
||||||
this.extension = extension;
|
|
||||||
this.adTagUri = adTagUri;
|
|
||||||
this.sphericalStereoMode = sphericalStereoMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Intent buildIntent(
|
|
||||||
Context context, boolean preferExtensionDecoders, String abrAlgorithm) {
|
|
||||||
return super.buildIntent(context, preferExtensionDecoders, abrAlgorithm)
|
|
||||||
.setData(uri)
|
|
||||||
.putExtra(PlayerActivity.EXTENSION_EXTRA, extension)
|
|
||||||
.putExtra(PlayerActivity.AD_TAG_URI_EXTRA, adTagUri)
|
|
||||||
.putExtra(PlayerActivity.SPHERICAL_STEREO_MODE_EXTRA, sphericalStereoMode)
|
|
||||||
.setAction(PlayerActivity.ACTION_VIEW);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class PlaylistSample extends Sample {
|
|
||||||
|
|
||||||
public final UriSample[] children;
|
|
||||||
|
|
||||||
public PlaylistSample(
|
|
||||||
String name,
|
|
||||||
DrmInfo drmInfo,
|
|
||||||
UriSample... children) {
|
|
||||||
super(name, drmInfo);
|
|
||||||
this.children = children;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Intent buildIntent(
|
|
||||||
Context context, boolean preferExtensionDecoders, String abrAlgorithm) {
|
|
||||||
String[] uris = new String[children.length];
|
|
||||||
String[] extensions = new String[children.length];
|
|
||||||
for (int i = 0; i < children.length; i++) {
|
|
||||||
uris[i] = children[i].uri.toString();
|
|
||||||
extensions[i] = children[i].extension;
|
|
||||||
}
|
|
||||||
return super.buildIntent(context, preferExtensionDecoders, abrAlgorithm)
|
|
||||||
.putExtra(PlayerActivity.URI_LIST_EXTRA, uris)
|
|
||||||
.putExtra(PlayerActivity.EXTENSION_LIST_EXTRA, extensions)
|
|
||||||
.setAction(PlayerActivity.ACTION_VIEW_LIST);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@
|
||||||
|
|
||||||
<string name="ima_not_loaded">Playing sample without ads, as the IMA extension was not loaded</string>
|
<string name="ima_not_loaded">Playing sample without ads, as the IMA extension was not loaded</string>
|
||||||
|
|
||||||
|
<string name="unsupported_ads_in_concatenation">Playing sample without ads, as ads are not supported in concatenations</string>
|
||||||
|
|
||||||
<string name="download_start_error">Failed to start download</string>
|
<string name="download_start_error">Failed to start download</string>
|
||||||
|
|
||||||
<string name="download_playlist_unsupported">This demo app does not support downloading playlists</string>
|
<string name="download_playlist_unsupported">This demo app does not support downloading playlists</string>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue