mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Add basic DRM support to CastPlayer's demo app
PiperOrigin-RevId: 249624829
This commit is contained in:
parent
8d329fb41f
commit
3314391932
5 changed files with 108 additions and 34 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
### dev-v2 (not yet released) ###
|
### dev-v2 (not yet released) ###
|
||||||
|
|
||||||
|
* Add basic DRM support to the Cast demo app.
|
||||||
* Add `ResolvingDataSource` for just-in-time resolution of `DataSpec`s
|
* Add `ResolvingDataSource` for just-in-time resolution of `DataSpec`s
|
||||||
([#5779](https://github.com/google/ExoPlayer/issues/5779)).
|
([#5779](https://github.com/google/ExoPlayer/issues/5779)).
|
||||||
* Assume that encrypted content requires secure decoders in renderer support
|
* Assume that encrypted content requires secure decoders in renderer support
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,14 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
import com.google.android.exoplayer2.ui.PlayerControlView;
|
import com.google.android.exoplayer2.ui.PlayerControlView;
|
||||||
import com.google.android.exoplayer2.ui.PlayerView;
|
import com.google.android.exoplayer2.ui.PlayerView;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.gms.cast.MediaInfo;
|
import com.google.android.gms.cast.MediaInfo;
|
||||||
import com.google.android.gms.cast.MediaMetadata;
|
import com.google.android.gms.cast.MediaMetadata;
|
||||||
import com.google.android.gms.cast.MediaQueueItem;
|
import com.google.android.gms.cast.MediaQueueItem;
|
||||||
import com.google.android.gms.cast.framework.CastContext;
|
import com.google.android.gms.cast.framework.CastContext;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
/** Manages players and an internal media queue for the ExoPlayer/Cast demo app. */
|
/** Manages players and an internal media queue for the ExoPlayer/Cast demo app. */
|
||||||
/* package */ class DefaultReceiverPlayerManager
|
/* package */ class DefaultReceiverPlayerManager
|
||||||
|
|
@ -394,12 +397,47 @@ import java.util.ArrayList;
|
||||||
private static MediaQueueItem buildMediaQueueItem(MediaItem item) {
|
private static MediaQueueItem buildMediaQueueItem(MediaItem item) {
|
||||||
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
|
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
|
||||||
movieMetadata.putString(MediaMetadata.KEY_TITLE, item.title);
|
movieMetadata.putString(MediaMetadata.KEY_TITLE, item.title);
|
||||||
MediaInfo mediaInfo =
|
MediaInfo.Builder mediaInfoBuilder =
|
||||||
new MediaInfo.Builder(item.media.uri.toString())
|
new MediaInfo.Builder(item.media.uri.toString())
|
||||||
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
||||||
.setContentType(item.mimeType)
|
.setContentType(item.mimeType)
|
||||||
.setMetadata(movieMetadata)
|
.setMetadata(movieMetadata);
|
||||||
.build();
|
if (!item.drmSchemes.isEmpty()) {
|
||||||
return new MediaQueueItem.Builder(mediaInfo).build();
|
MediaItem.DrmScheme scheme = item.drmSchemes.get(0);
|
||||||
|
try {
|
||||||
|
// This configuration is only intended for testing and should *not* be used in production
|
||||||
|
// environments. See comment in the Cast Demo app's options provider.
|
||||||
|
JSONObject drmConfiguration = getDrmConfigurationJson(scheme);
|
||||||
|
if (drmConfiguration != null) {
|
||||||
|
mediaInfoBuilder.setCustomData(drmConfiguration);
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new MediaQueueItem.Builder(mediaInfoBuilder.build()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static JSONObject getDrmConfigurationJson(MediaItem.DrmScheme scheme)
|
||||||
|
throws JSONException {
|
||||||
|
String drmScheme;
|
||||||
|
if (C.WIDEVINE_UUID.equals(scheme.uuid)) {
|
||||||
|
drmScheme = "widevine";
|
||||||
|
} else if (C.PLAYREADY_UUID.equals(scheme.uuid)) {
|
||||||
|
drmScheme = "playready";
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
MediaItem.UriBundle licenseServer = Assertions.checkNotNull(scheme.licenseServer);
|
||||||
|
JSONObject exoplayerConfig =
|
||||||
|
new JSONObject().put("withCredentials", false).put("protectionSystem", drmScheme);
|
||||||
|
if (!licenseServer.uri.equals(Uri.EMPTY)) {
|
||||||
|
exoplayerConfig.put("licenseUrl", licenseServer.uri.toString());
|
||||||
|
}
|
||||||
|
if (!licenseServer.requestHeaders.isEmpty()) {
|
||||||
|
exoplayerConfig.put("headers", new JSONObject(licenseServer.requestHeaders));
|
||||||
|
}
|
||||||
|
return new JSONObject().put("exoPlayerConfig", exoplayerConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.castdemo;
|
package com.google.android.exoplayer2.castdemo;
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/** Utility methods and constants for the Cast demo application. */
|
/** Utility methods and constants for the Cast demo application. */
|
||||||
|
|
@ -30,44 +30,25 @@ import java.util.UUID;
|
||||||
/** Represents a media sample. */
|
/** Represents a media sample. */
|
||||||
public static final class Sample {
|
public static final class Sample {
|
||||||
|
|
||||||
/** The uri of the media content. */
|
/** The URI of the media content. */
|
||||||
public final String uri;
|
public final String uri;
|
||||||
/** The name of the sample. */
|
/** The name of the sample. */
|
||||||
public final String name;
|
public final String name;
|
||||||
/** The mime type of the sample media content. */
|
/** The mime type of the sample media content. */
|
||||||
public final String mimeType;
|
public final String mimeType;
|
||||||
/**
|
/** Data to configure DRM license acquisition. May be null if content is not DRM-protected. */
|
||||||
* The {@link UUID} of the DRM scheme that protects the content, or null if the content is not
|
@Nullable public final DrmConfiguration drmConfiguration;
|
||||||
* DRM-protected.
|
|
||||||
*/
|
|
||||||
@Nullable public final UUID drmSchemeUuid;
|
|
||||||
/**
|
|
||||||
* The url from which players should obtain DRM licenses, or null if the content is not
|
|
||||||
* DRM-protected.
|
|
||||||
*/
|
|
||||||
@Nullable public final Uri licenseServerUri;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param uri See {@link #uri}.
|
|
||||||
* @param name See {@link #name}.
|
|
||||||
* @param mimeType See {@link #mimeType}.
|
|
||||||
*/
|
|
||||||
public Sample(String uri, String name, String mimeType) {
|
public Sample(String uri, String name, String mimeType) {
|
||||||
this(uri, name, mimeType, /* drmSchemeUuid= */ null, /* licenseServerUriString= */ null);
|
this(uri, name, mimeType, /* drmConfiguration= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Sample(
|
public Sample(
|
||||||
String uri,
|
String uri, String name, String mimeType, @Nullable DrmConfiguration drmConfiguration) {
|
||||||
String name,
|
|
||||||
String mimeType,
|
|
||||||
@Nullable UUID drmSchemeUuid,
|
|
||||||
@Nullable String licenseServerUriString) {
|
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.mimeType = mimeType;
|
this.mimeType = mimeType;
|
||||||
this.drmSchemeUuid = drmSchemeUuid;
|
this.drmConfiguration = drmConfiguration;
|
||||||
this.licenseServerUri =
|
|
||||||
licenseServerUriString != null ? Uri.parse(licenseServerUriString) : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -76,6 +57,29 @@ import java.util.UUID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Holds information required to play DRM-protected content. */
|
||||||
|
public static final class DrmConfiguration {
|
||||||
|
|
||||||
|
/** The {@link UUID} of the DRM scheme that protects the content. */
|
||||||
|
public final UUID drmSchemeUuid;
|
||||||
|
/**
|
||||||
|
* The URI from which players should obtain DRM licenses. May be null if the license server URI
|
||||||
|
* is provided as part of the media.
|
||||||
|
*/
|
||||||
|
@Nullable public final String licenseServerUri;
|
||||||
|
/** HTTP request headers to include the in DRM license requests. */
|
||||||
|
public final Map<String, String> httpRequestHeaders;
|
||||||
|
|
||||||
|
public DrmConfiguration(
|
||||||
|
UUID drmSchemeUuid,
|
||||||
|
@Nullable String licenseServerUri,
|
||||||
|
Map<String, String> httpRequestHeaders) {
|
||||||
|
this.drmSchemeUuid = drmSchemeUuid;
|
||||||
|
this.licenseServerUri = licenseServerUri;
|
||||||
|
this.httpRequestHeaders = httpRequestHeaders;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static final String MIME_TYPE_DASH = MimeTypes.APPLICATION_MPD;
|
public static final String MIME_TYPE_DASH = MimeTypes.APPLICATION_MPD;
|
||||||
public static final String MIME_TYPE_HLS = MimeTypes.APPLICATION_M3U8;
|
public static final String MIME_TYPE_HLS = MimeTypes.APPLICATION_M3U8;
|
||||||
public static final String MIME_TYPE_SS = MimeTypes.APPLICATION_SS;
|
public static final String MIME_TYPE_SS = MimeTypes.APPLICATION_SS;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
package com.google.android.exoplayer2.castdemo;
|
package com.google.android.exoplayer2.castdemo;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.core.graphics.ColorUtils;
|
import androidx.core.graphics.ColorUtils;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
@ -36,6 +37,7 @@ import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider;
|
||||||
import com.google.android.exoplayer2.ext.cast.MediaItem;
|
import com.google.android.exoplayer2.ext.cast.MediaItem;
|
||||||
import com.google.android.exoplayer2.ui.PlayerControlView;
|
import com.google.android.exoplayer2.ui.PlayerControlView;
|
||||||
import com.google.android.exoplayer2.ui.PlayerView;
|
import com.google.android.exoplayer2.ui.PlayerView;
|
||||||
|
|
@ -121,6 +123,7 @@ public class MainActivity extends AppCompatActivity
|
||||||
String applicationId = castContext.getCastOptions().getReceiverApplicationId();
|
String applicationId = castContext.getCastOptions().getReceiverApplicationId();
|
||||||
switch (applicationId) {
|
switch (applicationId) {
|
||||||
case CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID:
|
case CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID:
|
||||||
|
case DefaultCastOptionsProvider.APP_ID_DEFAULT_RECEIVER_WITH_DRM:
|
||||||
playerManager =
|
playerManager =
|
||||||
new DefaultReceiverPlayerManager(
|
new DefaultReceiverPlayerManager(
|
||||||
/* listener= */ this,
|
/* listener= */ this,
|
||||||
|
|
@ -202,11 +205,17 @@ public class MainActivity extends AppCompatActivity
|
||||||
.setMedia(sample.uri)
|
.setMedia(sample.uri)
|
||||||
.setTitle(sample.name)
|
.setTitle(sample.name)
|
||||||
.setMimeType(sample.mimeType);
|
.setMimeType(sample.mimeType);
|
||||||
if (sample.drmSchemeUuid != null) {
|
DemoUtil.DrmConfiguration drmConfiguration = sample.drmConfiguration;
|
||||||
|
if (drmConfiguration != null) {
|
||||||
mediaItemBuilder.setDrmSchemes(
|
mediaItemBuilder.setDrmSchemes(
|
||||||
Collections.singletonList(
|
Collections.singletonList(
|
||||||
new MediaItem.DrmScheme(
|
new MediaItem.DrmScheme(
|
||||||
sample.drmSchemeUuid, new MediaItem.UriBundle(sample.licenseServerUri))));
|
drmConfiguration.drmSchemeUuid,
|
||||||
|
new MediaItem.UriBundle(
|
||||||
|
drmConfiguration.licenseServerUri != null
|
||||||
|
? Uri.parse(drmConfiguration.licenseServerUri)
|
||||||
|
: Uri.EMPTY,
|
||||||
|
drmConfiguration.httpRequestHeaders))));
|
||||||
}
|
}
|
||||||
playerManager.addItem(mediaItemBuilder.build());
|
playerManager.addItem(mediaItemBuilder.build());
|
||||||
mediaQueueListAdapter.notifyItemInserted(playerManager.getMediaQueueSize() - 1);
|
mediaQueueListAdapter.notifyItemInserted(playerManager.getMediaQueueSize() - 1);
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,33 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public final class DefaultCastOptionsProvider implements OptionsProvider {
|
public final class DefaultCastOptionsProvider implements OptionsProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App id of the Default Media Receiver app. Apps that do not require DRM support may use this
|
||||||
|
* receiver receiver app ID.
|
||||||
|
*
|
||||||
|
* <p>See https://developers.google.com/cast/docs/caf_receiver/#default_media_receiver.
|
||||||
|
*/
|
||||||
|
public static final String APP_ID_DEFAULT_RECEIVER =
|
||||||
|
CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App id for receiver app with rudimentary support for DRM.
|
||||||
|
*
|
||||||
|
* <p>This app id is only suitable for ExoPlayer's Cast Demo app, and it is not intended for
|
||||||
|
* production use. In order to use DRM, custom receiver apps should be used. For environments that
|
||||||
|
* do not require DRM, the default receiver app should be used (see {@link
|
||||||
|
* #APP_ID_DEFAULT_RECEIVER}).
|
||||||
|
*/
|
||||||
|
// TODO: Add a documentation resource link for DRM support in the receiver app [Internal ref:
|
||||||
|
// b/128603245].
|
||||||
|
public static final String APP_ID_DEFAULT_RECEIVER_WITH_DRM = "A12D4273";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CastOptions getCastOptions(Context context) {
|
public CastOptions getCastOptions(Context context) {
|
||||||
return new CastOptions.Builder()
|
return new CastOptions.Builder()
|
||||||
.setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
|
.setReceiverApplicationId(APP_ID_DEFAULT_RECEIVER_WITH_DRM)
|
||||||
.setStopReceiverApplicationWhenEndingSession(true).build();
|
.setStopReceiverApplicationWhenEndingSession(true)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue