blob: b4c0e2aa7a6b0c744976aa661a91d319947e2c14 [file] [log] [blame]
/*
* Copyright 2020 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.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import android.content.Intent;
import android.net.Uri;
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;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/** 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";
public static final String ACTION_VIEW_LIST =
"com.google.android.exoplayer.demo.action.VIEW_LIST";
// Activity extras.
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_TOP_BOTTOM = "top_bottom";
public static final String SPHERICAL_STEREO_MODE_LEFT_RIGHT = "left_right";
// 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 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";
public static final String DRM_KEY_REQUEST_PROPERTIES_EXTRA = "drm_key_request_properties";
public static final String DRM_SESSION_FOR_CLEAR_TYPES_EXTRA = "drm_session_for_clear_types";
public static final String DRM_MULTI_SESSION_EXTRA = "drm_multi_session";
public static final String AD_TAG_URI_EXTRA = "ad_tag_uri";
public static final String SUBTITLE_URI_EXTRA = "subtitle_uri";
public static final String SUBTITLE_MIME_TYPE_EXTRA = "subtitle_mime_type";
public static final String SUBTITLE_LANGUAGE_EXTRA = "subtitle_language";
// For backwards compatibility only.
public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid";
public static final String PREFER_EXTENSION_DECODERS_EXTRA = "prefer_extension_decoders";
public static final String TUNNELING_EXTRA = "tunneling";
/** Creates a list of {@link MediaItem media items} from an {@link Intent}. */
public static List<MediaItem> createMediaItemsFromIntent(
Intent intent, DownloadTracker downloadTracker) {
List<MediaItem> mediaItems = new ArrayList<>();
if (ACTION_VIEW_LIST.equals(intent.getAction())) {
int index = 0;
while (intent.hasExtra(URI_EXTRA + "_" + index)) {
Uri uri = Uri.parse(intent.getStringExtra(URI_EXTRA + "_" + index));
mediaItems.add(
createMediaItemFromIntent(
uri,
intent,
/* extrasKeySuffix= */ "_" + index,
downloadTracker.getDownloadRequest(uri)));
index++;
}
} else {
Uri uri = intent.getData();
mediaItems.add(
createMediaItemFromIntent(
uri, intent, /* extrasKeySuffix= */ "", downloadTracker.getDownloadRequest(uri)));
}
return mediaItems;
}
/** Populates the intent with the given list of {@link MediaItem media items}. */
public static void addToIntent(List<MediaItem> 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 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(mimeType)
.setAdTagUri(intent.getStringExtra(AD_TAG_URI_EXTRA + extrasKeySuffix))
.setSubtitles(createSubtitlesFromIntent(intent, extrasKeySuffix));
return populateDrmPropertiesFromIntent(builder, intent, extrasKeySuffix).build();
}
private static List<MediaItem.Subtitle> createSubtitlesFromIntent(
Intent intent, String extrasKeySuffix) {
if (!intent.hasExtra(SUBTITLE_URI_EXTRA + extrasKeySuffix)) {
return Collections.emptyList();
}
return Collections.singletonList(
new MediaItem.Subtitle(
Uri.parse(intent.getStringExtra(SUBTITLE_URI_EXTRA + extrasKeySuffix)),
checkNotNull(intent.getStringExtra(SUBTITLE_MIME_TYPE_EXTRA + extrasKeySuffix)),
intent.getStringExtra(SUBTITLE_LANGUAGE_EXTRA + extrasKeySuffix),
C.SELECTION_FLAG_DEFAULT));
}
private static MediaItem.Builder populateDrmPropertiesFromIntent(
MediaItem.Builder builder, 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 builder;
}
String drmSchemeExtra =
intent.hasExtra(schemeKey)
? intent.getStringExtra(schemeKey)
: intent.getStringExtra(schemeUuidKey);
String[] drmSessionForClearTypesExtra =
intent.getStringArrayExtra(DRM_SESSION_FOR_CLEAR_TYPES_EXTRA + extrasKeySuffix);
Map<String, String> headers = new HashMap<>();
String[] keyRequestPropertiesArray =
intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES_EXTRA + extrasKeySuffix);
if (keyRequestPropertiesArray != null) {
for (int i = 0; i < keyRequestPropertiesArray.length; i += 2) {
headers.put(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]);
}
}
builder
.setDrmUuid(Util.getDrmUuid(Util.castNonNull(drmSchemeExtra)))
.setDrmLicenseUri(intent.getStringExtra(DRM_LICENSE_URL_EXTRA + extrasKeySuffix))
.setDrmSessionForClearTypes(toTrackTypeList(drmSessionForClearTypesExtra))
.setDrmMultiSession(
intent.getBooleanExtra(DRM_MULTI_SESSION_EXTRA + extrasKeySuffix, false))
.setDrmLicenseRequestHeaders(headers);
return builder;
}
private static List<Integer> toTrackTypeList(@Nullable String[] trackTypeStringsArray) {
if (trackTypeStringsArray == null) {
return Collections.emptyList();
}
HashSet<Integer> 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 new ArrayList<>(trackTypes);
}
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<String, String> entry : drmConfiguration.requestHeaders.entrySet()) {
drmKeyRequestProperties[index++] = entry.getKey();
drmKeyRequestProperties[index++] = entry.getValue();
}
intent.putExtra(DRM_KEY_REQUEST_PROPERTIES_EXTRA + extrasKeySuffix, drmKeyRequestProperties);
ArrayList<String> 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]));
}
}