Commit 13889c91 by olly Committed by Oliver Woodman

Pass multiple PSSH boxes to Widevine CDM

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=210551848
parent 6b9e1824
...@@ -31,7 +31,9 @@ import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest; ...@@ -31,7 +31,9 @@ import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest;
import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest;
import com.google.android.exoplayer2.util.EventDispatcher; import com.google.android.exoplayer2.util.EventDispatcher;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
...@@ -76,9 +78,11 @@ import java.util.UUID; ...@@ -76,9 +78,11 @@ import java.util.UUID;
private static final int MSG_KEYS = 1; private static final int MSG_KEYS = 1;
private static final int MAX_LICENSE_DURATION_TO_RENEW = 60; private static final int MAX_LICENSE_DURATION_TO_RENEW = 60;
/** The DRM scheme datas, or null if this session uses offline keys. */
public final @Nullable List<SchemeData> schemeDatas;
private final ExoMediaDrm<T> mediaDrm; private final ExoMediaDrm<T> mediaDrm;
private final ProvisioningManager<T> provisioningManager; private final ProvisioningManager<T> provisioningManager;
private final SchemeData schemeData;
private final @DefaultDrmSessionManager.Mode int mode; private final @DefaultDrmSessionManager.Mode int mode;
private final HashMap<String, String> optionalKeyRequestParameters; private final HashMap<String, String> optionalKeyRequestParameters;
private final EventDispatcher<DefaultDrmSessionEventListener> eventDispatcher; private final EventDispatcher<DefaultDrmSessionEventListener> eventDispatcher;
...@@ -95,10 +99,10 @@ import java.util.UUID; ...@@ -95,10 +99,10 @@ import java.util.UUID;
private T mediaCrypto; private T mediaCrypto;
private DrmSessionException lastException; private DrmSessionException lastException;
private byte[] sessionId; private byte[] sessionId;
private byte[] offlineLicenseKeySetId; private @Nullable byte[] offlineLicenseKeySetId;
private Object currentKeyRequest; private KeyRequest currentKeyRequest;
private Object currentProvisionRequest; private ProvisionRequest currentProvisionRequest;
/** /**
* Instantiates a new DRM session. * Instantiates a new DRM session.
...@@ -106,8 +110,8 @@ import java.util.UUID; ...@@ -106,8 +110,8 @@ import java.util.UUID;
* @param uuid The UUID of the drm scheme. * @param uuid The UUID of the drm scheme.
* @param mediaDrm The media DRM. * @param mediaDrm The media DRM.
* @param provisioningManager The manager for provisioning. * @param provisioningManager The manager for provisioning.
* @param schemeData The DRM data for this session, or null if a {@code offlineLicenseKeySetId} is * @param schemeDatas DRM scheme datas for this session, or null if an {@code
* provided. * offlineLicenseKeySetId} is provided.
* @param mode The DRM mode. * @param mode The DRM mode.
* @param offlineLicenseKeySetId The offline license key set identifier, or null when not using * @param offlineLicenseKeySetId The offline license key set identifier, or null when not using
* offline keys. * offline keys.
...@@ -122,7 +126,7 @@ import java.util.UUID; ...@@ -122,7 +126,7 @@ import java.util.UUID;
UUID uuid, UUID uuid,
ExoMediaDrm<T> mediaDrm, ExoMediaDrm<T> mediaDrm,
ProvisioningManager<T> provisioningManager, ProvisioningManager<T> provisioningManager,
@Nullable SchemeData schemeData, @Nullable List<SchemeData> schemeDatas,
@DefaultDrmSessionManager.Mode int mode, @DefaultDrmSessionManager.Mode int mode,
@Nullable byte[] offlineLicenseKeySetId, @Nullable byte[] offlineLicenseKeySetId,
HashMap<String, String> optionalKeyRequestParameters, HashMap<String, String> optionalKeyRequestParameters,
...@@ -135,7 +139,8 @@ import java.util.UUID; ...@@ -135,7 +139,8 @@ import java.util.UUID;
this.mediaDrm = mediaDrm; this.mediaDrm = mediaDrm;
this.mode = mode; this.mode = mode;
this.offlineLicenseKeySetId = offlineLicenseKeySetId; this.offlineLicenseKeySetId = offlineLicenseKeySetId;
this.schemeData = offlineLicenseKeySetId == null ? schemeData : null; this.schemeDatas =
offlineLicenseKeySetId == null ? Collections.unmodifiableList(schemeDatas) : null;
this.optionalKeyRequestParameters = optionalKeyRequestParameters; this.optionalKeyRequestParameters = optionalKeyRequestParameters;
this.callback = callback; this.callback = callback;
this.initialDrmRequestRetryCount = initialDrmRequestRetryCount; this.initialDrmRequestRetryCount = initialDrmRequestRetryCount;
...@@ -185,10 +190,6 @@ import java.util.UUID; ...@@ -185,10 +190,6 @@ import java.util.UUID;
return false; return false;
} }
public boolean hasInitData(byte[] initData) {
return Arrays.equals(schemeData != null ? schemeData.data : null, initData);
}
public boolean hasSessionId(byte[] sessionId) { public boolean hasSessionId(byte[] sessionId) {
return Arrays.equals(this.sessionId, sessionId); return Arrays.equals(this.sessionId, sessionId);
} }
...@@ -380,18 +381,9 @@ import java.util.UUID; ...@@ -380,18 +381,9 @@ import java.util.UUID;
private void postKeyRequest(int type, boolean allowRetry) { private void postKeyRequest(int type, boolean allowRetry) {
byte[] scope = type == ExoMediaDrm.KEY_TYPE_RELEASE ? offlineLicenseKeySetId : sessionId; byte[] scope = type == ExoMediaDrm.KEY_TYPE_RELEASE ? offlineLicenseKeySetId : sessionId;
byte[] initData = null;
String mimeType = null;
String licenseServerUrl = null;
if (schemeData != null) {
initData = schemeData.data;
mimeType = schemeData.mimeType;
licenseServerUrl = schemeData.licenseServerUrl;
}
try { try {
KeyRequest mediaDrmKeyRequest = currentKeyRequest =
mediaDrm.getKeyRequest(scope, initData, mimeType, type, optionalKeyRequestParameters); mediaDrm.getKeyRequest(scope, schemeDatas, type, optionalKeyRequestParameters);
currentKeyRequest = Pair.create(mediaDrmKeyRequest, licenseServerUrl);
postRequestHandler.post(MSG_KEYS, currentKeyRequest, allowRetry); postRequestHandler.post(MSG_KEYS, currentKeyRequest, allowRetry);
} catch (Exception e) { } catch (Exception e) {
onKeysError(e); onKeysError(e);
...@@ -510,10 +502,7 @@ import java.util.UUID; ...@@ -510,10 +502,7 @@ import java.util.UUID;
response = callback.executeProvisionRequest(uuid, (ProvisionRequest) request); response = callback.executeProvisionRequest(uuid, (ProvisionRequest) request);
break; break;
case MSG_KEYS: case MSG_KEYS:
Pair<KeyRequest, String> keyRequest = (Pair<KeyRequest, String>) request; response = callback.executeKeyRequest(uuid, (KeyRequest) request);
KeyRequest mediaDrmKeyRequest = keyRequest.first;
String licenseServerUrl = keyRequest.second;
response = callback.executeKeyRequest(uuid, mediaDrmKeyRequest, licenseServerUrl);
break; break;
default: default:
throw new RuntimeException(); throw new RuntimeException();
......
...@@ -29,7 +29,6 @@ import com.google.android.exoplayer2.drm.DefaultDrmSession.ProvisioningManager; ...@@ -29,7 +29,6 @@ import com.google.android.exoplayer2.drm.DefaultDrmSession.ProvisioningManager;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
import com.google.android.exoplayer2.drm.ExoMediaDrm.OnEventListener; import com.google.android.exoplayer2.drm.ExoMediaDrm.OnEventListener;
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.EventDispatcher; import com.google.android.exoplayer2.util.EventDispatcher;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -133,7 +132,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -133,7 +132,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
* *
* @param callback Performs key and provisioning requests. * @param callback Performs key and provisioning requests.
* @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument
* to {@link ExoMediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. * to {@link ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}. May be null.
* @throws UnsupportedDrmException If the specified DRM scheme is not supported. * @throws UnsupportedDrmException If the specified DRM scheme is not supported.
*/ */
public static DefaultDrmSessionManager<FrameworkMediaCrypto> newWidevineInstance( public static DefaultDrmSessionManager<FrameworkMediaCrypto> newWidevineInstance(
...@@ -209,7 +208,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -209,7 +208,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
* @param uuid The UUID of the drm scheme. * @param uuid The UUID of the drm scheme.
* @param callback Performs key and provisioning requests. * @param callback Performs key and provisioning requests.
* @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument
* to {@link ExoMediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. * to {@link ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}. May be null.
* @throws UnsupportedDrmException If the specified DRM scheme is not supported. * @throws UnsupportedDrmException If the specified DRM scheme is not supported.
*/ */
public static DefaultDrmSessionManager<FrameworkMediaCrypto> newFrameworkInstance( public static DefaultDrmSessionManager<FrameworkMediaCrypto> newFrameworkInstance(
...@@ -247,7 +246,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -247,7 +246,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
* @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager. * @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager.
* @param callback Performs key and provisioning requests. * @param callback Performs key and provisioning requests.
* @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument
* to {@link ExoMediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. * to {@link ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}. May be null.
*/ */
public DefaultDrmSessionManager( public DefaultDrmSessionManager(
UUID uuid, UUID uuid,
...@@ -287,7 +286,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -287,7 +286,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
* @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager. * @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager.
* @param callback Performs key and provisioning requests. * @param callback Performs key and provisioning requests.
* @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument
* to {@link ExoMediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. * to {@link ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}. May be null.
* @param multiSession A boolean that specify whether multiple key session support is enabled. * @param multiSession A boolean that specify whether multiple key session support is enabled.
* Default is false. * Default is false.
*/ */
...@@ -337,7 +336,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -337,7 +336,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
* @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager. * @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager.
* @param callback Performs key and provisioning requests. * @param callback Performs key and provisioning requests.
* @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument
* to {@link ExoMediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. * to {@link ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}. May be null.
* @param multiSession A boolean that specify whether multiple key session support is enabled. * @param multiSession A boolean that specify whether multiple key session support is enabled.
* Default is false. * Default is false.
* @param initialDrmRequestRetryCount The number of times to retry for initial provisioning and * @param initialDrmRequestRetryCount The number of times to retry for initial provisioning and
...@@ -475,8 +474,8 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -475,8 +474,8 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
// An offline license can be restored so a session can always be acquired. // An offline license can be restored so a session can always be acquired.
return true; return true;
} }
SchemeData schemeData = getSchemeData(drmInitData, uuid, true); List<SchemeData> schemeDatas = getSchemeDatas(drmInitData, uuid, true);
if (schemeData == null) { if (schemeDatas.isEmpty()) {
if (drmInitData.schemeDataCount == 1 && drmInitData.get(0).matches(C.COMMON_PSSH_UUID)) { if (drmInitData.schemeDataCount == 1 && drmInitData.get(0).matches(C.COMMON_PSSH_UUID)) {
// Assume scheme specific data will be added before the session is opened. // Assume scheme specific data will be added before the session is opened.
Log.w( Log.w(
...@@ -510,10 +509,10 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -510,10 +509,10 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
} }
} }
SchemeData schemeData = null; List<SchemeData> schemeDatas = null;
if (offlineLicenseKeySetId == null) { if (offlineLicenseKeySetId == null) {
schemeData = getSchemeData(drmInitData, uuid, false); schemeDatas = getSchemeDatas(drmInitData, uuid, false);
if (schemeData == null) { if (schemeDatas.isEmpty()) {
final MissingSchemeDataException error = new MissingSchemeDataException(uuid); final MissingSchemeDataException error = new MissingSchemeDataException(uuid);
eventDispatcher.dispatch(listener -> listener.onDrmSessionManagerError(error)); eventDispatcher.dispatch(listener -> listener.onDrmSessionManagerError(error));
return new ErrorStateDrmSession<>(new DrmSessionException(error)); return new ErrorStateDrmSession<>(new DrmSessionException(error));
...@@ -526,9 +525,8 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -526,9 +525,8 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
} else { } else {
// Only use an existing session if it has matching init data. // Only use an existing session if it has matching init data.
session = null; session = null;
byte[] initData = schemeData != null ? schemeData.data : null;
for (DefaultDrmSession<T> existingSession : sessions) { for (DefaultDrmSession<T> existingSession : sessions) {
if (existingSession.hasInitData(initData)) { if (Util.areEqual(existingSession.schemeDatas, schemeDatas)) {
session = existingSession; session = existingSession;
break; break;
} }
...@@ -542,7 +540,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -542,7 +540,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
uuid, uuid,
mediaDrm, mediaDrm,
this, this,
schemeData, schemeDatas,
mode, mode,
offlineLicenseKeySetId, offlineLicenseKeySetId,
optionalKeyRequestParameters, optionalKeyRequestParameters,
...@@ -605,16 +603,17 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -605,16 +603,17 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
// Internal methods. // Internal methods.
/** /**
* Extracts {@link SchemeData} suitable for the given DRM scheme {@link UUID}. * Extracts {@link SchemeData} instances suitable for the given DRM scheme {@link UUID}.
* *
* @param drmInitData The {@link DrmInitData} from which to extract the {@link SchemeData}. * @param drmInitData The {@link DrmInitData} from which to extract the {@link SchemeData}.
* @param uuid The UUID. * @param uuid The UUID.
* @param allowMissingData Whether a {@link SchemeData} with null {@link SchemeData#data} may be * @param allowMissingData Whether a {@link SchemeData} with null {@link SchemeData#data} may be
* returned. * returned.
* @return The extracted {@link SchemeData}, or null if no suitable data is present. * @return The extracted {@link SchemeData} instances, or an empty list if no suitable data is
* present.
*/ */
private static SchemeData getSchemeData(DrmInitData drmInitData, UUID uuid, private static List<SchemeData> getSchemeDatas(
boolean allowMissingData) { DrmInitData drmInitData, UUID uuid, boolean allowMissingData) {
// Look for matching scheme data (matching the Common PSSH box for ClearKey). // Look for matching scheme data (matching the Common PSSH box for ClearKey).
List<SchemeData> matchingSchemeDatas = new ArrayList<>(drmInitData.schemeDataCount); List<SchemeData> matchingSchemeDatas = new ArrayList<>(drmInitData.schemeDataCount);
for (int i = 0; i < drmInitData.schemeDataCount; i++) { for (int i = 0; i < drmInitData.schemeDataCount; i++) {
...@@ -625,27 +624,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe ...@@ -625,27 +624,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
matchingSchemeDatas.add(schemeData); matchingSchemeDatas.add(schemeData);
} }
} }
return matchingSchemeDatas;
if (matchingSchemeDatas.isEmpty()) {
return null;
}
// For Widevine PSSH boxes, prefer V1 boxes from API 23 and V0 before.
if (C.WIDEVINE_UUID.equals(uuid)) {
for (int i = 0; i < matchingSchemeDatas.size(); i++) {
SchemeData matchingSchemeData = matchingSchemeDatas.get(i);
int version = matchingSchemeData.hasData()
? PsshAtomUtil.parseVersion(matchingSchemeData.data) : -1;
if (Util.SDK_INT < 23 && version == 0) {
return matchingSchemeData;
} else if (Util.SDK_INT >= 23 && version == 1) {
return matchingSchemeData;
}
}
}
// If we don't have any special handling, prefer the first matching scheme data.
return matchingSchemeDatas.get(0);
} }
@SuppressLint("HandlerLeak") @SuppressLint("HandlerLeak")
......
...@@ -22,6 +22,7 @@ import android.media.MediaDrmException; ...@@ -22,6 +22,7 @@ import android.media.MediaDrmException;
import android.media.NotProvisionedException; import android.media.NotProvisionedException;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -130,19 +131,19 @@ public interface ExoMediaDrm<T extends ExoMediaCrypto> { ...@@ -130,19 +131,19 @@ public interface ExoMediaDrm<T extends ExoMediaCrypto> {
final class KeyRequest { final class KeyRequest {
private final byte[] data; private final byte[] data;
private final String defaultUrl; private final String licenseServerUrl;
public KeyRequest(byte[] data, String defaultUrl) { public KeyRequest(byte[] data, String licenseServerUrl) {
this.data = data; this.data = data;
this.defaultUrl = defaultUrl; this.licenseServerUrl = licenseServerUrl;
} }
public byte[] getData() { public byte[] getData() {
return data; return data;
} }
public String getDefaultUrl() { public String getLicenseServerUrl() {
return defaultUrl; return licenseServerUrl;
} }
} }
...@@ -188,13 +189,29 @@ public interface ExoMediaDrm<T extends ExoMediaCrypto> { ...@@ -188,13 +189,29 @@ public interface ExoMediaDrm<T extends ExoMediaCrypto> {
*/ */
void closeSession(byte[] sessionId); void closeSession(byte[] sessionId);
/** @see MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap) */ /**
* Generates a key request.
*
* @param scope If {@code keyType} is {@link #KEY_TYPE_STREAMING} or {@link #KEY_TYPE_OFFLINE},
* the session id that the keys will be provided to. If {@code keyType} is {@link
* #KEY_TYPE_RELEASE}, the keySetId of the keys to release.
* @param schemeDatas If key type is {@link #KEY_TYPE_STREAMING} or {@link #KEY_TYPE_OFFLINE}, a
* list of {@link SchemeData} instances extracted from the media. Null otherwise.
* @param keyType The type of the request. Either {@link #KEY_TYPE_STREAMING} to acquire keys for
* streaming, {@link #KEY_TYPE_OFFLINE} to acquire keys for offline usage, or {@link
* #KEY_TYPE_RELEASE} to release acquired keys. Releasing keys invalidates them for all
* sessions.
* @param optionalParameters Are included in the key request message to allow a client application
* to provide additional message parameters to the server. This may be {@code null} if no
* additional parameters are to be sent.
* @return The generated key request.
* @see MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)
*/
KeyRequest getKeyRequest( KeyRequest getKeyRequest(
byte[] scope, byte[] scope,
byte[] init, @Nullable List<SchemeData> schemeDatas,
String mimeType,
int keyType, int keyType,
HashMap<String, String> optionalParameters) @Nullable HashMap<String, String> optionalParameters)
throws NotProvisionedException; throws NotProvisionedException;
/** @see MediaDrm#provideKeyResponse(byte[], byte[]) */ /** @see MediaDrm#provideKeyResponse(byte[], byte[]) */
......
...@@ -24,7 +24,10 @@ import android.media.MediaDrm; ...@@ -24,7 +24,10 @@ import android.media.MediaDrm;
import android.media.MediaDrmException; import android.media.MediaDrmException;
import android.media.NotProvisionedException; import android.media.NotProvisionedException;
import android.media.UnsupportedSchemeException; import android.media.UnsupportedSchemeException;
import android.support.annotation.Nullable;
import android.text.TextUtils;
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.extractor.mp4.PsshAtomUtil; 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.MimeTypes;
...@@ -119,44 +122,26 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto ...@@ -119,44 +122,26 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
@Override @Override
public KeyRequest getKeyRequest( public KeyRequest getKeyRequest(
byte[] scope, byte[] scope,
byte[] init, @Nullable List<DrmInitData.SchemeData> schemeDatas,
String mimeType,
int keyType, int keyType,
HashMap<String, String> optionalParameters) HashMap<String, String> optionalParameters)
throws NotProvisionedException { throws NotProvisionedException {
SchemeData schemeData = getSchemeData(uuid, schemeDatas);
// Prior to L the Widevine CDM required data to be extracted from the PSSH atom. Some Amazon byte[] initData = adjustRequestInitData(uuid, schemeData.data);
// devices also required data to be extracted from the PSSH atom for PlayReady. String mimeType = adjustRequestMimeType(uuid, schemeData.mimeType);
if ((Util.SDK_INT < 21 && C.WIDEVINE_UUID.equals(uuid))
|| (C.PLAYREADY_UUID.equals(uuid)
&& "Amazon".equals(Util.MANUFACTURER)
&& ("AFTB".equals(Util.MODEL) // Fire TV Gen 1
|| "AFTS".equals(Util.MODEL) // Fire TV Gen 2
|| "AFTM".equals(Util.MODEL)))) { // Fire TV Stick Gen 1
byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(init, uuid);
if (psshData == null) {
// Extraction failed. schemeData isn't a PSSH atom, so leave it unchanged.
} else {
init = psshData;
}
}
// Prior to API level 26 the ClearKey CDM only accepted "cenc" as the scheme for MP4. MediaDrm.KeyRequest request =
if (Util.SDK_INT < 26 mediaDrm.getKeyRequest(scope, initData, mimeType, keyType, optionalParameters);
&& C.CLEARKEY_UUID.equals(uuid)
&& (MimeTypes.VIDEO_MP4.equals(mimeType) || MimeTypes.AUDIO_MP4.equals(mimeType))) {
mimeType = CENC_SCHEME_MIME_TYPE;
}
final MediaDrm.KeyRequest request = mediaDrm.getKeyRequest(scope, init, mimeType, keyType, byte[] requestData = adjustRequestData(uuid, request.getData());
optionalParameters);
byte[] requestData = request.getData(); String licenseServerUrl = request.getDefaultUrl();
if (C.CLEARKEY_UUID.equals(uuid)) { if (TextUtils.isEmpty(licenseServerUrl) && !TextUtils.isEmpty(schemeData.licenseServerUrl)) {
requestData = ClearKeyUtil.adjustRequestData(requestData); licenseServerUrl = schemeData.licenseServerUrl;
} }
return new KeyRequest(requestData, request.getDefaultUrl()); return new KeyRequest(requestData, licenseServerUrl);
} }
@Override @Override
...@@ -226,6 +211,94 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto ...@@ -226,6 +211,94 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
forceAllowInsecureDecoderComponents); forceAllowInsecureDecoderComponents);
} }
private static SchemeData getSchemeData(UUID uuid, List<SchemeData> schemeDatas) {
if (!C.WIDEVINE_UUID.equals(uuid)) {
// For non-Widevine CDMs always use the first scheme data.
return schemeDatas.get(0);
}
if (Util.SDK_INT >= 28 && schemeDatas.size() > 1) {
// For API level 28 and above, concatenate multiple PSSH scheme datas if possible.
SchemeData firstSchemeData = schemeDatas.get(0);
int concatenatedDataLength = 0;
boolean canConcatenateData = true;
for (int i = 0; i < schemeDatas.size(); i++) {
SchemeData schemeData = schemeDatas.get(i);
if (schemeData.requiresSecureDecryption == firstSchemeData.requiresSecureDecryption
&& Util.areEqual(schemeData.mimeType, firstSchemeData.mimeType)
&& Util.areEqual(schemeData.licenseServerUrl, firstSchemeData.licenseServerUrl)
&& PsshAtomUtil.isPsshAtom(schemeData.data)) {
concatenatedDataLength += schemeData.data.length;
} else {
canConcatenateData = false;
break;
}
}
if (canConcatenateData) {
byte[] concatenatedData = new byte[concatenatedDataLength];
int concatenatedDataPosition = 0;
for (int i = 0; i < schemeDatas.size(); i++) {
SchemeData schemeData = schemeDatas.get(i);
int schemeDataLength = schemeData.data.length;
System.arraycopy(
schemeData.data, 0, concatenatedData, concatenatedDataPosition, schemeDataLength);
concatenatedDataPosition += schemeDataLength;
}
return firstSchemeData.copyWithData(concatenatedData);
}
}
// For API levels 23 - 27, prefer the first V1 PSSH box. For API levels 22 and earlier, prefer
// the first V0 box.
for (int i = 0; i < schemeDatas.size(); i++) {
SchemeData schemeData = schemeDatas.get(i);
int version = PsshAtomUtil.parseVersion(schemeData.data);
if (Util.SDK_INT < 23 && version == 0) {
return schemeData;
} else if (Util.SDK_INT >= 23 && version == 1) {
return schemeData;
}
}
// If all else fails, use the first scheme data.
return schemeDatas.get(0);
}
private static byte[] adjustRequestInitData(UUID uuid, byte[] initData) {
// Prior to L the Widevine CDM required data to be extracted from the PSSH atom. Some Amazon
// devices also required data to be extracted from the PSSH atom for PlayReady.
if ((Util.SDK_INT < 21 && C.WIDEVINE_UUID.equals(uuid))
|| (C.PLAYREADY_UUID.equals(uuid)
&& "Amazon".equals(Util.MANUFACTURER)
&& ("AFTB".equals(Util.MODEL) // Fire TV Gen 1
|| "AFTS".equals(Util.MODEL) // Fire TV Gen 2
|| "AFTM".equals(Util.MODEL)))) { // Fire TV Stick Gen 1
byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(initData, uuid);
if (psshData != null) {
// Extraction succeeded, so return the extracted data.
return psshData;
}
}
return initData;
}
private static String adjustRequestMimeType(UUID uuid, String mimeType) {
// Prior to API level 26 the ClearKey CDM only accepted "cenc" as the scheme for MP4.
if (Util.SDK_INT < 26
&& C.CLEARKEY_UUID.equals(uuid)
&& (MimeTypes.VIDEO_MP4.equals(mimeType) || MimeTypes.AUDIO_MP4.equals(mimeType))) {
return CENC_SCHEME_MIME_TYPE;
}
return mimeType;
}
private static byte[] adjustRequestData(UUID uuid, byte[] requestData) {
if (C.CLEARKEY_UUID.equals(uuid)) {
return ClearKeyUtil.adjustRequestData(requestData);
}
return requestData;
}
@SuppressLint("WrongConstant") // Suppress spurious lint error [Internal ref: b/32137960] @SuppressLint("WrongConstant") // Suppress spurious lint error [Internal ref: b/32137960]
private static void forceWidevineL3(MediaDrm mediaDrm) { private static void forceWidevineL3(MediaDrm mediaDrm) {
mediaDrm.setPropertyString("securityLevel", "L3"); mediaDrm.setPropertyString("securityLevel", "L3");
......
...@@ -17,7 +17,6 @@ package com.google.android.exoplayer2.drm; ...@@ -17,7 +17,6 @@ package com.google.android.exoplayer2.drm;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest; import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest;
...@@ -115,13 +114,8 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { ...@@ -115,13 +114,8 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
} }
@Override @Override
public byte[] executeKeyRequest( public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception {
UUID uuid, KeyRequest request, @Nullable String mediaProvidedLicenseServerUrl) String url = request.getLicenseServerUrl();
throws Exception {
String url = request.getDefaultUrl();
if (TextUtils.isEmpty(url)) {
url = mediaProvidedLicenseServerUrl;
}
if (forceDefaultLicenseUrl || TextUtils.isEmpty(url)) { if (forceDefaultLicenseUrl || TextUtils.isEmpty(url)) {
url = defaultLicenseUrl; url = defaultLicenseUrl;
} }
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.drm; package com.google.android.exoplayer2.drm;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest; import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest;
import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
...@@ -45,9 +44,7 @@ public final class LocalMediaDrmCallback implements MediaDrmCallback { ...@@ -45,9 +44,7 @@ public final class LocalMediaDrmCallback implements MediaDrmCallback {
} }
@Override @Override
public byte[] executeKeyRequest( public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception {
UUID uuid, KeyRequest request, @Nullable String mediaProvidedLicenseServerUrl)
throws Exception {
return keyResponse; return keyResponse;
} }
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.drm; package com.google.android.exoplayer2.drm;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest; import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest;
import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest;
import java.util.UUID; import java.util.UUID;
...@@ -39,13 +38,9 @@ public interface MediaDrmCallback { ...@@ -39,13 +38,9 @@ public interface MediaDrmCallback {
* Executes a key request. * Executes a key request.
* *
* @param uuid The UUID of the content protection scheme. * @param uuid The UUID of the content protection scheme.
* @param request The request generated by the content decryption module. * @param request The request.
* @param mediaProvidedLicenseServerUrl A license server URL provided by the media, or null if the
* media does not include any license server URL.
* @return The response data. * @return The response data.
* @throws Exception If an error occurred executing the request. * @throws Exception If an error occurred executing the request.
*/ */
byte[] executeKeyRequest( byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception;
UUID uuid, KeyRequest request, @Nullable String mediaProvidedLicenseServerUrl)
throws Exception;
} }
...@@ -78,6 +78,16 @@ public final class PsshAtomUtil { ...@@ -78,6 +78,16 @@ public final class PsshAtomUtil {
} }
/** /**
* Returns whether the data is a valid PSSH atom.
*
* @param data The data to parse.
* @return Whether the data is a valid PSSH atom.
*/
public static boolean isPsshAtom(byte[] data) {
return parsePsshAtom(data) != null;
}
/**
* Parses the UUID from a PSSH atom. Version 0 and 1 PSSH atoms are supported. * Parses the UUID from a PSSH atom. Version 0 and 1 PSSH atoms are supported.
* *
* <p>The UUID is only parsed if the data is a valid PSSH atom. * <p>The UUID is only parsed if the data is a valid PSSH atom.
......
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