Fill manifest drm info with media files' pssh when needed
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=174185407
This commit is contained in:
parent
46172ffd0f
commit
8c424798c4
@ -21,6 +21,7 @@ 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;
|
||||
@ -464,8 +465,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 ? manifestFormat.drmInitData
|
||||
: this.drmInitData;
|
||||
DrmInitData drmInitData = manifestFormat.drmInitData != null
|
||||
? getFilledManifestDrmData(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,
|
||||
@ -731,4 +732,42 @@ 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()]));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -340,7 +340,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
|
||||
|
||||
@Override
|
||||
public boolean canAcquireSession(@NonNull DrmInitData drmInitData) {
|
||||
SchemeData schemeData = getSchemeData(drmInitData, uuid);
|
||||
SchemeData schemeData = getSchemeData(drmInitData, uuid, true);
|
||||
if (schemeData == null) {
|
||||
// No data for this manager's scheme.
|
||||
return false;
|
||||
@ -371,7 +371,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
|
||||
byte[] initData = null;
|
||||
String mimeType = null;
|
||||
if (offlineLicenseKeySetId == null) {
|
||||
SchemeData data = getSchemeData(drmInitData, uuid);
|
||||
SchemeData data = getSchemeData(drmInitData, uuid, false);
|
||||
if (data == null) {
|
||||
final IllegalStateException error = new IllegalStateException(
|
||||
"Media does not support uuid: " + uuid);
|
||||
@ -467,15 +467,19 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
|
||||
*
|
||||
* @param drmInitData The {@link DrmInitData} from which to extract the {@link SchemeData}.
|
||||
* @param uuid The UUID.
|
||||
* @param allowMissingData Whether a {@link SchemeData} with null {@link SchemeData#data} may be
|
||||
* returned.
|
||||
* @return The extracted {@link SchemeData}, or null if no suitable data is present.
|
||||
*/
|
||||
private static SchemeData getSchemeData(DrmInitData drmInitData, UUID uuid) {
|
||||
private static SchemeData getSchemeData(DrmInitData drmInitData, UUID uuid,
|
||||
boolean allowMissingData) {
|
||||
// Look for matching scheme data (matching the Common PSSH box for ClearKey).
|
||||
List<SchemeData> matchingSchemeDatas = new ArrayList<>(drmInitData.schemeDataCount);
|
||||
for (int i = 0; i < drmInitData.schemeDataCount; i++) {
|
||||
SchemeData schemeData = drmInitData.get(i);
|
||||
if (schemeData.matches(uuid)
|
||||
|| (C.CLEARKEY_UUID.equals(uuid) && schemeData.matches(C.COMMON_PSSH_UUID))) {
|
||||
boolean uuidMatches = schemeData.matches(uuid)
|
||||
|| (C.CLEARKEY_UUID.equals(uuid) && schemeData.matches(C.COMMON_PSSH_UUID));
|
||||
if (uuidMatches && (schemeData.data != null || allowMissingData)) {
|
||||
matchingSchemeDatas.add(schemeData);
|
||||
}
|
||||
}
|
||||
@ -488,7 +492,8 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
|
||||
if (C.WIDEVINE_UUID.equals(uuid)) {
|
||||
for (int i = 0; i < matchingSchemeDatas.size(); i++) {
|
||||
SchemeData matchingSchemeData = matchingSchemeDatas.get(i);
|
||||
int version = PsshAtomUtil.parseVersion(matchingSchemeData.data);
|
||||
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) {
|
||||
|
@ -54,6 +54,14 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
||||
this(null, false, schemeDatas.toArray(new SchemeData[schemeDatas.size()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param schemeType See {@link #schemeType}.
|
||||
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
|
||||
*/
|
||||
public DrmInitData(String schemeType, List<SchemeData> schemeDatas) {
|
||||
this(schemeType, false, schemeDatas.toArray(new SchemeData[schemeDatas.size()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
|
||||
*/
|
||||
@ -62,7 +70,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param schemeType The protection scheme type, or null if not applicable or unknown.
|
||||
* @param schemeType See {@link #schemeType}.
|
||||
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
|
||||
*/
|
||||
public DrmInitData(@Nullable String schemeType, SchemeData... schemeDatas) {
|
||||
@ -203,7 +211,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
||||
*/
|
||||
public final String mimeType;
|
||||
/**
|
||||
* The initialization data.
|
||||
* The initialization data. May be null for scheme support checks only.
|
||||
*/
|
||||
public final byte[] data;
|
||||
/**
|
||||
@ -214,8 +222,8 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
||||
/**
|
||||
* @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 mimeType The mimeType of the initialization data.
|
||||
* @param data The initialization data.
|
||||
* @param mimeType See {@link #mimeType}.
|
||||
* @param data See {@link #data}.
|
||||
*/
|
||||
public SchemeData(UUID uuid, String mimeType, byte[] data) {
|
||||
this(uuid, mimeType, data, false);
|
||||
@ -224,14 +232,14 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
||||
/**
|
||||
* @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 mimeType The mimeType of the initialization data.
|
||||
* @param data The initialization data.
|
||||
* @param requiresSecureDecryption Whether secure decryption is required.
|
||||
* @param mimeType See {@link #mimeType}.
|
||||
* @param data See {@link #data}.
|
||||
* @param requiresSecureDecryption See {@link #requiresSecureDecryption}.
|
||||
*/
|
||||
public SchemeData(UUID uuid, String mimeType, byte[] data, boolean requiresSecureDecryption) {
|
||||
this.uuid = Assertions.checkNotNull(uuid);
|
||||
this.mimeType = Assertions.checkNotNull(mimeType);
|
||||
this.data = Assertions.checkNotNull(data);
|
||||
this.data = data;
|
||||
this.requiresSecureDecryption = requiresSecureDecryption;
|
||||
}
|
||||
|
||||
@ -252,6 +260,23 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
||||
return C.UUID_NIL.equals(uuid) || schemeUuid.equals(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this {@link SchemeData} can be used to replace {@code other}.
|
||||
*
|
||||
* @param other A {@link SchemeData}.
|
||||
* @return Whether this {@link SchemeData} can be used to replace {@code other}.
|
||||
*/
|
||||
public boolean canReplace(SchemeData other) {
|
||||
return hasData() && !other.hasData() && matches(other.uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether {@link #data} is non-null.
|
||||
*/
|
||||
public boolean hasData() {
|
||||
return data != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof SchemeData)) {
|
||||
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.drm;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Looper;
|
||||
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
||||
|
||||
/**
|
||||
* Manages a DRM session.
|
||||
@ -39,7 +40,8 @@ public interface DrmSessionManager<T extends ExoMediaCrypto> {
|
||||
* must be returned to {@link #releaseSession(DrmSession)} when it is no longer required.
|
||||
*
|
||||
* @param playbackLooper The looper associated with the media playback thread.
|
||||
* @param drmInitData DRM initialization data.
|
||||
* @param drmInitData DRM initialization data. All contained {@link SchemeData}s must contain
|
||||
* non-null {@link SchemeData#data}.
|
||||
* @return The DRM session.
|
||||
*/
|
||||
DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitData);
|
||||
|
@ -346,45 +346,54 @@ public class DashManifestParser extends DefaultHandler
|
||||
protected Pair<String, SchemeData> parseContentProtection(XmlPullParser xpp)
|
||||
throws XmlPullParserException, IOException {
|
||||
String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri");
|
||||
boolean isPlayReady = "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95".equals(schemeIdUri);
|
||||
String schemeType = null;
|
||||
byte[] data = null;
|
||||
UUID uuid = null;
|
||||
boolean requiresSecureDecoder = false;
|
||||
|
||||
if ("urn:mpeg:dash:mp4protection:2011".equals(schemeIdUri)) {
|
||||
schemeType = xpp.getAttributeValue(null, "value");
|
||||
String defaultKid = xpp.getAttributeValue(null, "cenc:default_KID");
|
||||
if (defaultKid != null && !"00000000-0000-0000-0000-000000000000".equals(defaultKid)) {
|
||||
UUID keyId = UUID.fromString(defaultKid);
|
||||
data = PsshAtomUtil.buildPsshAtom(C.COMMON_PSSH_UUID, new UUID[] {keyId}, null);
|
||||
uuid = C.COMMON_PSSH_UUID;
|
||||
}
|
||||
switch (schemeIdUri) {
|
||||
case "urn:mpeg:dash:mp4protection:2011":
|
||||
schemeType = xpp.getAttributeValue(null, "value");
|
||||
String defaultKid = xpp.getAttributeValue(null, "cenc:default_KID");
|
||||
if (defaultKid != null && !"00000000-0000-0000-0000-000000000000".equals(defaultKid)) {
|
||||
UUID keyId = UUID.fromString(defaultKid);
|
||||
data = PsshAtomUtil.buildPsshAtom(C.COMMON_PSSH_UUID, new UUID[] {keyId}, null);
|
||||
uuid = C.COMMON_PSSH_UUID;
|
||||
}
|
||||
break;
|
||||
case "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95":
|
||||
uuid = C.PLAYREADY_UUID;
|
||||
break;
|
||||
case "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed":
|
||||
uuid = C.WIDEVINE_UUID;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
do {
|
||||
xpp.next();
|
||||
if (data == null && XmlPullParserUtil.isStartTag(xpp, "cenc:pssh")
|
||||
&& xpp.next() == XmlPullParser.TEXT) {
|
||||
// The cenc:pssh element is defined in 23001-7:2015.
|
||||
data = Base64.decode(xpp.getText(), Base64.DEFAULT);
|
||||
uuid = PsshAtomUtil.parseUuid(data);
|
||||
if (uuid == null) {
|
||||
Log.w(TAG, "Skipping malformed cenc:pssh data");
|
||||
data = null;
|
||||
}
|
||||
} else if (data == null && isPlayReady && XmlPullParserUtil.isStartTag(xpp, "mspr:pro")
|
||||
&& xpp.next() == XmlPullParser.TEXT) {
|
||||
// The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady.
|
||||
data = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID,
|
||||
Base64.decode(xpp.getText(), Base64.DEFAULT));
|
||||
uuid = C.PLAYREADY_UUID;
|
||||
} else if (XmlPullParserUtil.isStartTag(xpp, "widevine:license")) {
|
||||
if (XmlPullParserUtil.isStartTag(xpp, "widevine:license")) {
|
||||
String robustnessLevel = xpp.getAttributeValue(null, "robustness_level");
|
||||
requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW");
|
||||
} else if (data == null) {
|
||||
if (XmlPullParserUtil.isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) {
|
||||
// The cenc:pssh element is defined in 23001-7:2015.
|
||||
data = Base64.decode(xpp.getText(), Base64.DEFAULT);
|
||||
uuid = PsshAtomUtil.parseUuid(data);
|
||||
if (uuid == null) {
|
||||
Log.w(TAG, "Skipping malformed cenc:pssh data");
|
||||
data = null;
|
||||
}
|
||||
} else if (uuid == C.PLAYREADY_UUID && XmlPullParserUtil.isStartTag(xpp, "mspr:pro")
|
||||
&& xpp.next() == XmlPullParser.TEXT) {
|
||||
// The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady.
|
||||
data = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID,
|
||||
Base64.decode(xpp.getText(), Base64.DEFAULT));
|
||||
}
|
||||
}
|
||||
} while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection"));
|
||||
SchemeData schemeData = data != null
|
||||
SchemeData schemeData = uuid != null
|
||||
? new SchemeData(uuid, MimeTypes.VIDEO_MP4, data, requiresSecureDecoder) : null;
|
||||
return Pair.create(schemeType, schemeData);
|
||||
}
|
||||
@ -518,10 +527,8 @@ public class DashManifestParser extends DefaultHandler
|
||||
ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas;
|
||||
drmSchemeDatas.addAll(extraDrmSchemeDatas);
|
||||
if (!drmSchemeDatas.isEmpty()) {
|
||||
DrmInitData drmInitData = new DrmInitData(drmSchemeDatas);
|
||||
if (drmSchemeType != null) {
|
||||
drmInitData = drmInitData.copyWithSchemeType(drmSchemeType);
|
||||
}
|
||||
filterRedundantIncompleteSchemeDatas(drmSchemeDatas);
|
||||
DrmInitData drmInitData = new DrmInitData(drmSchemeType, drmSchemeDatas);
|
||||
format = format.copyWithDrmInitData(drmInitData);
|
||||
}
|
||||
ArrayList<Descriptor> inbandEventStreams = representationInfo.inbandEventStreams;
|
||||
@ -728,6 +735,25 @@ public class DashManifestParser extends DefaultHandler
|
||||
|
||||
// Utility methods.
|
||||
|
||||
/**
|
||||
* Removes unnecessary {@link SchemeData}s with null {@link SchemeData#data}.
|
||||
*/
|
||||
private static void filterRedundantIncompleteSchemeDatas(ArrayList<SchemeData> schemeDatas) {
|
||||
for (int i = schemeDatas.size() - 1; i >= 0; i--) {
|
||||
SchemeData schemeData = schemeDatas.get(i);
|
||||
if (!schemeData.hasData()) {
|
||||
for (int j = 0; j < schemeDatas.size(); j++) {
|
||||
if (schemeDatas.get(j).canReplace(schemeData)) {
|
||||
// schemeData is incomplete, but there is another matching SchemeData which does contain
|
||||
// data, so we remove the incomplete one.
|
||||
schemeDatas.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a sample mimeType from a container mimeType and codecs attribute.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user