Skip to content

Commit

Permalink
Fill manifest drm info with media files' pssh when needed
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=174185407
  • Loading branch information
AquilesCanta authored and ojw28 committed Nov 2, 2017
1 parent 46172ff commit 8c42479
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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 @@ -464,8 +465,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 ? 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,
Expand Down Expand Up @@ -731,4 +732,42 @@ 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 @@ -340,7 +340,7 @@ public void setMode(@Mode int mode, byte[] offlineLicenseKeySetId) {

@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;
Expand Down Expand Up @@ -371,7 +371,7 @@ public DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitDa
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);
Expand Down Expand Up @@ -467,15 +467,19 @@ public void onProvisionError(Exception error) {
*
* @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);
}
}
Expand All @@ -488,7 +492,8 @@ private static SchemeData getSchemeData(DrmInitData drmInitData, UUID uuid) {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ public DrmInitData(List<SchemeData> schemeDatas) {
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.
*/
Expand All @@ -62,7 +70,7 @@ public DrmInitData(SchemeData... schemeDatas) {
}

/**
* @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) {
Expand Down Expand Up @@ -203,7 +211,7 @@ public static final class SchemeData implements Parcelable {
*/
public final String mimeType;
/**
* The initialization data.
* The initialization data. May be null for scheme support checks only.
*/
public final byte[] data;
/**
Expand All @@ -214,8 +222,8 @@ public static final class SchemeData implements 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);
Expand All @@ -224,14 +232,14 @@ public SchemeData(UUID uuid, String mimeType, byte[] data) {
/**
* @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;
}

Expand All @@ -252,6 +260,23 @@ public boolean matches(UUID schemeUuid) {
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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import android.annotation.TargetApi;
import android.os.Looper;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;

/**
* Manages a DRM session.
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,45 +346,54 @@ protected int getContentType(Format format) {
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);
}
Expand Down Expand Up @@ -518,10 +527,8 @@ protected Representation buildRepresentation(RepresentationInfo representationIn
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;
Expand Down Expand Up @@ -728,6 +735,25 @@ protected int parseAudioChannelConfiguration(XmlPullParser xpp)

// 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.
*
Expand Down

0 comments on commit 8c42479

Please sign in to comment.