mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Refactor DrmSession part into a separate class to prepare for multi session scenario. NO_SQ=flaky
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=165497666
This commit is contained in:
parent
978019a1a3
commit
c2d2d967e9
2 changed files with 561 additions and 438 deletions
|
|
@ -0,0 +1,546 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.drm;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.media.DeniedByServerException;
|
||||||
|
import android.media.MediaDrm;
|
||||||
|
import android.media.NotProvisionedException;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
||||||
|
import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest;
|
||||||
|
import com.google.android.exoplayer2.drm.ExoMediaDrm.OnEventListener;
|
||||||
|
import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest;
|
||||||
|
import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil;
|
||||||
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link DrmSession} that supports playbacks using {@link MediaDrm}.
|
||||||
|
*/
|
||||||
|
@TargetApi(18)
|
||||||
|
/* package */ class DefaultDrmSession<T extends ExoMediaCrypto> implements DrmSession<T> {
|
||||||
|
private static final String TAG = "DefaultDrmSession";
|
||||||
|
|
||||||
|
private static final String CENC_SCHEME_MIME_TYPE = "cenc";
|
||||||
|
|
||||||
|
private static final int MSG_PROVISION = 0;
|
||||||
|
private static final int MSG_KEYS = 1;
|
||||||
|
|
||||||
|
private static final int MAX_LICENSE_DURATION_TO_RENEW = 60;
|
||||||
|
|
||||||
|
private final Handler eventHandler;
|
||||||
|
private final DefaultDrmSessionManager.EventListener eventListener;
|
||||||
|
private final ExoMediaDrm<T> mediaDrm;
|
||||||
|
private final HashMap<String, String> optionalKeyRequestParameters;
|
||||||
|
/* package */ final MediaDrmCallback callback;
|
||||||
|
/* package */ final UUID uuid;
|
||||||
|
/* package */ MediaDrmHandler mediaDrmHandler;
|
||||||
|
/* package */ PostResponseHandler postResponseHandler;
|
||||||
|
private HandlerThread requestHandlerThread;
|
||||||
|
private Handler postRequestHandler;
|
||||||
|
|
||||||
|
@DefaultDrmSessionManager.Mode
|
||||||
|
private final int mode;
|
||||||
|
private int openCount;
|
||||||
|
private boolean provisioningInProgress;
|
||||||
|
@DrmSession.State
|
||||||
|
private int state;
|
||||||
|
private T mediaCrypto;
|
||||||
|
private DrmSessionException lastException;
|
||||||
|
private final byte[] schemeInitData;
|
||||||
|
private final String schemeMimeType;
|
||||||
|
private byte[] sessionId;
|
||||||
|
private byte[] offlineLicenseKeySetId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new DRM session.
|
||||||
|
*
|
||||||
|
* @param uuid The UUID of the drm scheme.
|
||||||
|
* @param mediaDrm The media DRM.
|
||||||
|
* @param initData The DRM init data.
|
||||||
|
* @param mode The DRM mode.
|
||||||
|
* @param offlineLicenseKeySetId The offlineLicense KeySetId.
|
||||||
|
* @param optionalKeyRequestParameters The optional key request parameters.
|
||||||
|
* @param callback The media DRM callback.
|
||||||
|
* @param playbackLooper The playback looper.
|
||||||
|
* @param eventHandler The handler to post listener events.
|
||||||
|
* @param eventListener The DRM session manager event listener.
|
||||||
|
*/
|
||||||
|
public DefaultDrmSession(UUID uuid, ExoMediaDrm<T> mediaDrm, DrmInitData initData,
|
||||||
|
@DefaultDrmSessionManager.Mode int mode, byte[] offlineLicenseKeySetId,
|
||||||
|
HashMap<String, String> optionalKeyRequestParameters, MediaDrmCallback callback,
|
||||||
|
Looper playbackLooper, Handler eventHandler,
|
||||||
|
DefaultDrmSessionManager.EventListener eventListener) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.mediaDrm = mediaDrm;
|
||||||
|
this.mode = mode;
|
||||||
|
this.offlineLicenseKeySetId = offlineLicenseKeySetId;
|
||||||
|
this.optionalKeyRequestParameters = optionalKeyRequestParameters;
|
||||||
|
this.callback = callback;
|
||||||
|
|
||||||
|
this.eventHandler = eventHandler;
|
||||||
|
this.eventListener = eventListener;
|
||||||
|
state = STATE_OPENING;
|
||||||
|
|
||||||
|
mediaDrmHandler = new MediaDrmHandler(playbackLooper);
|
||||||
|
mediaDrm.setOnEventListener(new MediaDrmEventListener());
|
||||||
|
postResponseHandler = new PostResponseHandler(playbackLooper);
|
||||||
|
requestHandlerThread = new HandlerThread("DrmRequestHandler");
|
||||||
|
requestHandlerThread.start();
|
||||||
|
postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper());
|
||||||
|
|
||||||
|
// Parse init data.
|
||||||
|
byte[] schemeInitData = null;
|
||||||
|
String schemeMimeType = null;
|
||||||
|
if (offlineLicenseKeySetId == null) {
|
||||||
|
SchemeData data = getSchemeData(initData, uuid);
|
||||||
|
if (data == null) {
|
||||||
|
onError(new IllegalStateException("Media does not support uuid: " + uuid));
|
||||||
|
} else {
|
||||||
|
schemeInitData = data.data;
|
||||||
|
schemeMimeType = data.mimeType;
|
||||||
|
if (Util.SDK_INT < 21) {
|
||||||
|
// Prior to L the Widevine CDM required data to be extracted from the PSSH atom.
|
||||||
|
byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(schemeInitData, uuid);
|
||||||
|
if (psshData == null) {
|
||||||
|
// Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged.
|
||||||
|
} else {
|
||||||
|
schemeInitData = psshData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Util.SDK_INT < 26 && C.CLEARKEY_UUID.equals(uuid)
|
||||||
|
&& (MimeTypes.VIDEO_MP4.equals(schemeMimeType)
|
||||||
|
|| MimeTypes.AUDIO_MP4.equals(schemeMimeType))) {
|
||||||
|
// Prior to API level 26 the ClearKey CDM only accepted "cenc" as the scheme for MP4.
|
||||||
|
schemeMimeType = CENC_SCHEME_MIME_TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.schemeInitData = schemeInitData;
|
||||||
|
this.schemeMimeType = schemeMimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Life cycle.
|
||||||
|
|
||||||
|
public void acquire() {
|
||||||
|
if (++openCount == 1) {
|
||||||
|
if (state == STATE_ERROR) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (openInternal(true)) {
|
||||||
|
doLicense();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return True if the session is closed and cleaned up, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean release() {
|
||||||
|
if (--openCount == 0) {
|
||||||
|
state = STATE_RELEASED;
|
||||||
|
provisioningInProgress = false;
|
||||||
|
mediaDrmHandler.removeCallbacksAndMessages(null);
|
||||||
|
mediaDrmHandler = null;
|
||||||
|
postResponseHandler.removeCallbacksAndMessages(null);
|
||||||
|
postRequestHandler.removeCallbacksAndMessages(null);
|
||||||
|
postRequestHandler = null;
|
||||||
|
requestHandlerThread.quit();
|
||||||
|
requestHandlerThread = null;
|
||||||
|
mediaCrypto = null;
|
||||||
|
lastException = null;
|
||||||
|
if (sessionId != null) {
|
||||||
|
mediaDrm.closeSession(sessionId);
|
||||||
|
sessionId = null;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrmSession Implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@DrmSession.State
|
||||||
|
public final int getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final DrmSessionException getError() {
|
||||||
|
return state == STATE_ERROR ? lastException : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final T getMediaCrypto() {
|
||||||
|
return mediaCrypto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> queryKeyStatus() {
|
||||||
|
return sessionId == null ? null : mediaDrm.queryKeyStatus(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getOfflineLicenseKeySetId() {
|
||||||
|
return offlineLicenseKeySetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal methods.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to open a session, do provisioning if necessary.
|
||||||
|
* @param allowProvisioning if provisioning is allowed, set this to false when calling from
|
||||||
|
* processing provision response.
|
||||||
|
* @return true on success, false otherwise.
|
||||||
|
*/
|
||||||
|
private boolean openInternal(boolean allowProvisioning) {
|
||||||
|
if (isOpen()) {
|
||||||
|
// Already opened
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
sessionId = mediaDrm.openSession();
|
||||||
|
mediaCrypto = mediaDrm.createMediaCrypto(uuid, sessionId);
|
||||||
|
state = STATE_OPENED;
|
||||||
|
return true;
|
||||||
|
} catch (NotProvisionedException e) {
|
||||||
|
if (allowProvisioning) {
|
||||||
|
postProvisionRequest();
|
||||||
|
} else {
|
||||||
|
onError(e);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// MediaCryptoException
|
||||||
|
// ResourceBusyException only available on 19+
|
||||||
|
onError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postProvisionRequest() {
|
||||||
|
if (provisioningInProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
provisioningInProgress = true;
|
||||||
|
ProvisionRequest request = mediaDrm.getProvisionRequest();
|
||||||
|
postRequestHandler.obtainMessage(MSG_PROVISION, request).sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onProvisionResponse(Object response) {
|
||||||
|
provisioningInProgress = false;
|
||||||
|
if (state != STATE_OPENING && !isOpen()) {
|
||||||
|
// This event is stale.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response instanceof Exception) {
|
||||||
|
onError((Exception) response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mediaDrm.provideProvisionResponse((byte[]) response);
|
||||||
|
if (openInternal(false)) {
|
||||||
|
doLicense();
|
||||||
|
}
|
||||||
|
} catch (DeniedByServerException e) {
|
||||||
|
onError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doLicense() {
|
||||||
|
switch (mode) {
|
||||||
|
case DefaultDrmSessionManager.MODE_PLAYBACK:
|
||||||
|
case DefaultDrmSessionManager.MODE_QUERY:
|
||||||
|
if (offlineLicenseKeySetId == null) {
|
||||||
|
postKeyRequest(MediaDrm.KEY_TYPE_STREAMING);
|
||||||
|
} else {
|
||||||
|
if (restoreKeys()) {
|
||||||
|
long licenseDurationRemainingSec = getLicenseDurationRemainingSec();
|
||||||
|
if (mode == DefaultDrmSessionManager.MODE_PLAYBACK
|
||||||
|
&& licenseDurationRemainingSec <= MAX_LICENSE_DURATION_TO_RENEW) {
|
||||||
|
Log.d(TAG, "Offline license has expired or will expire soon. "
|
||||||
|
+ "Remaining seconds: " + licenseDurationRemainingSec);
|
||||||
|
postKeyRequest(MediaDrm.KEY_TYPE_OFFLINE);
|
||||||
|
} else if (licenseDurationRemainingSec <= 0) {
|
||||||
|
onError(new KeysExpiredException());
|
||||||
|
} else {
|
||||||
|
state = STATE_OPENED_WITH_KEYS;
|
||||||
|
if (eventHandler != null && eventListener != null) {
|
||||||
|
eventHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
eventListener.onDrmKeysRestored();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DefaultDrmSessionManager.MODE_DOWNLOAD:
|
||||||
|
if (offlineLicenseKeySetId == null) {
|
||||||
|
postKeyRequest(MediaDrm.KEY_TYPE_OFFLINE);
|
||||||
|
} else {
|
||||||
|
// Renew
|
||||||
|
if (restoreKeys()) {
|
||||||
|
postKeyRequest(MediaDrm.KEY_TYPE_OFFLINE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DefaultDrmSessionManager.MODE_RELEASE:
|
||||||
|
// It's not necessary to restore the key (and open a session to do that) before releasing it
|
||||||
|
// but this serves as a good sanity/fast-failure check.
|
||||||
|
if (restoreKeys()) {
|
||||||
|
postKeyRequest(MediaDrm.KEY_TYPE_RELEASE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean restoreKeys() {
|
||||||
|
try {
|
||||||
|
mediaDrm.restoreKeys(sessionId, offlineLicenseKeySetId);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error trying to restore Widevine keys.", e);
|
||||||
|
onError(e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getLicenseDurationRemainingSec() {
|
||||||
|
if (!C.WIDEVINE_UUID.equals(uuid)) {
|
||||||
|
return Long.MAX_VALUE;
|
||||||
|
}
|
||||||
|
Pair<Long, Long> pair = WidevineUtil.getLicenseDurationRemainingSec(this);
|
||||||
|
return Math.min(pair.first, pair.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postKeyRequest(int type) {
|
||||||
|
byte[] scope = type == MediaDrm.KEY_TYPE_RELEASE ? offlineLicenseKeySetId : sessionId;
|
||||||
|
try {
|
||||||
|
KeyRequest request = mediaDrm.getKeyRequest(scope, schemeInitData, schemeMimeType, type,
|
||||||
|
optionalKeyRequestParameters);
|
||||||
|
postRequestHandler.obtainMessage(MSG_KEYS, request).sendToTarget();
|
||||||
|
} catch (Exception e) {
|
||||||
|
onKeysError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onKeyResponse(Object response) {
|
||||||
|
if (!isOpen()) {
|
||||||
|
// This event is stale.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response instanceof Exception) {
|
||||||
|
onKeysError((Exception) response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (mode == DefaultDrmSessionManager.MODE_RELEASE) {
|
||||||
|
mediaDrm.provideKeyResponse(offlineLicenseKeySetId, (byte[]) response);
|
||||||
|
if (eventHandler != null && eventListener != null) {
|
||||||
|
eventHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
eventListener.onDrmKeysRemoved();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
byte[] keySetId = mediaDrm.provideKeyResponse(sessionId, (byte[]) response);
|
||||||
|
if ((mode == DefaultDrmSessionManager.MODE_DOWNLOAD
|
||||||
|
|| (mode == DefaultDrmSessionManager.MODE_PLAYBACK && offlineLicenseKeySetId != null))
|
||||||
|
&& keySetId != null && keySetId.length != 0) {
|
||||||
|
offlineLicenseKeySetId = keySetId;
|
||||||
|
}
|
||||||
|
state = STATE_OPENED_WITH_KEYS;
|
||||||
|
if (eventHandler != null && eventListener != null) {
|
||||||
|
eventHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
eventListener.onDrmKeysLoaded();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
onKeysError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onKeysExpired() {
|
||||||
|
if (state == STATE_OPENED_WITH_KEYS) {
|
||||||
|
state = STATE_OPENED;
|
||||||
|
onError(new KeysExpiredException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onKeysError(Exception e) {
|
||||||
|
if (e instanceof NotProvisionedException) {
|
||||||
|
postProvisionRequest();
|
||||||
|
} else {
|
||||||
|
onError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onError(final Exception e) {
|
||||||
|
lastException = new DrmSessionException(e);
|
||||||
|
if (eventHandler != null && eventListener != null) {
|
||||||
|
eventHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
eventListener.onDrmSessionManagerError(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (state != STATE_OPENED_WITH_KEYS) {
|
||||||
|
state = STATE_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOpen() {
|
||||||
|
return state == STATE_OPENED || state == STATE_OPENED_WITH_KEYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("HandlerLeak")
|
||||||
|
private class MediaDrmHandler extends Handler {
|
||||||
|
|
||||||
|
public MediaDrmHandler(Looper looper) {
|
||||||
|
super(looper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
if (!isOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (msg.what) {
|
||||||
|
case MediaDrm.EVENT_KEY_REQUIRED:
|
||||||
|
doLicense();
|
||||||
|
break;
|
||||||
|
case MediaDrm.EVENT_KEY_EXPIRED:
|
||||||
|
// When an already expired key is loaded MediaDrm sends this event immediately. Ignore
|
||||||
|
// this event if the state isn't STATE_OPENED_WITH_KEYS yet which means we're still
|
||||||
|
// waiting for key response.
|
||||||
|
onKeysExpired();
|
||||||
|
break;
|
||||||
|
case MediaDrm.EVENT_PROVISION_REQUIRED:
|
||||||
|
state = STATE_OPENED;
|
||||||
|
postProvisionRequest();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MediaDrmEventListener implements OnEventListener<T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvent(ExoMediaDrm<? extends T> md, byte[] sessionId, int event, int extra,
|
||||||
|
byte[] data) {
|
||||||
|
if (mode == DefaultDrmSessionManager.MODE_PLAYBACK) {
|
||||||
|
mediaDrmHandler.sendEmptyMessage(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("HandlerLeak")
|
||||||
|
private class PostResponseHandler extends Handler {
|
||||||
|
|
||||||
|
public PostResponseHandler(Looper looper) {
|
||||||
|
super(looper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_PROVISION:
|
||||||
|
onProvisionResponse(msg.obj);
|
||||||
|
break;
|
||||||
|
case MSG_KEYS:
|
||||||
|
onKeyResponse(msg.obj);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("HandlerLeak")
|
||||||
|
private class PostRequestHandler extends Handler {
|
||||||
|
|
||||||
|
public PostRequestHandler(Looper backgroundLooper) {
|
||||||
|
super(backgroundLooper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
Object response;
|
||||||
|
try {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_PROVISION:
|
||||||
|
response = callback.executeProvisionRequest(uuid, (ProvisionRequest) msg.obj);
|
||||||
|
break;
|
||||||
|
case MSG_KEYS:
|
||||||
|
response = callback.executeKeyRequest(uuid, (KeyRequest) msg.obj);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
response = e;
|
||||||
|
}
|
||||||
|
postResponseHandler.obtainMessage(msg.what, response).sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts {@link SchemeData} suitable for the given DRM scheme {@link UUID}.
|
||||||
|
*
|
||||||
|
* @param drmInitData The {@link DrmInitData} from which to extract the {@link SchemeData}.
|
||||||
|
* @param uuid The UUID of the scheme.
|
||||||
|
* @return The extracted {@link SchemeData}, or null if no suitable data is present.
|
||||||
|
*/
|
||||||
|
public static SchemeData getSchemeData(DrmInitData drmInitData, UUID uuid) {
|
||||||
|
SchemeData schemeData = drmInitData.get(uuid);
|
||||||
|
if (schemeData == null && C.CLEARKEY_UUID.equals(uuid)) {
|
||||||
|
// If present, the Common PSSH box should be used for ClearKey.
|
||||||
|
schemeData = drmInitData.get(C.COMMON_PSSH_UUID);
|
||||||
|
}
|
||||||
|
return schemeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -15,41 +15,27 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.drm;
|
package com.google.android.exoplayer2.drm;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.media.DeniedByServerException;
|
|
||||||
import android.media.MediaDrm;
|
import android.media.MediaDrm;
|
||||||
import android.media.NotProvisionedException;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
import android.util.Pair;
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
||||||
import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest;
|
|
||||||
import com.google.android.exoplayer2.drm.ExoMediaDrm.OnEventListener;
|
|
||||||
import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest;
|
|
||||||
import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil;
|
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link DrmSessionManager} that supports playbacks using {@link MediaDrm}.
|
* A {@link DrmSessionManager} that supports playbacks using {@link MediaDrm}.
|
||||||
*/
|
*/
|
||||||
@TargetApi(18)
|
@TargetApi(18)
|
||||||
public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager<T>,
|
public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager<T> {
|
||||||
DrmSession<T> {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener of {@link DefaultDrmSessionManager} events.
|
* Listener of {@link DefaultDrmSessionManager} events.
|
||||||
|
|
@ -95,8 +81,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
|
||||||
*/
|
*/
|
||||||
public static final int MODE_PLAYBACK = 0;
|
public static final int MODE_PLAYBACK = 0;
|
||||||
/**
|
/**
|
||||||
* Restores an offline license to allow its status to be queried. If the offline license is
|
* Restores an offline license to allow its status to be queried.
|
||||||
* expired sets state to {@link #STATE_ERROR}.
|
|
||||||
*/
|
*/
|
||||||
public static final int MODE_QUERY = 1;
|
public static final int MODE_QUERY = 1;
|
||||||
/** Downloads an offline license or renews an existing one. */
|
/** Downloads an offline license or renews an existing one. */
|
||||||
|
|
@ -104,40 +89,18 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
|
||||||
/** Releases an existing offline license. */
|
/** Releases an existing offline license. */
|
||||||
public static final int MODE_RELEASE = 3;
|
public static final int MODE_RELEASE = 3;
|
||||||
|
|
||||||
private static final String TAG = "OfflineDrmSessionMngr";
|
|
||||||
private static final String CENC_SCHEME_MIME_TYPE = "cenc";
|
|
||||||
|
|
||||||
private static final int MSG_PROVISION = 0;
|
|
||||||
private static final int MSG_KEYS = 1;
|
|
||||||
|
|
||||||
private static final int MAX_LICENSE_DURATION_TO_RENEW = 60;
|
|
||||||
|
|
||||||
private final Handler eventHandler;
|
private final Handler eventHandler;
|
||||||
private final EventListener eventListener;
|
private final EventListener eventListener;
|
||||||
private final ExoMediaDrm<T> mediaDrm;
|
private final ExoMediaDrm<T> mediaDrm;
|
||||||
private final HashMap<String, String> optionalKeyRequestParameters;
|
private final HashMap<String, String> optionalKeyRequestParameters;
|
||||||
|
|
||||||
/* package */ final MediaDrmCallback callback;
|
private final MediaDrmCallback callback;
|
||||||
/* package */ final UUID uuid;
|
private final UUID uuid;
|
||||||
|
|
||||||
/* package */ MediaDrmHandler mediaDrmHandler;
|
|
||||||
/* package */ PostResponseHandler postResponseHandler;
|
|
||||||
|
|
||||||
private Looper playbackLooper;
|
private Looper playbackLooper;
|
||||||
private HandlerThread requestHandlerThread;
|
|
||||||
private Handler postRequestHandler;
|
|
||||||
|
|
||||||
private int mode;
|
private int mode;
|
||||||
private int openCount;
|
|
||||||
private boolean provisioningInProgress;
|
|
||||||
@DrmSession.State
|
|
||||||
private int state;
|
|
||||||
private T mediaCrypto;
|
|
||||||
private DrmSessionException lastException;
|
|
||||||
private byte[] schemeInitData;
|
|
||||||
private String schemeMimeType;
|
|
||||||
private byte[] sessionId;
|
|
||||||
private byte[] offlineLicenseKeySetId;
|
private byte[] offlineLicenseKeySetId;
|
||||||
|
private DefaultDrmSession<T> session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new instance using the Widevine scheme.
|
* Instantiates a new instance using the Widevine scheme.
|
||||||
|
|
@ -224,7 +187,6 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
|
||||||
this.optionalKeyRequestParameters = optionalKeyRequestParameters;
|
this.optionalKeyRequestParameters = optionalKeyRequestParameters;
|
||||||
this.eventHandler = eventHandler;
|
this.eventHandler = eventHandler;
|
||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
mediaDrm.setOnEventListener(new MediaDrmEventListener());
|
|
||||||
mode = MODE_PLAYBACK;
|
mode = MODE_PLAYBACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -299,7 +261,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
|
||||||
* @param offlineLicenseKeySetId The key set id of the license to be used with the given mode.
|
* @param offlineLicenseKeySetId The key set id of the license to be used with the given mode.
|
||||||
*/
|
*/
|
||||||
public void setMode(@Mode int mode, byte[] offlineLicenseKeySetId) {
|
public void setMode(@Mode int mode, byte[] offlineLicenseKeySetId) {
|
||||||
Assertions.checkState(openCount == 0);
|
Assertions.checkState(session == null);
|
||||||
if (mode == MODE_QUERY || mode == MODE_RELEASE) {
|
if (mode == MODE_QUERY || mode == MODE_RELEASE) {
|
||||||
Assertions.checkNotNull(offlineLicenseKeySetId);
|
Assertions.checkNotNull(offlineLicenseKeySetId);
|
||||||
}
|
}
|
||||||
|
|
@ -311,7 +273,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canAcquireSession(@NonNull DrmInitData drmInitData) {
|
public boolean canAcquireSession(@NonNull DrmInitData drmInitData) {
|
||||||
SchemeData schemeData = getSchemeData(drmInitData);
|
SchemeData schemeData = DefaultDrmSession.getSchemeData(drmInitData, uuid);
|
||||||
if (schemeData == null) {
|
if (schemeData == null) {
|
||||||
// No data for this manager's scheme.
|
// No data for this manager's scheme.
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -332,407 +294,22 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
|
||||||
@Override
|
@Override
|
||||||
public DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitData) {
|
public DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitData) {
|
||||||
Assertions.checkState(this.playbackLooper == null || this.playbackLooper == playbackLooper);
|
Assertions.checkState(this.playbackLooper == null || this.playbackLooper == playbackLooper);
|
||||||
if (++openCount != 1) {
|
if (session == null) {
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.playbackLooper == null) {
|
|
||||||
this.playbackLooper = playbackLooper;
|
this.playbackLooper = playbackLooper;
|
||||||
mediaDrmHandler = new MediaDrmHandler(playbackLooper);
|
session = new DefaultDrmSession<T>(uuid, mediaDrm, drmInitData, mode, offlineLicenseKeySetId,
|
||||||
postResponseHandler = new PostResponseHandler(playbackLooper);
|
optionalKeyRequestParameters, callback, playbackLooper, eventHandler, eventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestHandlerThread = new HandlerThread("DrmRequestHandler");
|
session.acquire();
|
||||||
requestHandlerThread.start();
|
return session;
|
||||||
postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper());
|
|
||||||
|
|
||||||
if (offlineLicenseKeySetId == null) {
|
|
||||||
SchemeData schemeData = getSchemeData(drmInitData);
|
|
||||||
if (schemeData == null) {
|
|
||||||
onError(new IllegalStateException("Media does not support uuid: " + uuid));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
schemeInitData = schemeData.data;
|
|
||||||
schemeMimeType = schemeData.mimeType;
|
|
||||||
if (Util.SDK_INT < 21) {
|
|
||||||
// Prior to L the Widevine CDM required data to be extracted from the PSSH atom.
|
|
||||||
byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(schemeInitData, C.WIDEVINE_UUID);
|
|
||||||
if (psshData == null) {
|
|
||||||
// Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged.
|
|
||||||
} else {
|
|
||||||
schemeInitData = psshData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Util.SDK_INT < 26 && C.CLEARKEY_UUID.equals(uuid)
|
|
||||||
&& (MimeTypes.VIDEO_MP4.equals(schemeMimeType)
|
|
||||||
|| MimeTypes.AUDIO_MP4.equals(schemeMimeType))) {
|
|
||||||
// Prior to API level 26 the ClearKey CDM only accepted "cenc" as the scheme for MP4.
|
|
||||||
schemeMimeType = CENC_SCHEME_MIME_TYPE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state = STATE_OPENING;
|
|
||||||
openInternal(true);
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseSession(DrmSession<T> session) {
|
public void releaseSession(DrmSession<T> session) {
|
||||||
if (--openCount != 0) {
|
Assertions.checkState(session == this.session);
|
||||||
return;
|
if (this.session.release()) {
|
||||||
}
|
this.session = null;
|
||||||
state = STATE_RELEASED;
|
|
||||||
provisioningInProgress = false;
|
|
||||||
mediaDrmHandler.removeCallbacksAndMessages(null);
|
|
||||||
postResponseHandler.removeCallbacksAndMessages(null);
|
|
||||||
postRequestHandler.removeCallbacksAndMessages(null);
|
|
||||||
postRequestHandler = null;
|
|
||||||
requestHandlerThread.quit();
|
|
||||||
requestHandlerThread = null;
|
|
||||||
schemeInitData = null;
|
|
||||||
schemeMimeType = null;
|
|
||||||
mediaCrypto = null;
|
|
||||||
lastException = null;
|
|
||||||
if (sessionId != null) {
|
|
||||||
mediaDrm.closeSession(sessionId);
|
|
||||||
sessionId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrmSession implementation.
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@DrmSession.State
|
|
||||||
public final int getState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final DrmSessionException getError() {
|
|
||||||
return state == STATE_ERROR ? lastException : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final T getMediaCrypto() {
|
|
||||||
return mediaCrypto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> queryKeyStatus() {
|
|
||||||
return sessionId == null ? null : mediaDrm.queryKeyStatus(sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getOfflineLicenseKeySetId() {
|
|
||||||
return offlineLicenseKeySetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal methods.
|
|
||||||
|
|
||||||
private void openInternal(boolean allowProvisioning) {
|
|
||||||
try {
|
|
||||||
sessionId = mediaDrm.openSession();
|
|
||||||
mediaCrypto = mediaDrm.createMediaCrypto(uuid, sessionId);
|
|
||||||
state = STATE_OPENED;
|
|
||||||
doLicense();
|
|
||||||
} catch (NotProvisionedException e) {
|
|
||||||
if (allowProvisioning) {
|
|
||||||
postProvisionRequest();
|
|
||||||
} else {
|
|
||||||
onError(e);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
onError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void postProvisionRequest() {
|
|
||||||
if (provisioningInProgress) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
provisioningInProgress = true;
|
|
||||||
ProvisionRequest request = mediaDrm.getProvisionRequest();
|
|
||||||
postRequestHandler.obtainMessage(MSG_PROVISION, request).sendToTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onProvisionResponse(Object response) {
|
|
||||||
provisioningInProgress = false;
|
|
||||||
if (state != STATE_OPENING && state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) {
|
|
||||||
// This event is stale.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response instanceof Exception) {
|
|
||||||
onError((Exception) response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
mediaDrm.provideProvisionResponse((byte[]) response);
|
|
||||||
if (state == STATE_OPENING) {
|
|
||||||
openInternal(false);
|
|
||||||
} else {
|
|
||||||
doLicense();
|
|
||||||
}
|
|
||||||
} catch (DeniedByServerException e) {
|
|
||||||
onError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doLicense() {
|
|
||||||
switch (mode) {
|
|
||||||
case MODE_PLAYBACK:
|
|
||||||
case MODE_QUERY:
|
|
||||||
if (offlineLicenseKeySetId == null) {
|
|
||||||
postKeyRequest(sessionId, MediaDrm.KEY_TYPE_STREAMING);
|
|
||||||
} else {
|
|
||||||
if (restoreKeys()) {
|
|
||||||
long licenseDurationRemainingSec = getLicenseDurationRemainingSec();
|
|
||||||
if (mode == MODE_PLAYBACK
|
|
||||||
&& licenseDurationRemainingSec <= MAX_LICENSE_DURATION_TO_RENEW) {
|
|
||||||
Log.d(TAG, "Offline license has expired or will expire soon. "
|
|
||||||
+ "Remaining seconds: " + licenseDurationRemainingSec);
|
|
||||||
postKeyRequest(sessionId, MediaDrm.KEY_TYPE_OFFLINE);
|
|
||||||
} else if (licenseDurationRemainingSec <= 0) {
|
|
||||||
onError(new KeysExpiredException());
|
|
||||||
} else {
|
|
||||||
state = STATE_OPENED_WITH_KEYS;
|
|
||||||
if (eventHandler != null && eventListener != null) {
|
|
||||||
eventHandler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
eventListener.onDrmKeysRestored();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MODE_DOWNLOAD:
|
|
||||||
if (offlineLicenseKeySetId == null) {
|
|
||||||
postKeyRequest(sessionId, MediaDrm.KEY_TYPE_OFFLINE);
|
|
||||||
} else {
|
|
||||||
// Renew
|
|
||||||
if (restoreKeys()) {
|
|
||||||
postKeyRequest(sessionId, MediaDrm.KEY_TYPE_OFFLINE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MODE_RELEASE:
|
|
||||||
// It's not necessary to restore the key (and open a session to do that) before releasing it
|
|
||||||
// but this serves as a good sanity/fast-failure check.
|
|
||||||
if (restoreKeys()) {
|
|
||||||
postKeyRequest(offlineLicenseKeySetId, MediaDrm.KEY_TYPE_RELEASE);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean restoreKeys() {
|
|
||||||
try {
|
|
||||||
mediaDrm.restoreKeys(sessionId, offlineLicenseKeySetId);
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error trying to restore Widevine keys.", e);
|
|
||||||
onError(e);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long getLicenseDurationRemainingSec() {
|
|
||||||
if (!C.WIDEVINE_UUID.equals(uuid)) {
|
|
||||||
return Long.MAX_VALUE;
|
|
||||||
}
|
|
||||||
Pair<Long, Long> pair = WidevineUtil.getLicenseDurationRemainingSec(this);
|
|
||||||
return Math.min(pair.first, pair.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void postKeyRequest(byte[] scope, int keyType) {
|
|
||||||
try {
|
|
||||||
KeyRequest keyRequest = mediaDrm.getKeyRequest(scope, schemeInitData, schemeMimeType, keyType,
|
|
||||||
optionalKeyRequestParameters);
|
|
||||||
postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget();
|
|
||||||
} catch (Exception e) {
|
|
||||||
onKeysError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onKeyResponse(Object response) {
|
|
||||||
if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) {
|
|
||||||
// This event is stale.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response instanceof Exception) {
|
|
||||||
onKeysError((Exception) response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (mode == MODE_RELEASE) {
|
|
||||||
mediaDrm.provideKeyResponse(offlineLicenseKeySetId, (byte[]) response);
|
|
||||||
if (eventHandler != null && eventListener != null) {
|
|
||||||
eventHandler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
eventListener.onDrmKeysRemoved();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
byte[] keySetId = mediaDrm.provideKeyResponse(sessionId, (byte[]) response);
|
|
||||||
if ((mode == MODE_DOWNLOAD || (mode == MODE_PLAYBACK && offlineLicenseKeySetId != null))
|
|
||||||
&& keySetId != null && keySetId.length != 0) {
|
|
||||||
offlineLicenseKeySetId = keySetId;
|
|
||||||
}
|
|
||||||
state = STATE_OPENED_WITH_KEYS;
|
|
||||||
if (eventHandler != null && eventListener != null) {
|
|
||||||
eventHandler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
eventListener.onDrmKeysLoaded();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
onKeysError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onKeysError(Exception e) {
|
|
||||||
if (e instanceof NotProvisionedException) {
|
|
||||||
postProvisionRequest();
|
|
||||||
} else {
|
|
||||||
onError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onError(final Exception e) {
|
|
||||||
lastException = new DrmSessionException(e);
|
|
||||||
if (eventHandler != null && eventListener != null) {
|
|
||||||
eventHandler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
eventListener.onDrmSessionManagerError(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (state != STATE_OPENED_WITH_KEYS) {
|
|
||||||
state = STATE_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts {@link SchemeData} suitable for this manager.
|
|
||||||
*
|
|
||||||
* @param drmInitData The {@link DrmInitData} from which to extract the {@link SchemeData}.
|
|
||||||
* @return The extracted {@link SchemeData}, or null if no suitable data is present.
|
|
||||||
*/
|
|
||||||
private SchemeData getSchemeData(DrmInitData drmInitData) {
|
|
||||||
SchemeData schemeData = drmInitData.get(uuid);
|
|
||||||
if (schemeData == null && C.CLEARKEY_UUID.equals(uuid)) {
|
|
||||||
// If present, the Common PSSH box should be used for ClearKey.
|
|
||||||
schemeData = drmInitData.get(C.COMMON_PSSH_UUID);
|
|
||||||
}
|
|
||||||
return schemeData;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("HandlerLeak")
|
|
||||||
private class MediaDrmHandler extends Handler {
|
|
||||||
|
|
||||||
public MediaDrmHandler(Looper looper) {
|
|
||||||
super(looper);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Override
|
|
||||||
public void handleMessage(Message msg) {
|
|
||||||
if (openCount == 0 || (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (msg.what) {
|
|
||||||
case MediaDrm.EVENT_KEY_REQUIRED:
|
|
||||||
doLicense();
|
|
||||||
break;
|
|
||||||
case MediaDrm.EVENT_KEY_EXPIRED:
|
|
||||||
// When an already expired key is loaded MediaDrm sends this event immediately. Ignore
|
|
||||||
// this event if the state isn't STATE_OPENED_WITH_KEYS yet which means we're still
|
|
||||||
// waiting for key response.
|
|
||||||
if (state == STATE_OPENED_WITH_KEYS) {
|
|
||||||
state = STATE_OPENED;
|
|
||||||
onError(new KeysExpiredException());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MediaDrm.EVENT_PROVISION_REQUIRED:
|
|
||||||
state = STATE_OPENED;
|
|
||||||
postProvisionRequest();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MediaDrmEventListener implements OnEventListener<T> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEvent(ExoMediaDrm<? extends T> md, byte[] sessionId, int event, int extra,
|
|
||||||
byte[] data) {
|
|
||||||
if (mode == MODE_PLAYBACK) {
|
|
||||||
mediaDrmHandler.sendEmptyMessage(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("HandlerLeak")
|
|
||||||
private class PostResponseHandler extends Handler {
|
|
||||||
|
|
||||||
public PostResponseHandler(Looper looper) {
|
|
||||||
super(looper);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleMessage(Message msg) {
|
|
||||||
switch (msg.what) {
|
|
||||||
case MSG_PROVISION:
|
|
||||||
onProvisionResponse(msg.obj);
|
|
||||||
break;
|
|
||||||
case MSG_KEYS:
|
|
||||||
onKeyResponse(msg.obj);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("HandlerLeak")
|
|
||||||
private class PostRequestHandler extends Handler {
|
|
||||||
|
|
||||||
public PostRequestHandler(Looper backgroundLooper) {
|
|
||||||
super(backgroundLooper);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleMessage(Message msg) {
|
|
||||||
Object response;
|
|
||||||
try {
|
|
||||||
switch (msg.what) {
|
|
||||||
case MSG_PROVISION:
|
|
||||||
response = callback.executeProvisionRequest(uuid, (ProvisionRequest) msg.obj);
|
|
||||||
break;
|
|
||||||
case MSG_KEYS:
|
|
||||||
response = callback.executeKeyRequest(uuid, (KeyRequest) msg.obj);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
response = e;
|
|
||||||
}
|
|
||||||
postResponseHandler.obtainMessage(msg.what, response).sendToTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue