Skip to content

Commit

Permalink
Add license server URL to SchemeData
Browse files Browse the repository at this point in the history
Allows DrmInitData to carry a license server URL when the media declares one.

Issue:#3393

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=199643743
  • Loading branch information
AquilesCanta authored and ojw28 committed Jun 18, 2018
1 parent 48193e2 commit 4bbc93b
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 98 deletions.
3 changes: 3 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
bandwidth estimates in the future. Always null at the moment.
* HLS:
* Allow injection of custom playlist trackers.
* DRM:
* Allow DrmInitData to carry a license server URL
([#3393](https://github.com/google/ExoPlayer/issues/3393)).
* Add method to `BandwidthMeter` to return the `TransferListener` used to gather
bandwidth information.
* Add callback to `VideoListener` to notify of surface size changes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.Nullable;
import android.util.Log;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DefaultDrmSessionEventListener.EventDispatcher;
import com.google.android.exoplayer2.drm.ExoMediaDrm.DefaultKeyRequest;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest;
import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest;
import java.util.Arrays;
Expand Down Expand Up @@ -77,8 +78,7 @@ public interface ProvisioningManager<T extends ExoMediaCrypto> {

private final ExoMediaDrm<T> mediaDrm;
private final ProvisioningManager<T> provisioningManager;
private final byte[] initData;
private final String mimeType;
private final SchemeData schemeData;
private final @DefaultDrmSessionManager.Mode int mode;
private final HashMap<String, String> optionalKeyRequestParameters;
private final EventDispatcher eventDispatcher;
Expand All @@ -103,9 +103,11 @@ public interface ProvisioningManager<T extends ExoMediaCrypto> {
* @param uuid The UUID of the drm scheme.
* @param mediaDrm The media DRM.
* @param provisioningManager The manager for provisioning.
* @param initData The DRM init data.
* @param schemeData The DRM data for this session, or null if a {@code offlineLicenseKeySetId} is
* provided.
* @param mode The DRM mode.
* @param offlineLicenseKeySetId The offlineLicense KeySetId.
* @param offlineLicenseKeySetId The offline license key set identifier, or null when not using
* offline keys.
* @param optionalKeyRequestParameters The optional key request parameters.
* @param callback The media DRM callback.
* @param playbackLooper The playback looper.
Expand All @@ -117,10 +119,9 @@ public DefaultDrmSession(
UUID uuid,
ExoMediaDrm<T> mediaDrm,
ProvisioningManager<T> provisioningManager,
byte[] initData,
String mimeType,
@Nullable SchemeData schemeData,
@DefaultDrmSessionManager.Mode int mode,
byte[] offlineLicenseKeySetId,
@Nullable byte[] offlineLicenseKeySetId,
HashMap<String, String> optionalKeyRequestParameters,
MediaDrmCallback callback,
Looper playbackLooper,
Expand All @@ -131,6 +132,7 @@ public DefaultDrmSession(
this.mediaDrm = mediaDrm;
this.mode = mode;
this.offlineLicenseKeySetId = offlineLicenseKeySetId;
this.schemeData = offlineLicenseKeySetId == null ? schemeData : null;
this.optionalKeyRequestParameters = optionalKeyRequestParameters;
this.callback = callback;
this.initialDrmRequestRetryCount = initialDrmRequestRetryCount;
Expand All @@ -141,14 +143,6 @@ public DefaultDrmSession(
requestHandlerThread = new HandlerThread("DrmRequestHandler");
requestHandlerThread.start();
postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper());

if (offlineLicenseKeySetId == null) {
this.initData = initData;
this.mimeType = mimeType;
} else {
this.initData = null;
this.mimeType = null;
}
}

// Life cycle.
Expand Down Expand Up @@ -187,13 +181,37 @@ public boolean release() {
}

public boolean hasInitData(byte[] initData) {
return Arrays.equals(this.initData, initData);
return Arrays.equals(schemeData != null ? schemeData.data : null, initData);
}

public boolean hasSessionId(byte[] sessionId) {
return Arrays.equals(this.sessionId, sessionId);
}

@SuppressWarnings("deprecation")
public void onMediaDrmEvent(int what) {
if (!isOpen()) {
return;
}
switch (what) {
case ExoMediaDrm.EVENT_KEY_REQUIRED:
doLicense(false);
break;
case ExoMediaDrm.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 ExoMediaDrm.EVENT_PROVISION_REQUIRED:
state = STATE_OPENED;
provisioningManager.provisionRequired(this);
break;
default:
break;
}
}

// Provisioning implementation.

public void provision() {
Expand Down Expand Up @@ -356,14 +374,19 @@ private long getLicenseDurationRemainingSec() {

private void postKeyRequest(int type, boolean allowRetry) {
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 {
KeyRequest request = mediaDrm.getKeyRequest(scope, initData, mimeType, type,
optionalKeyRequestParameters);
if (C.CLEARKEY_UUID.equals(uuid)) {
request = new DefaultKeyRequest(ClearKeyUtil.adjustRequestData(request.getData()),
request.getDefaultUrl());
}
postRequestHandler.obtainMessage(MSG_KEYS, request, allowRetry).sendToTarget();
KeyRequest request =
mediaDrm.getKeyRequest(scope, initData, mimeType, type, optionalKeyRequestParameters);
Pair<KeyRequest, String> arguments = Pair.create(request, licenseServerUrl);
postRequestHandler.obtainMessage(MSG_KEYS, arguments, allowRetry).sendToTarget();
} catch (Exception e) {
onKeysError(e);
}
Expand All @@ -382,9 +405,6 @@ private void onKeyResponse(Object response) {

try {
byte[] responseData = (byte[]) response;
if (C.CLEARKEY_UUID.equals(uuid)) {
responseData = ClearKeyUtil.adjustResponseData(responseData);
}
if (mode == DefaultDrmSessionManager.MODE_RELEASE) {
mediaDrm.provideKeyResponse(offlineLicenseKeySetId, responseData);
eventDispatcher.drmKeysRemoved();
Expand Down Expand Up @@ -430,30 +450,7 @@ private boolean isOpen() {
return state == STATE_OPENED || state == STATE_OPENED_WITH_KEYS;
}

@SuppressWarnings("deprecation")
public void onMediaDrmEvent(int what) {
if (!isOpen()) {
return;
}
switch (what) {
case ExoMediaDrm.EVENT_KEY_REQUIRED:
doLicense(false);
break;
case ExoMediaDrm.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 ExoMediaDrm.EVENT_PROVISION_REQUIRED:
state = STATE_OPENED;
provisioningManager.provisionRequired(this);
break;
default:
break;
}

}
// Internal classes.

@SuppressLint("HandlerLeak")
private class PostResponseHandler extends Handler {
Expand Down Expand Up @@ -492,6 +489,7 @@ Message obtainMessage(int what, Object object, boolean allowRetry) {
}

@Override
@SuppressWarnings("unchecked")
public void handleMessage(Message msg) {
Object response;
try {
Expand All @@ -500,7 +498,8 @@ public void handleMessage(Message msg) {
response = callback.executeProvisionRequest(uuid, (ProvisionRequest) msg.obj);
break;
case MSG_KEYS:
response = callback.executeKeyRequest(uuid, (KeyRequest) msg.obj);
Pair<KeyRequest, String> arguments = (Pair<KeyRequest, String>) msg.obj;
response = callback.executeKeyRequest(uuid, arguments.first, arguments.second);
break;
default:
throw new RuntimeException();
Expand Down Expand Up @@ -534,5 +533,4 @@ private long getRetryDelayMillis(int errorCount) {
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
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.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
Expand Down Expand Up @@ -89,7 +88,6 @@ private MissingSchemeDataException(UUID uuid) {
public static final int INITIAL_DRM_REQUEST_RETRY_COUNT = 3;

private static final String TAG = "DefaultDrmSessionMgr";
private static final String CENC_SCHEME_MIME_TYPE = "cenc";

private final UUID uuid;
private final ExoMediaDrm<T> mediaDrm;
Expand Down Expand Up @@ -509,17 +507,14 @@ public DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitDa
}
}

byte[] initData = null;
String mimeType = null;
SchemeData schemeData = null;
if (offlineLicenseKeySetId == null) {
SchemeData data = getSchemeData(drmInitData, uuid, false);
if (data == null) {
schemeData = getSchemeData(drmInitData, uuid, false);
if (schemeData == null) {
final MissingSchemeDataException error = new MissingSchemeDataException(uuid);
eventDispatcher.drmSessionManagerError(error);
return new ErrorStateDrmSession<>(new DrmSessionException(error));
}
initData = getSchemeInitData(data, uuid);
mimeType = getSchemeMimeType(data, uuid);
}

DefaultDrmSession<T> session;
Expand All @@ -528,6 +523,7 @@ public DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitDa
} else {
// Only use an existing session if it has matching init data.
session = null;
byte[] initData = schemeData != null ? schemeData.data : null;
for (DefaultDrmSession<T> existingSession : sessions) {
if (existingSession.hasInitData(initData)) {
session = existingSession;
Expand All @@ -543,8 +539,7 @@ public DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitDa
uuid,
mediaDrm,
this,
initData,
mimeType,
schemeData,
mode,
offlineLicenseKeySetId,
optionalKeyRequestParameters,
Expand Down Expand Up @@ -650,31 +645,6 @@ private static SchemeData getSchemeData(DrmInitData drmInitData, UUID uuid,
return matchingSchemeDatas.get(0);
}

private static byte[] getSchemeInitData(SchemeData data, UUID uuid) {
byte[] schemeInitData = data.data;
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;
}
}
return schemeInitData;
}

private static String getSchemeMimeType(SchemeData data, UUID uuid) {
String schemeMimeType = data.mimeType;
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;
}
return schemeMimeType;
}

@SuppressLint("HandlerLeak")
private class MediaDrmHandler extends Handler {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,9 @@ public static final class SchemeData implements Parcelable {
* applies to all schemes).
*/
private final UUID uuid;
/**
* The mimeType of {@link #data}.
*/
/** The URL of the server to which license requests should be made. May be null if unknown. */
public final @Nullable String licenseServerUrl;
/** The mimeType of {@link #data}. */
public final String mimeType;
/**
* The initialization data. May be null for scheme support checks only.
Expand Down Expand Up @@ -297,14 +297,33 @@ public SchemeData(UUID uuid, String mimeType, byte[] data) {
* @param requiresSecureDecryption See {@link #requiresSecureDecryption}.
*/
public SchemeData(UUID uuid, String mimeType, byte[] data, boolean requiresSecureDecryption) {
this(uuid, /* licenseServerUrl= */ null, mimeType, data, requiresSecureDecryption);
}

/**
* @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is
* universal (i.e. applies to all schemes).
* @param licenseServerUrl See {@link #licenseServerUrl}.
* @param mimeType See {@link #mimeType}.
* @param data See {@link #data}.
* @param requiresSecureDecryption See {@link #requiresSecureDecryption}.
*/
public SchemeData(
UUID uuid,
@Nullable String licenseServerUrl,
String mimeType,
byte[] data,
boolean requiresSecureDecryption) {
this.uuid = Assertions.checkNotNull(uuid);
this.licenseServerUrl = licenseServerUrl;
this.mimeType = Assertions.checkNotNull(mimeType);
this.data = data;
this.requiresSecureDecryption = requiresSecureDecryption;
}

/* package */ SchemeData(Parcel in) {
uuid = new UUID(in.readLong(), in.readLong());
licenseServerUrl = in.readString();
mimeType = in.readString();
data = in.createByteArray();
requiresSecureDecryption = in.readByte() != 0;
Expand Down Expand Up @@ -346,14 +365,17 @@ public boolean equals(@Nullable Object obj) {
return true;
}
SchemeData other = (SchemeData) obj;
return mimeType.equals(other.mimeType) && Util.areEqual(uuid, other.uuid)
return Util.areEqual(licenseServerUrl, other.licenseServerUrl)
&& Util.areEqual(mimeType, other.mimeType)
&& Util.areEqual(uuid, other.uuid)
&& Arrays.equals(data, other.data);
}

@Override
public int hashCode() {
if (hashCode == 0) {
int result = uuid.hashCode();
result = 31 * result + (licenseServerUrl == null ? 0 : licenseServerUrl.hashCode());
result = 31 * result + mimeType.hashCode();
result = 31 * result + Arrays.hashCode(data);
hashCode = result;
Expand All @@ -372,6 +394,7 @@ public int describeContents() {
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(uuid.getMostSignificantBits());
dest.writeLong(uuid.getLeastSignificantBits());
dest.writeString(licenseServerUrl);
dest.writeString(mimeType);
dest.writeByteArray(data);
dest.writeByte((byte) (requiresSecureDecryption ? 1 : 0));
Expand Down
Loading

0 comments on commit 4bbc93b

Please sign in to comment.