提交 8716c0ee 编写于 作者: A aquilescanta 提交者: Oliver Woodman

Implement a best-effort DRM session acquisition approach

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
上级 91cacc29
......@@ -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
......
......@@ -647,10 +647,12 @@ public class LibvpxVideoRenderer extends BaseRenderer {
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;
}
}
......
......@@ -21,7 +21,6 @@ import android.media.MediaFormat;
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;
......@@ -502,8 +501,8 @@ public final class Format implements Parcelable {
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,
......@@ -768,43 +767,4 @@ public final class Format implements Parcelable {
}
};
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()]));
}
}
......@@ -566,10 +566,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
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;
}
}
......
......@@ -23,6 +23,7 @@ import android.os.Message;
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;
......@@ -83,6 +84,17 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
}
/**
* 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.
*/
......@@ -108,6 +120,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
/** 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;
......@@ -351,8 +364,14 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
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)) {
......@@ -382,15 +401,15 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
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));
}
......
......@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C;
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;
......@@ -32,6 +33,58 @@ import java.util.UUID;
*/
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.
......@@ -193,6 +246,18 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
};
// 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.
*/
......
......@@ -338,13 +338,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
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) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册