Commit 9304cb3f by olly Committed by Oliver Woodman

Plumb key-rotation and drm-playlist support

With this change, MediaCodecRenderer acquires a session
from its DrmSessionManager whenever the DrmInitData in
the format changes. The DrmSessionManager is permitted
to return the same session if it can be re-used.

This plumbing adds support for:

1. Key-rotation, in the case that a key-rotation-aware
   DrmSessionManager is used. Such an implementation will
   return the same DrmSession for every aquisition, but
   will be making use of multiple MediaDrm instances within
   that session to enable the rotation.

2. Playlists with mixed clear and protected content. One
   final piece to this will be to have each MediaPeriod
   provide its own license URL. We could also allow each
   MediaPeriod to specify the DRM UUID, but switching from
   PlayReady to Widevine in a playlist seems like quite a
   hypothetical thing.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=127816046
parent ba9a610a
...@@ -99,8 +99,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -99,8 +99,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
SimpleExoPlayer.Id3MetadataListener, MappingTrackSelector.EventListener { SimpleExoPlayer.Id3MetadataListener, MappingTrackSelector.EventListener {
public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid"; public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid";
public static final String DRM_CONTENT_ID_EXTRA = "drm_content_id"; public static final String DRM_LICENSE_URL = "drm_license_url";
public static final String DRM_PROVIDER_EXTRA = "drm_provider";
public static final String PREFER_EXTENSION_DECODERS = "prefer_extension_decoders"; public static final String PREFER_EXTENSION_DECODERS = "prefer_extension_decoders";
public static final String ACTION_VIEW = "com.google.android.exoplayer.demo.action.VIEW"; public static final String ACTION_VIEW = "com.google.android.exoplayer.demo.action.VIEW";
...@@ -267,13 +266,13 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -267,13 +266,13 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
Intent intent = getIntent(); Intent intent = getIntent();
if (player == null) { if (player == null) {
boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false); boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false);
UUID drmSchemeUuid = (UUID) intent.getSerializableExtra(DRM_SCHEME_UUID_EXTRA); UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA)
? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null;
DrmSessionManager drmSessionManager = null; DrmSessionManager drmSessionManager = null;
if (drmSchemeUuid != null) { if (drmSchemeUuid != null) {
String drmContentId = intent.getStringExtra(DRM_CONTENT_ID_EXTRA); String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL);
String drmProvider = intent.getStringExtra(DRM_PROVIDER_EXTRA);
try { try {
drmSessionManager = buildDrmSessionManager(drmSchemeUuid, drmContentId, drmProvider); drmSessionManager = buildDrmSessionManager(drmSchemeUuid, drmLicenseUrl);
} catch (UnsupportedDrmException e) { } catch (UnsupportedDrmException e) {
onUnsupportedDrmError(e); onUnsupportedDrmError(e);
return; return;
...@@ -367,17 +366,17 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -367,17 +366,17 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
} }
} }
private DrmSessionManager buildDrmSessionManager(UUID uuid, String id, String provider) private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl)
throws UnsupportedDrmException { throws UnsupportedDrmException {
if (Util.SDK_INT < 18) { if (Util.SDK_INT < 18) {
return null; return null;
} }
if (C.PLAYREADY_UUID.equals(uuid)) { if (C.PLAYREADY_UUID.equals(uuid)) {
return StreamingDrmSessionManager.newPlayReadyInstance( return StreamingDrmSessionManager.newPlayReadyInstance(
TestMediaDrmCallback.newPlayReadyInstance(), null, mainHandler, eventLogger); TestMediaDrmCallback.newPlayReadyInstance(licenseUrl), null, mainHandler, eventLogger);
} else if (C.WIDEVINE_UUID.equals(uuid)) { } else if (C.WIDEVINE_UUID.equals(uuid)) {
return StreamingDrmSessionManager.newWidevineInstance( return StreamingDrmSessionManager.newWidevineInstance(
TestMediaDrmCallback.newWidevineInstance(id, provider), null, mainHandler, eventLogger); TestMediaDrmCallback.newWidevineInstance(licenseUrl), null, mainHandler, eventLogger);
} else { } else {
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME); throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME);
} }
......
...@@ -162,8 +162,7 @@ public class SampleChooserActivity extends Activity { ...@@ -162,8 +162,7 @@ public class SampleChooserActivity extends Activity {
String uri = null; String uri = null;
String extension = null; String extension = null;
UUID drmUuid = null; UUID drmUuid = null;
String drmContentId = null; String drmLicenseUrl = null;
String drmProvider = null;
boolean preferExtensionDecoders = false; boolean preferExtensionDecoders = false;
ArrayList<UriSample> playlistSamples = null; ArrayList<UriSample> playlistSamples = null;
...@@ -180,17 +179,22 @@ public class SampleChooserActivity extends Activity { ...@@ -180,17 +179,22 @@ public class SampleChooserActivity extends Activity {
case "extension": case "extension":
extension = reader.nextString(); extension = reader.nextString();
break; break;
case "drm": case "drm_scheme":
String[] drmComponents = reader.nextString().split(":", -1); Assertions.checkState(!insidePlaylist, "Invalid attribute on nested item: drm_scheme");
drmUuid = getDrmUuid(drmComponents[0]); drmUuid = getDrmUuid(reader.nextString());
drmContentId = drmComponents[1]; break;
drmProvider = drmComponents[2]; case "drm_license_url":
Assertions.checkState(!insidePlaylist,
"Invalid attribute on nested item: drm_license_url");
drmLicenseUrl = reader.nextString();
break; break;
case "prefer_extension_decoders": case "prefer_extension_decoders":
Assertions.checkState(!insidePlaylist,
"Invalid attribute on nested item: prefer_extension_decoders");
preferExtensionDecoders = reader.nextBoolean(); preferExtensionDecoders = reader.nextBoolean();
break; break;
case "playlist": case "playlist":
Assertions.checkState(!insidePlaylist, "Nested playlists are invalid"); Assertions.checkState(!insidePlaylist, "Invalid nesting of playlists");
playlistSamples = new ArrayList<>(); playlistSamples = new ArrayList<>();
reader.beginArray(); reader.beginArray();
while (reader.hasNext()) { while (reader.hasNext()) {
...@@ -207,11 +211,11 @@ public class SampleChooserActivity extends Activity { ...@@ -207,11 +211,11 @@ public class SampleChooserActivity extends Activity {
if (playlistSamples != null) { if (playlistSamples != null) {
UriSample[] playlistSamplesArray = playlistSamples.toArray( UriSample[] playlistSamplesArray = playlistSamples.toArray(
new UriSample[playlistSamples.size()]); new UriSample[playlistSamples.size()]);
return new PlaylistSample(sampleName, drmUuid, drmContentId, drmProvider, return new PlaylistSample(sampleName, drmUuid, drmLicenseUrl, preferExtensionDecoders,
preferExtensionDecoders, playlistSamplesArray); playlistSamplesArray);
} else { } else {
return new UriSample(sampleName, drmUuid, drmContentId, drmProvider, return new UriSample(sampleName, drmUuid, drmLicenseUrl, preferExtensionDecoders, uri,
preferExtensionDecoders, uri, extension); extension);
} }
} }
...@@ -233,7 +237,11 @@ public class SampleChooserActivity extends Activity { ...@@ -233,7 +237,11 @@ public class SampleChooserActivity extends Activity {
case "playready": case "playready":
return C.PLAYREADY_UUID; return C.PLAYREADY_UUID;
default: default:
throw new ParserException("Unsupported drm type: " + typeString); try {
return UUID.fromString(typeString);
} catch (RuntimeException e) {
throw new ParserException("Unsupported drm type: " + typeString);
}
} }
} }
...@@ -331,24 +339,26 @@ public class SampleChooserActivity extends Activity { ...@@ -331,24 +339,26 @@ public class SampleChooserActivity extends Activity {
public final String name; public final String name;
public final boolean preferExtensionDecoders; public final boolean preferExtensionDecoders;
// TODO: DRM properties should be specified on UriSample only. This requires changes to
// PlayerActivity and beyond to be able to handle playlists containing multiple DRM protected
// items that have different DRM properties.
public final UUID drmSchemeUuid; public final UUID drmSchemeUuid;
public final String drmContentId; public final String drmLicenseUrl;
public final String drmProvider;
public Sample(String name, UUID drmSchemeUuid, String drmContentId, String drmProvider, public Sample(String name, UUID drmSchemeUuid, String drmLicenseUrl,
boolean preferExtensionDecoders) { boolean preferExtensionDecoders) {
this.name = name; this.name = name;
this.drmSchemeUuid = drmSchemeUuid; this.drmSchemeUuid = drmSchemeUuid;
this.drmContentId = drmContentId; this.drmLicenseUrl = drmLicenseUrl;
this.drmProvider = drmProvider;
this.preferExtensionDecoders = preferExtensionDecoders; this.preferExtensionDecoders = preferExtensionDecoders;
} }
public abstract Intent buildIntent(Context context); public Intent buildIntent(Context context) {
Intent intent = new Intent(context, PlayerActivity.class);
intent.putExtra(PlayerActivity.PREFER_EXTENSION_DECODERS, preferExtensionDecoders);
if (drmSchemeUuid != null) {
intent.putExtra(PlayerActivity.DRM_SCHEME_UUID_EXTRA, drmSchemeUuid.toString());
intent.putExtra(PlayerActivity.DRM_LICENSE_URL, drmLicenseUrl);
}
return intent;
}
} }
...@@ -357,21 +367,16 @@ public class SampleChooserActivity extends Activity { ...@@ -357,21 +367,16 @@ public class SampleChooserActivity extends Activity {
public final String uri; public final String uri;
public final String extension; public final String extension;
public UriSample(String name, UUID drmSchemeUuid, String drmContentId, String drmProvider, public UriSample(String name, UUID drmSchemeUuid, String drmLicenseUrl,
boolean preferExtensionDecoders, String uri, String extension) { boolean preferExtensionDecoders, String uri, String extension) {
super(name, drmSchemeUuid, drmContentId, drmProvider, preferExtensionDecoders); super(name, drmSchemeUuid, drmLicenseUrl, preferExtensionDecoders);
this.uri = uri; this.uri = uri;
this.extension = extension; this.extension = extension;
} }
@Override @Override
public Intent buildIntent(Context context) { public Intent buildIntent(Context context) {
return new Intent(context, PlayerActivity.class) return super.buildIntent(context)
.setAction(PlayerActivity.ACTION_VIEW)
.putExtra(PlayerActivity.DRM_SCHEME_UUID_EXTRA, drmSchemeUuid)
.putExtra(PlayerActivity.DRM_CONTENT_ID_EXTRA, drmContentId)
.putExtra(PlayerActivity.DRM_PROVIDER_EXTRA, drmProvider)
.putExtra(PlayerActivity.PREFER_EXTENSION_DECODERS, preferExtensionDecoders)
.setData(Uri.parse(uri)) .setData(Uri.parse(uri))
.putExtra(PlayerActivity.EXTENSION_EXTRA, extension) .putExtra(PlayerActivity.EXTENSION_EXTRA, extension)
.setAction(PlayerActivity.ACTION_VIEW); .setAction(PlayerActivity.ACTION_VIEW);
...@@ -383,9 +388,9 @@ public class SampleChooserActivity extends Activity { ...@@ -383,9 +388,9 @@ public class SampleChooserActivity extends Activity {
public final UriSample[] children; public final UriSample[] children;
public PlaylistSample(String name, UUID drmSchemeUuid, String drmContentId, String drmProvider, public PlaylistSample(String name, UUID drmSchemeUuid, String drmLicenseUrl,
boolean preferExtensionDecoders, UriSample... children) { boolean preferExtensionDecoders, UriSample... children) {
super(name, drmSchemeUuid, drmContentId, drmProvider, preferExtensionDecoders); super(name, drmSchemeUuid, drmLicenseUrl, preferExtensionDecoders);
this.children = children; this.children = children;
} }
...@@ -397,12 +402,7 @@ public class SampleChooserActivity extends Activity { ...@@ -397,12 +402,7 @@ public class SampleChooserActivity extends Activity {
uris[i] = children[i].uri; uris[i] = children[i].uri;
extensions[i] = children[i].extension; extensions[i] = children[i].extension;
} }
return new Intent(context, PlayerActivity.class) return super.buildIntent(context)
.setAction(PlayerActivity.ACTION_VIEW)
.putExtra(PlayerActivity.DRM_SCHEME_UUID_EXTRA, drmSchemeUuid)
.putExtra(PlayerActivity.DRM_CONTENT_ID_EXTRA, drmContentId)
.putExtra(PlayerActivity.DRM_PROVIDER_EXTRA, drmProvider)
.putExtra(PlayerActivity.PREFER_EXTENSION_DECODERS, preferExtensionDecoders)
.putExtra(PlayerActivity.URI_LIST_EXTRA, uris) .putExtra(PlayerActivity.URI_LIST_EXTRA, uris)
.putExtra(PlayerActivity.EXTENSION_LIST_EXTRA, extensions) .putExtra(PlayerActivity.EXTENSION_LIST_EXTRA, extensions)
.setAction(PlayerActivity.ACTION_VIEW_LIST); .setAction(PlayerActivity.ACTION_VIEW_LIST);
......
...@@ -38,9 +38,6 @@ import java.util.UUID; ...@@ -38,9 +38,6 @@ import java.util.UUID;
@TargetApi(18) @TargetApi(18)
/* package */ final class TestMediaDrmCallback implements MediaDrmCallback { /* package */ final class TestMediaDrmCallback implements MediaDrmCallback {
private static final String WIDEVINE_BASE_URL = "https://proxy.uat.widevine.com/proxy";
private static final String PLAYREADY_BASE_URL =
"http://playready.directtaps.net/pr/svc/rightsmanager.asmx";
private static final Map<String, String> PLAYREADY_KEY_REQUEST_PROPERTIES; private static final Map<String, String> PLAYREADY_KEY_REQUEST_PROPERTIES;
static { static {
HashMap<String, String> keyRequestProperties = new HashMap<>(); HashMap<String, String> keyRequestProperties = new HashMap<>();
...@@ -53,13 +50,12 @@ import java.util.UUID; ...@@ -53,13 +50,12 @@ import java.util.UUID;
private final String defaultUrl; private final String defaultUrl;
private final Map<String, String> keyRequestProperties; private final Map<String, String> keyRequestProperties;
public static TestMediaDrmCallback newWidevineInstance(String contentId, String provider) { public static TestMediaDrmCallback newWidevineInstance(String defaultUrl) {
String defaultUrl = WIDEVINE_BASE_URL + "?video_id=" + contentId + "&provider=" + provider;
return new TestMediaDrmCallback(defaultUrl, null); return new TestMediaDrmCallback(defaultUrl, null);
} }
public static TestMediaDrmCallback newPlayReadyInstance() { public static TestMediaDrmCallback newPlayReadyInstance(String defaultUrl) {
return new TestMediaDrmCallback(PLAYREADY_BASE_URL, PLAYREADY_KEY_REQUEST_PROPERTIES); return new TestMediaDrmCallback(defaultUrl, PLAYREADY_KEY_REQUEST_PROPERTIES);
} }
private TestMediaDrmCallback(String defaultUrl, Map<String, String> keyRequestProperties) { private TestMediaDrmCallback(String defaultUrl, Map<String, String> keyRequestProperties) {
......
...@@ -177,6 +177,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -177,6 +177,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private Format format; private Format format;
private MediaCodec codec; private MediaCodec codec;
private DrmSession drmSession; private DrmSession drmSession;
private DrmSession pendingDrmSession;
private boolean codecIsAdaptive; private boolean codecIsAdaptive;
private boolean codecNeedsDiscardToSpsWorkaround; private boolean codecNeedsDiscardToSpsWorkaround;
private boolean codecNeedsFlushWorkaround; private boolean codecNeedsFlushWorkaround;
...@@ -287,17 +288,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -287,17 +288,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return; return;
} }
drmSession = pendingDrmSession;
String mimeType = format.sampleMimeType; String mimeType = format.sampleMimeType;
MediaCrypto mediaCrypto = null; MediaCrypto mediaCrypto = null;
boolean drmSessionRequiresSecureDecoder = false; boolean drmSessionRequiresSecureDecoder = false;
if (format.drmInitData != null) { if (drmSession != null) {
if (drmSessionManager == null) {
throw ExoPlaybackException.createForRenderer(
new IllegalStateException("Media requires a DrmSessionManager"), getIndex());
}
if (drmSession == null) {
drmSession = drmSessionManager.acquireSession(Looper.myLooper(), format.drmInitData);
}
int drmSessionState = drmSession.getState(); int drmSessionState = drmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) { if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
...@@ -400,9 +395,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -400,9 +395,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
try { try {
releaseCodec(); releaseCodec();
} finally { } finally {
if (drmSession != null) { try {
drmSessionManager.releaseSession(drmSession); if (drmSession != null) {
drmSession = null; drmSessionManager.releaseSession(drmSession);
}
} finally {
try {
if (pendingDrmSession != null && pendingDrmSession != drmSession) {
drmSessionManager.releaseSession(pendingDrmSession);
}
} finally {
drmSession = null;
pendingDrmSession = null;
}
} }
} }
} }
...@@ -439,6 +444,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -439,6 +444,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codec.release(); codec.release();
} finally { } finally {
codec = null; codec = null;
if (drmSession != null && pendingDrmSession != drmSession) {
try {
drmSessionManager.releaseSession(drmSession);
} finally {
drmSession = null;
}
}
} }
} }
} }
...@@ -700,7 +712,26 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -700,7 +712,26 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
Format oldFormat = format; Format oldFormat = format;
format = newFormat; format = newFormat;
if (codec != null && canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) {
boolean drmInitDataChanged = !Util.areEqual(format.drmInitData, oldFormat == null ? null
: oldFormat.drmInitData);
if (drmInitDataChanged) {
if (format.drmInitData != null) {
if (drmSessionManager == null) {
throw ExoPlaybackException.createForRenderer(
new IllegalStateException("Media requires a DrmSessionManager"), getIndex());
}
pendingDrmSession = drmSessionManager.acquireSession(Looper.myLooper(), format.drmInitData);
if (pendingDrmSession == drmSession) {
drmSessionManager.releaseSession(pendingDrmSession);
}
} else {
pendingDrmSession = null;
}
}
if (pendingDrmSession == drmSession && codec != null
&& canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) {
codecReconfigured = true; codecReconfigured = true;
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment