Skip to content

Commit

Permalink
Implement a best-effort DRM session acquisition approach
Browse files Browse the repository at this point in the history
Try to delay failure for as long as possible. That is, propagate
DRM session failures only after an encrypted buffer arrives or clear
sample playback without session is not allowed.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=183076348
  • Loading branch information
AquilesCanta authored and ojw28 committed Jan 24, 2018
1 parent 91cacc2 commit 8716c0e
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 64 deletions.
4 changes: 3 additions & 1 deletion RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
positions.
* Note: `SeekParameters` are not currently supported when playing HLS streams.
* DRM: Optimistically attempt playback of DRM protected content that does not
declare scheme specific init data
declare scheme specific init data in the manifest. If playback of clear
samples without keys is allowed, delay DRM session error propagation until
keys are actually needed
([#3630](https://github.com/google/ExoPlayer/issues/3630)).
* DASH:
* Support in-band Emsg events targeting player with scheme id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -647,10 +647,12 @@ private void maybeInitDecoder() throws ExoPlaybackException {
if (mediaCrypto == null) {
DrmSessionException drmError = drmSession.getError();
if (drmError != null) {
throw ExoPlaybackException.createForRenderer(drmError, getIndex());
// Continue for now. We may be able to avoid failure if the session recovers, or if a new
// input format causes the session to be replaced before it's used.
} else {
// The drm session isn't open yet.
return;
}
// The drm session isn't open yet.
return;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
Expand Down Expand Up @@ -502,8 +501,8 @@ public Format copyWithManifestFormatInfo(Format manifestFormat) {
float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate;
@C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags;
String language = this.language == null ? manifestFormat.language : this.language;
DrmInitData drmInitData = manifestFormat.drmInitData != null
? getFilledManifestDrmData(manifestFormat.drmInitData) : this.drmInitData;
DrmInitData drmInitData =
DrmInitData.createSessionCreationData(manifestFormat.drmInitData, this.drmInitData);
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode,
colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
Expand Down Expand Up @@ -768,43 +767,4 @@ public Format[] newArray(int size) {
}

};

private DrmInitData getFilledManifestDrmData(DrmInitData manifestDrmData) {
// All exposed SchemeDatas must include key request information.
ArrayList<SchemeData> exposedSchemeDatas = new ArrayList<>();
ArrayList<SchemeData> emptySchemeDatas = new ArrayList<>();
for (int i = 0; i < manifestDrmData.schemeDataCount; i++) {
SchemeData schemeData = manifestDrmData.get(i);
if (schemeData.hasData()) {
exposedSchemeDatas.add(schemeData);
} else /* needs initialization data filling */ {
emptySchemeDatas.add(schemeData);
}
}

if (emptySchemeDatas.isEmpty()) {
// Manifest DRM information is complete.
return manifestDrmData;
} else if (drmInitData == null) {
// The manifest DRM data needs filling but this format does not include enough information to
// do it. A subset of the manifest's scheme datas should not be exposed because a
// DrmSessionManager could decide it does not support the format, while the missing
// information comes in a format feed immediately after.
return null;
}

int needFillingCount = emptySchemeDatas.size();
for (int i = 0; i < drmInitData.schemeDataCount; i++) {
SchemeData mediaSchemeData = drmInitData.get(i);
for (int j = 0; j < needFillingCount; j++) {
if (mediaSchemeData.canReplace(emptySchemeDatas.get(j))) {
exposedSchemeDatas.add(mediaSchemeData);
break;
}
}
}
return exposedSchemeDatas.isEmpty() ? null : new DrmInitData(manifestDrmData.schemeType,
exposedSchemeDatas.toArray(new SchemeData[exposedSchemeDatas.size()]));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -566,10 +566,12 @@ private void maybeInitDecoder() throws ExoPlaybackException {
if (mediaCrypto == null) {
DrmSessionException drmError = drmSession.getError();
if (drmError != null) {
throw ExoPlaybackException.createForRenderer(drmError, getIndex());
// Continue for now. We may be able to avoid failure if the session recovers, or if a new
// input format causes the session to be replaced before it's used.
} else {
// The drm session isn't open yet.
return;
}
// The drm session isn't open yet.
return;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.drm.DefaultDrmSession.ProvisioningManager;
Expand Down Expand Up @@ -83,6 +84,17 @@ public interface EventListener {

}

/**
* Signals that the {@link DrmInitData} passed to {@link #acquireSession} does not contain does
* not contain scheme data for the required UUID.
*/
public static final class MissingSchemeDataException extends Exception {

private MissingSchemeDataException(UUID uuid) {
super("Media does not support uuid: " + uuid);
}
}

/**
* The key to use when passing CustomData to a PlayReady instance in an optional parameter map.
*/
Expand All @@ -108,6 +120,7 @@ public interface EventListener {
/** Number of times to retry for initial provisioning and key request for reporting error. */
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;
Expand Down Expand Up @@ -351,8 +364,14 @@ public void setMode(@Mode int mode, byte[] offlineLicenseKeySetId) {
public boolean canAcquireSession(@NonNull DrmInitData drmInitData) {
SchemeData schemeData = getSchemeData(drmInitData, uuid, true);
if (schemeData == null) {
// No data for this manager's scheme.
return false;
if (drmInitData.schemeDataCount == 1 && drmInitData.get(0).matches(C.COMMON_PSSH_UUID)) {
// Assume scheme specific data will be added before the session is opened.
Log.w(
TAG, "DrmInitData only contains common PSSH SchemeData. Assuming support for: " + uuid);
} else {
// No data for this manager's scheme.
return false;
}
}
String schemeType = drmInitData.schemeType;
if (schemeType == null || C.CENC_TYPE_cenc.equals(schemeType)) {
Expand Down Expand Up @@ -382,15 +401,15 @@ public DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitDa
if (offlineLicenseKeySetId == null) {
SchemeData data = getSchemeData(drmInitData, uuid, false);
if (data == null) {
final IllegalStateException error = new IllegalStateException(
"Media does not support uuid: " + uuid);
final MissingSchemeDataException error = new MissingSchemeDataException(uuid);
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDrmSessionManagerError(error);
}
});
eventHandler.post(
new Runnable() {
@Override
public void run() {
eventListener.onDrmSessionManagerError(error);
}
});
}
return new ErrorStateDrmSession<>(new DrmSessionException(error));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
Expand All @@ -32,6 +33,58 @@
*/
public final class DrmInitData implements Comparator<SchemeData>, Parcelable {

/**
* Merges {@link DrmInitData} obtained from a media manifest and a media stream.
*
* <p>The result is generated as follows.
*
* <ol>
* <ol>
* Include all {@link SchemeData}s from {@code manifestData} where {@link
* SchemeData#hasData()} is true.
* </ol>
* <ol>
* Include all {@link SchemeData}s in {@code mediaData} where {@link SchemeData#hasData()} is
* true and for which we did not include an entry from the manifest targeting the same UUID.
* </ol>
* <ol>
* If available, the scheme type from the manifest is used. If not, the scheme type from the
* media is used.
* </ol>
* </ol>
*
* @param manifestData DRM session acquisition data obtained from the manifest.
* @param mediaData DRM session acquisition data obtained from the media.
* @return A {@link DrmInitData} obtained from merging a media manifest and a media stream.
*/
public static @Nullable DrmInitData createSessionCreationData(
@Nullable DrmInitData manifestData, @Nullable DrmInitData mediaData) {
ArrayList<SchemeData> result = new ArrayList<>();
String schemeType = null;
if (manifestData != null) {
schemeType = manifestData.schemeType;
for (SchemeData data : manifestData.schemeDatas) {
if (data.hasData()) {
result.add(data);
}
}
}

if (mediaData != null) {
if (schemeType == null) {
schemeType = mediaData.schemeType;
}
int manifestDatasCount = result.size();
for (SchemeData data : mediaData.schemeDatas) {
if (data.hasData() && !containsSchemeDataWithUuid(result, manifestDatasCount, data.uuid)) {
result.add(data);
}
}
}

return result.isEmpty() ? null : new DrmInitData(schemeType, result);
}

private final SchemeData[] schemeDatas;

// Lazily initialized hashcode.
Expand Down Expand Up @@ -193,6 +246,18 @@ public DrmInitData[] newArray(int size) {

};

// Internal methods.

private static boolean containsSchemeDataWithUuid(
ArrayList<SchemeData> datas, int limit, UUID uuid) {
for (int i = 0; i < limit; i++) {
if (datas.get(i).uuid.equals(uuid)) {
return true;
}
}
return false;
}

/**
* Scheme initialization data.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,13 +338,16 @@ protected final void maybeInitCodec() throws ExoPlaybackException {
if (mediaCrypto == null) {
DrmSessionException drmError = drmSession.getError();
if (drmError != null) {
throw ExoPlaybackException.createForRenderer(drmError, getIndex());
// Continue for now. We may be able to avoid failure if the session recovers, or if a new
// input format causes the session to be replaced before it's used.
} else {
// The drm session isn't open yet.
return;
}
// The drm session isn't open yet.
return;
} else {
wrappedMediaCrypto = mediaCrypto.getWrappedMediaCrypto();
drmSessionRequiresSecureDecoder = mediaCrypto.requiresSecureDecoderComponent(mimeType);
}
wrappedMediaCrypto = mediaCrypto.getWrappedMediaCrypto();
drmSessionRequiresSecureDecoder = mediaCrypto.requiresSecureDecoderComponent(mimeType);
}

if (codecInfo == null) {
Expand Down

0 comments on commit 8716c0e

Please sign in to comment.