MediaPlayer2: callback based DRM APIs

Bug: 112549021
Test: MediaPlayer2DrmTest
Change-Id: If837087ef38fe0b71ad67e01aefdc04bb0339c8e
This commit is contained in:
Robert Shih
2018-12-12 02:01:53 -08:00
parent 4a09436a63
commit cf96703641
2 changed files with 254 additions and 69 deletions

View File

@@ -25408,6 +25408,7 @@ package android.media {
method public java.lang.Object setAuxEffectSendLevel(float);
method public java.lang.Object setDataSource(android.media.DataSourceDesc);
method public java.lang.Object setDisplay(android.view.SurfaceHolder);
method public void setDrmEventCallback(java.util.concurrent.Executor, android.media.MediaPlayer2.DrmEventCallback);
method public java.lang.Object setNextDataSource(android.media.DataSourceDesc);
method public java.lang.Object setNextDataSources(java.util.List<android.media.DataSourceDesc>);
method public java.lang.Object setPlaybackParams(android.media.PlaybackParams);
@@ -25486,6 +25487,33 @@ package android.media {
field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
}
public static class MediaPlayer2.DrmEventCallback {
ctor public MediaPlayer2.DrmEventCallback();
method public void onDrmConfig(android.media.MediaPlayer2, android.media.DataSourceDesc, android.media.MediaDrm);
method public android.media.MediaPlayer2.DrmPreparationInfo onDrmInfo(android.media.MediaPlayer2, android.media.DataSourceDesc, android.media.MediaPlayer2.DrmInfo);
method public byte[] onDrmKeyRequest(android.media.MediaPlayer2, android.media.DataSourceDesc, android.media.MediaDrm.KeyRequest);
method public void onDrmPrepared(android.media.MediaPlayer2, android.media.DataSourceDesc, int, byte[]);
}
public static final class MediaPlayer2.DrmInfo {
method public java.util.Map<java.util.UUID, byte[]> getPssh();
method public java.util.List<java.util.UUID> getSupportedSchemes();
}
public static final class MediaPlayer2.DrmPreparationInfo {
}
public static final class MediaPlayer2.DrmPreparationInfo.Builder {
ctor public MediaPlayer2.DrmPreparationInfo.Builder();
method public android.media.MediaPlayer2.DrmPreparationInfo build();
method public android.media.MediaPlayer2.DrmPreparationInfo.Builder setInitData(byte[]);
method public android.media.MediaPlayer2.DrmPreparationInfo.Builder setKeySetId(byte[]);
method public android.media.MediaPlayer2.DrmPreparationInfo.Builder setKeyType(int);
method public android.media.MediaPlayer2.DrmPreparationInfo.Builder setMimeType(java.lang.String);
method public android.media.MediaPlayer2.DrmPreparationInfo.Builder setOptionalParameters(java.util.Map<java.lang.String, java.lang.String>);
method public android.media.MediaPlayer2.DrmPreparationInfo.Builder setUuid(java.util.UUID);
}
public static class MediaPlayer2.EventCallback {
ctor public MediaPlayer2.EventCallback();
method public void onCallCompleted(android.media.MediaPlayer2, android.media.DataSourceDesc, int, int);
@@ -25513,6 +25541,10 @@ package android.media {
field public static final java.lang.String WIDTH = "android.media.mediaplayer.width";
}
public static final class MediaPlayer2.NoDrmSchemeException extends android.media.MediaDrmException {
ctor public MediaPlayer2.NoDrmSchemeException(java.lang.String);
}
public static class MediaPlayer2.TrackInfo {
method public android.media.MediaFormat getFormat();
method public java.lang.String getLanguage();

View File

@@ -60,6 +60,7 @@ import java.net.URL;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
@@ -67,6 +68,7 @@ import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@@ -399,8 +401,7 @@ public class MediaPlayer2 implements AutoCloseable
clearSourceInfos();
// Modular DRM clean up
mOnDrmConfigHelper = null;
synchronized (mDrmEventCbLock) {
synchronized (mDrmEventCallbackLock) {
mDrmEventCallbackRecords.clear();
}
@@ -2889,7 +2890,7 @@ public class MediaPlayer2 implements AutoCloseable
}
private void sendDrmEvent(final DrmEventNotifier notifier) {
synchronized (mDrmEventCbLock) {
synchronized (mDrmEventCallbackLock) {
try {
for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
cb.first.execute(() -> notifier.notify(cb.second));
@@ -2901,12 +2902,28 @@ public class MediaPlayer2 implements AutoCloseable
}
}
private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier)
throws InterruptedException, ExecutionException {
synchronized (mDrmEventCallbackLock) {
mDrmEventCallbackRecords.get(0);
for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
CompletableFuture<T> ret = new CompletableFuture<>();
cb.first.execute(() -> ret.complete(notifier.notifyWait(cb.second)));
return ret.get();
}
}
return null;
}
private interface EventNotifier {
void notify(EventCallback callback);
}
private interface DrmEventNotifier {
void notify(DrmEventCallback callback);
private interface DrmEventNotifier<T> {
default void notify(DrmEventCallback callback) { }
default T notifyWait(DrmEventCallback callback) {
return null;
}
}
/* Do not change these values without updating their counterparts
@@ -3351,73 +3368,209 @@ public class MediaPlayer2 implements AutoCloseable
// Modular DRM begin
/**
* Interface definition of a callback to be invoked when the app
* can do DRM configuration (get/set properties) before the session
* is opened. This facilitates configuration of the properties, like
* 'securityLevel', which has to be set after DRM scheme creation but
* before the DRM session is opened.
* An immutable structure per {@link DataSourceDesc} with settings required to initiate a DRM
* protected playback session.
*
* The only allowed DRM calls in this listener are
* {@link MediaPlayer2#getDrmPropertyString(DataSourceDesc, String)}
* and {@link MediaPlayer2#setDrmPropertyString(DataSourceDesc, String, String)}.
* @hide
* @see DrmPreparationInfo.Builder
*/
public interface OnDrmConfigHelper {
public static final class DrmPreparationInfo {
/**
* Called to give the app the opportunity to configure DRM before the session is created
*
* @param mp the {@code MediaPlayer2} associated with this callback
* @param dsd the DataSourceDesc of this data source
* Mutable builder to create a {@link MediaPlayer2.DrmPreparationInfo} object.
*/
public void onDrmConfig(MediaPlayer2 mp, DataSourceDesc dsd);
}
public static final class Builder {
/**
* Register a callback to be invoked for configuration of the DRM object before
* the session is created.
* The callback will be invoked synchronously during the execution
* of {@link #prepareDrm}.
*
* @param listener the callback that will be run
* @hide
*/
// This is a synchronous call.
public void setOnDrmConfigHelper(OnDrmConfigHelper listener) {
mOnDrmConfigHelper = listener;
}
private UUID mUUID;
private byte[] mKeySetId;
private byte[] mInitData;
private String mMimeType;
private int mKeyType;
private Map<String, String> mOptionalParameters;
private OnDrmConfigHelper mOnDrmConfigHelper;
/**
* Set UUID of the crypto scheme selected to decrypt content. An UUID can be retrieved from
* the source listening to {@link MediaPlayer2.DrmEventCallback#onDrmInfo}.
*
* @param uuid of selected crypto scheme
* @return this
*/
public Builder setUuid(@NonNull UUID uuid) {
this.mUUID = uuid;
return this;
}
/**
* Set identifier of a persisted offline key obtained from
* {@link MediaPlayer2.DrmEventCallback#onDrmPrepared(MediaPlayer2, DataSourceDesc, int, byte[])}.
*
* A {@code keySetId} can be used to restore persisted offline keys into a new playback
* session of a DRM protected data source. When {@code keySetId} is set, {@code initData},
* {@code mimeType}, {@code keyType}, {@code optionalParameters} are ignored.
*
* @param keySetId identifier of a persisted offline key
* @return this
*/
public Builder setKeySetId(@Nullable byte[] keySetId) {
this.mKeySetId = keySetId;
return this;
}
/**
* Set container-specific DRM initialization data. Its meaning is interpreted based on
* {@code mimeType}. For example, it could contain the content ID, key ID or other data
* obtained from the content metadata that is required to generate a
* {@link MediaDrm.KeyRequest}.
*
* @param initData container-specific DRM initialization data
* @return this
*/
public Builder setInitData(@Nullable byte[] initData) {
this.mInitData = initData;
return this;
}
/**
* Set mime type of the content
*
* @param mimeType mime type to the content
* @return this
*/
public Builder setMimeType(@Nullable String mimeType) {
this.mMimeType = mimeType;
return this;
}
/**
* Set type of the key request. The request may be to acquire keys
* for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content,
* {@link MediaDrm#KEY_TYPE_OFFLINE}. Releasing previously acquired keys
* ({@link MediaDrm#KEY_TYPE_RELEASE}) is not allowed.
*
* @param keyType type of the key request
* @return this
*/
public Builder setKeyType(@MediaPlayer2.MediaDrmKeyType int keyType) {
this.mKeyType = keyType;
return this;
}
/**
* Set optional parameters to be included in a {@link MediaDrm.KeyRequest} message sent to
* the license server.
*
* @param optionalParameters optional parameters to be included in a key request
* @return this
*/
public Builder setOptionalParameters(
@Nullable Map<String, String> optionalParameters) {
this.mOptionalParameters = optionalParameters;
return this;
}
/**
* @return an immutable {@link MediaPlayer2.DrmPreparationInfo} representing the settings of this builder
*/
public MediaPlayer2.DrmPreparationInfo build() {
return new MediaPlayer2.DrmPreparationInfo(mUUID, mKeySetId, mInitData, mMimeType, mKeyType,
mOptionalParameters);
}
}
private final UUID mUUID;
private final byte[] mKeySetId;
private final byte[] mInitData;
private final String mMimeType;
private final int mKeyType;
private final Map<String, String> mOptionalParameters;
private DrmPreparationInfo(UUID mUUID, byte[] mKeySetId, byte[] mInitData, String mMimeType,
int mKeyType, Map<String, String> optionalParameters) {
this.mUUID = mUUID;
this.mKeySetId = mKeySetId;
this.mInitData = mInitData;
this.mMimeType = mMimeType;
this.mKeyType = mKeyType;
this.mOptionalParameters = optionalParameters;
}
}
/**
* Interface definition for callbacks to be invoked when the player has the corresponding
* DRM events.
* @hide
*/
public static class DrmEventCallback {
/**
* Called to indicate DRM info is available
* Called to indicate DRM info is available. Return a {@link DrmPreparationInfo} object that
* bundles DRM initialization parameters.
*
* @param mp the {@code MediaPlayer2} associated with this callback
* @param dsd the DataSourceDesc of this data source
* @param drmInfo DRM info of the source including PSSH, and subset
* of crypto schemes supported by this device
* @param dsd the {@link DataSourceDesc} of this data source
* @param drmInfo DRM info of the source including PSSH, and subset of crypto schemes
* supported by this device
* @return a {@link DrmPreparationInfo} object to initialize DRM playback, or null to skip
* DRM initialization
*/
public void onDrmInfo(MediaPlayer2 mp, DataSourceDesc dsd, DrmInfo drmInfo) { }
public DrmPreparationInfo onDrmInfo(MediaPlayer2 mp, DataSourceDesc dsd, DrmInfo drmInfo) {
return null;
}
/**
* Called to notify the client that {@link MediaPlayer2#prepareDrm(DataSourceDesc, UUID)}
* is finished and ready for key request/response.
* Called to notify the client that {@code mp} is ready to decrypt DRM protected data source
* {@code dsd}
*
* @param mp the {@code MediaPlayer2} associated with this callback
* @param dsd the DataSourceDesc of this data source
* @param dsd the {@link DataSourceDesc} of this data source
* @param status the result of DRM preparation.
* @param keySetId optional identifier that can be used to restore DRM playback initiated
* with a {@link MediaDrm#KEY_TYPE_OFFLINE} key request.
*
* @see DrmPreparationInfo.Builder#setKeySetId(byte[])
*/
public void onDrmPrepared(
MediaPlayer2 mp, DataSourceDesc dsd, @PrepareDrmStatusCode int status) { }
public void onDrmPrepared(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
@PrepareDrmStatusCode int status, @Nullable byte[] keySetId) { }
/**
* Called to give the app the opportunity to configure DRM before the session is created.
*
* This facilitates configuration of the properties, like 'securityLevel', which
* has to be set after DRM scheme creation but before the DRM session is opened.
*
* The only allowed DRM calls in this listener are
* {@link MediaDrm#getPropertyString(String)},
* {@link MediaDrm#getPropertyByteArray(String)},
* {@link MediaDrm#setPropertyString(String, String)},
* {@link MediaDrm#setPropertyByteArray(String, byte[])},
* {@link MediaDrm#setOnExpirationUpdateListener},
* and {@link MediaDrm#setOnKeyStatusChangeListener}.
*
* @param mp the {@code MediaPlayer2} associated with this callback
* @param dsd the {@link DataSourceDesc} of this data source
* @param drm handle to get/set DRM properties and listeners for this data source
*/
public void onDrmConfig(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
@NonNull MediaDrm drm) { }
/**
* Called to indicate the DRM session for {@code dsd} is ready for key request/response
*
* @param mp the {@code MediaPlayer2} associated with this callback
* @param dsd the {@link DataSourceDesc} of this data source
* @param request a {@link MediaDrm.KeyRequest} prepared using the
* {@link DrmPreparationInfo} returned from
* {@link #onDrmInfo(MediaPlayer2, DataSourceDesc, DrmInfo)}
* @return the response to {@code request} (from license server)
*/
public byte[] onDrmKeyRequest(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
@NonNull MediaDrm.KeyRequest request) {
return null;
}
}
private final Object mDrmEventCbLock = new Object();
private ArrayList<Pair<Executor, DrmEventCallback>> mDrmEventCallbackRecords =
private final Object mDrmEventCallbackLock = new Object();
private List<Pair<Executor, DrmEventCallback>> mDrmEventCallbackRecords =
new ArrayList<Pair<Executor, DrmEventCallback>>();
/**
@@ -3425,10 +3578,9 @@ public class MediaPlayer2 implements AutoCloseable
*
* @param eventCallback the callback that will be run
* @param executor the executor through which the callback should be invoked
* @hide
*/
// This is a synchronous call.
public void registerDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
public void setDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull DrmEventCallback eventCallback) {
if (eventCallback == null) {
throw new IllegalArgumentException("Illegal null EventCallback");
@@ -3437,8 +3589,9 @@ public class MediaPlayer2 implements AutoCloseable
throw new IllegalArgumentException(
"Illegal null Executor for the EventCallback");
}
synchronized (mDrmEventCbLock) {
mDrmEventCallbackRecords.add(new Pair(executor, eventCallback));
synchronized (mDrmEventCallbackLock) {
mDrmEventCallbackRecords = Collections.singletonList(
new Pair<Executor, DrmEventCallback>(executor, eventCallback));
}
}
@@ -3450,7 +3603,7 @@ public class MediaPlayer2 implements AutoCloseable
*/
// This is a synchronous call.
public void unregisterDrmEventCallback(DrmEventCallback eventCallback) {
synchronized (mDrmEventCbLock) {
synchronized (mDrmEventCallbackLock) {
for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
if (cb.second == eventCallback) {
mDrmEventCallbackRecords.remove(cb);
@@ -3564,7 +3717,7 @@ public class MediaPlayer2 implements AutoCloseable
/**
* Prepares the DRM for the given data source
* <p>
* If {@link OnDrmConfigHelper} is registered, it will be called during
* If {@link DrmEventCallback} is registered, it will be called during
* preparation to allow configuration of the DRM properties before opening the
* DRM session. It should be used only for a series of
* {@link #getDrmPropertyString(DataSourceDesc, String)} and
@@ -3587,8 +3740,7 @@ public class MediaPlayer2 implements AutoCloseable
* @param dsd The DRM protected data source
*
* @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
* from the source through {@link #getDrmInfo(DataSourceDesc)} or registering a
* {@link DrmEventCallback#onDrmInfo}.
* from the source listening to {@link DrmEventCallback#onDrmInfo}.
*
* @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
* @hide
@@ -3661,8 +3813,8 @@ public class MediaPlayer2 implements AutoCloseable
sendDrmEvent(new DrmEventNotifier() {
@Override
public void notify(DrmEventCallback callback) {
callback.onDrmPrepared(
MediaPlayer2.this, dsd, prepareDrmStatus);
callback.onDrmPrepared(MediaPlayer2.this, dsd, prepareDrmStatus,
/* TODO: keySetId */ null);
}
});
@@ -3876,7 +4028,6 @@ public class MediaPlayer2 implements AutoCloseable
/**
* Encapsulates the DRM properties of the source.
* @hide
*/
public static final class DrmInfo {
private Map<UUID, byte[]> mMapPssh;
@@ -4013,10 +4164,8 @@ public class MediaPlayer2 implements AutoCloseable
}; // DrmInfo
/**
* Thrown when a DRM method is called before preparing a DRM scheme through
* {@link MediaPlayer2#prepareDrm(DataSourceDesc, UUID)}.
* Thrown when a DRM method is called when there is no active DRM session.
* Extends MediaDrm.MediaDrmException
* @hide
*/
public static final class NoDrmSchemeException extends MediaDrmException {
public NoDrmSchemeException(String detailMessage) {
@@ -4291,9 +4440,9 @@ public class MediaPlayer2 implements AutoCloseable
}
void prepare(UUID uuid) throws UnsupportedSchemeException,
ResourceBusyException, NotProvisionedException {
final OnDrmConfigHelper onDrmConfigHelper = mOnDrmConfigHelper;
Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + onDrmConfigHelper);
ResourceBusyException, NotProvisionedException, InterruptedException,
ExecutionException {
Log.v(TAG, "prepareDrm: uuid: " + uuid);
synchronized (this) {
if (mActiveDrmUUID != null) {
@@ -4334,9 +4483,13 @@ public class MediaPlayer2 implements AutoCloseable
} // synchronized
// call the callback outside the lock
if (onDrmConfigHelper != null) {
onDrmConfigHelper.onDrmConfig(MediaPlayer2.this, mDSD);
}
sendDrmEventWait(new DrmEventNotifier<Void>() {
@Override
public Void notifyWait(DrmEventCallback callback) {
callback.onDrmConfig(MediaPlayer2.this, mDSD, mDrmObj);
return null;
}
});
synchronized (this) {
mDrmConfigAllowed = false;
@@ -4506,7 +4659,7 @@ public class MediaPlayer2 implements AutoCloseable
@Override
public void notify(DrmEventCallback callback) {
callback.onDrmPrepared(
MediaPlayer2.this, mDSD, finalStatus);
MediaPlayer2.this, mDSD, finalStatus, /* TODO: keySetId */ null);
}
});