private DrmInitData(boolean cloneSchemeDatas, SchemeData... schemeDatas) { if (cloneSchemeDatas) { schemeDatas = schemeDatas.clone(); } // Sorting ensures that universal scheme data(i.e. data that applies to all schemes) is matched // last. It's also required by the equals and hashcode implementations. Arrays.sort(schemeDatas, this); // Check for no duplicates. for (int i = 1; i < schemeDatas.length; i++) { if (schemeDatas[i - 1].uuid.equals(schemeDatas[i].uuid)) { throw new IllegalArgumentException("Duplicate data for uuid: " + schemeDatas[i].uuid); } } this.schemeDatas = schemeDatas; schemeDataCount = schemeDatas.length; }
/** Returns DrmInitData from leaf atoms. */ private static DrmInitData getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren) { ArrayList<SchemeData> schemeDatas = null; int leafChildrenSize = leafChildren.size(); for (int i = 0; i < leafChildrenSize; i++) { LeafAtom child = leafChildren.get(i); if (child.type == Atom.TYPE_pssh) { if (schemeDatas == null) { schemeDatas = new ArrayList<>(); } byte[] psshData = child.data.data; UUID uuid = PsshAtomUtil.parseUuid(psshData); if (uuid == null) { Log.w(TAG, "Skipped pssh atom (failed to extract uuid)"); } else { schemeDatas.add(new SchemeData(uuid, MimeTypes.VIDEO_MP4, psshData)); } } } return schemeDatas == null ? null : new DrmInitData(schemeDatas); }
@Override public Object build() { StreamElement[] streamElementArray = new StreamElement[streamElements.size()]; streamElements.toArray(streamElementArray); if (protectionElement != null) { DrmInitData drmInitData = new DrmInitData(new SchemeData(protectionElement.uuid, MimeTypes.VIDEO_MP4, protectionElement.data)); for (StreamElement streamElement : streamElementArray) { for (int i = 0; i < streamElement.formats.length; i++) { streamElement.formats[i] = streamElement.formats[i].copyWithDrmInitData(drmInitData); } } } return new SsManifest(majorVersion, minorVersion, timescale, duration, dvrWindowLength, lookAheadCount, isLive, protectionElement, streamElementArray); }
private static SchemeData parseWidevineSchemeData(String line, String keyFormat) throws ParserException { if (KEYFORMAT_WIDEVINE_PSSH_BINARY.equals(keyFormat)) { String uriString = parseStringAttr(line, REGEX_URI); return new SchemeData(C.WIDEVINE_UUID, MimeTypes.VIDEO_MP4, Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT)); } if (KEYFORMAT_WIDEVINE_PSSH_JSON.equals(keyFormat)) { try { return new SchemeData(C.WIDEVINE_UUID, "hls", line.getBytes(C.UTF8_NAME)); } catch (UnsupportedEncodingException e) { throw new ParserException(e); } } return null; }
protected Representation buildRepresentation(RepresentationInfo representationInfo, String contentId, String extraDrmSchemeType, ArrayList<SchemeData> extraDrmSchemeDatas, ArrayList<Descriptor> extraInbandEventStreams) { Format format = representationInfo.format; String drmSchemeType = representationInfo.drmSchemeType != null ? representationInfo.drmSchemeType : extraDrmSchemeType; ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas; drmSchemeDatas.addAll(extraDrmSchemeDatas); if (!drmSchemeDatas.isEmpty()) { filterRedundantIncompleteSchemeDatas(drmSchemeDatas); DrmInitData drmInitData = new DrmInitData(drmSchemeType, drmSchemeDatas); format = format.copyWithDrmInitData(drmInitData); } ArrayList<Descriptor> inbandEventStreams = representationInfo.inbandEventStreams; inbandEventStreams.addAll(extraInbandEventStreams); return Representation.newInstance(contentId, representationInfo.revisionId, format, representationInfo.baseUrl, representationInfo.segmentBase, inbandEventStreams); }
/** * 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; } } } } }
@Override public boolean canAcquireSession(@NonNull DrmInitData drmInitData) { SchemeData schemeData = getSchemeData(drmInitData, uuid, true); if (schemeData == null) { // No data for this manager's scheme. return false; } String schemeType = drmInitData.schemeType; if (schemeType == null || C.CENC_TYPE_cenc.equals(schemeType)) { // If there is no scheme information, assume patternless AES-CTR. return true; } else if (C.CENC_TYPE_cbc1.equals(schemeType) || C.CENC_TYPE_cbcs.equals(schemeType) || C.CENC_TYPE_cens.equals(schemeType)) { // AES-CBC and pattern encryption are supported on API 24 onwards. return Util.SDK_INT >= 24; } // Unknown schemes, assume one of them is supported. return true; }
/** * Retrieves data for a given DRM scheme, specified by its UUID. * * @param uuid The DRM scheme's UUID. * @return The initialization data for the scheme, or null if the scheme is not supported. */ public SchemeData get(UUID uuid) { for (SchemeData schemeData : schemeDatas) { if (schemeData.matches(uuid)) { return schemeData; } } return null; }
@Override public boolean equals(Object obj) { if (!(obj instanceof SchemeData)) { return false; } if (obj == this) { return true; } SchemeData other = (SchemeData) obj; return mimeType.equals(other.mimeType) && Util.areEqual(uuid, other.uuid) && Arrays.equals(data, other.data); }
/** * Parses a ContentProtection element. * * @param xpp The parser from which to read. * @throws XmlPullParserException If an error occurs parsing the element. * @throws IOException If an error occurs reading the element. * @return {@link SchemeData} parsed from the ContentProtection element, or null if the element is * unsupported. */ protected SchemeData parseContentProtection(XmlPullParser xpp) throws XmlPullParserException, IOException { String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); boolean isPlayReady = "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95".equals(schemeIdUri); byte[] data = null; UUID uuid = null; boolean requiresSecureDecoder = false; 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")) { String robustnessLevel = xpp.getAttributeValue(null, "robustness_level"); requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW"); } } while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection")); return data != null ? new SchemeData(uuid, MimeTypes.VIDEO_MP4, data, requiresSecureDecoder) : null; }
protected Representation buildRepresentation(RepresentationInfo representationInfo, String contentId, ArrayList<SchemeData> extraDrmSchemeDatas, ArrayList<SchemeValuePair> extraInbandEventStreams) { Format format = representationInfo.format; ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas; drmSchemeDatas.addAll(extraDrmSchemeDatas); if (!drmSchemeDatas.isEmpty()) { format = format.copyWithDrmInitData(new DrmInitData(drmSchemeDatas)); } ArrayList<SchemeValuePair> inbandEventStremas = representationInfo.inbandEventStreams; inbandEventStremas.addAll(extraInbandEventStreams); return Representation.newInstance(contentId, Representation.REVISION_ID_DEFAULT, format, representationInfo.baseUrl, representationInfo.segmentBase, inbandEventStremas); }
public RepresentationInfo(Format format, String baseUrl, SegmentBase segmentBase, ArrayList<SchemeData> drmSchemeDatas, ArrayList<SchemeValuePair> inbandEventStreams) { this.format = format; this.baseUrl = baseUrl; this.segmentBase = segmentBase; this.drmSchemeDatas = drmSchemeDatas; this.inbandEventStreams = inbandEventStreams; }
/** * Parses a ContentProtection element. * * @param xpp The parser from which to read. * @throws XmlPullParserException If an error occurs parsing the element. * @throws IOException If an error occurs reading the element. * @return {@link SchemeData} parsed from the ContentProtection element, or null if the element is * unsupported. */ protected SchemeData parseContentProtection(XmlPullParser xpp) throws XmlPullParserException, IOException { byte[] data = null; UUID uuid = null; boolean seenPsshElement = false; boolean requiresSecureDecoder = false; do { xpp.next(); // The cenc:pssh element is defined in 23001-7:2015. if (XmlPullParserUtil.isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) { seenPsshElement = true; data = Base64.decode(xpp.getText(), Base64.DEFAULT); uuid = PsshAtomUtil.parseUuid(data); } else if (XmlPullParserUtil.isStartTag(xpp, "widevine:license")) { String robustnessLevel = xpp.getAttributeValue(null, "robustness_level"); requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW"); } } while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection")); if (!seenPsshElement) { return null; } else if (uuid != null) { return new SchemeData(uuid, MimeTypes.VIDEO_MP4, data, requiresSecureDecoder); } else { Log.w(TAG, "Skipped unsupported ContentProtection element"); return null; } }
protected Representation buildRepresentation(RepresentationInfo representationInfo, String contentId, ArrayList<SchemeData> extraDrmSchemeDatas) { Format format = representationInfo.format; ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas; drmSchemeDatas.addAll(extraDrmSchemeDatas); if (!drmSchemeDatas.isEmpty()) { format = format.copyWithDrmInitData(new DrmInitData(drmSchemeDatas)); } return Representation.newInstance(contentId, Representation.REVISION_ID_DEFAULT, format, representationInfo.segmentBase); }
/** * Parses a ContentProtection element. * * @param xpp The parser from which to read. * @throws XmlPullParserException If an error occurs parsing the element. * @throws IOException If an error occurs reading the element. * @return The scheme type and/or {@link SchemeData} parsed from the ContentProtection element. * Either or both may be null, depending on the ContentProtection element being parsed. */ protected Pair<String, SchemeData> parseContentProtection(XmlPullParser xpp) throws XmlPullParserException, IOException { String schemeType = null; byte[] data = null; UUID uuid = null; boolean requiresSecureDecoder = false; String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); if (schemeIdUri != null) { switch (Util.toLowerInvariant(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 (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 = uuid != null ? new SchemeData(uuid, MimeTypes.VIDEO_MP4, data, requiresSecureDecoder) : null; return Pair.create(schemeType, schemeData); }
public RepresentationInfo(Format format, String baseUrl, SegmentBase segmentBase, String drmSchemeType, ArrayList<SchemeData> drmSchemeDatas, ArrayList<Descriptor> inbandEventStreams, long revisionId) { this.format = format; this.baseUrl = baseUrl; this.segmentBase = segmentBase; this.drmSchemeType = drmSchemeType; this.drmSchemeDatas = drmSchemeDatas; this.inbandEventStreams = inbandEventStreams; this.revisionId = revisionId; }
private DrmInitData(@Nullable String schemeType, boolean cloneSchemeDatas, SchemeData... schemeDatas) { this.schemeType = schemeType; if (cloneSchemeDatas) { schemeDatas = schemeDatas.clone(); } // Sorting ensures that universal scheme data (i.e. data that applies to all schemes) is matched // last. It's also required by the equals and hashcode implementations. Arrays.sort(schemeDatas, this); this.schemeDatas = schemeDatas; schemeDataCount = schemeDatas.length; }
/** * Retrieves data for a given DRM scheme, specified by its UUID. * * @deprecated Use {@link #get(int)} and {@link SchemeData#matches(UUID)} instead. * @param uuid The DRM scheme's UUID. * @return The initialization data for the scheme, or null if the scheme is not supported. */ @Deprecated public SchemeData get(UUID uuid) { for (SchemeData schemeData : schemeDatas) { if (schemeData.matches(uuid)) { return schemeData; } } return null; }
/** * Extracts {@link SchemeData} suitable for the given DRM scheme {@link UUID}. * * @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, 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); 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); } } if (matchingSchemeDatas.isEmpty()) { return null; } // For Widevine PSSH boxes, prefer V1 boxes from API 23 and V0 before. if (C.WIDEVINE_UUID.equals(uuid)) { for (int i = 0; i < matchingSchemeDatas.size(); i++) { SchemeData matchingSchemeData = matchingSchemeDatas.get(i); 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) { return matchingSchemeData; } } } // If we don't have any special handling, prefer the first matching scheme data. 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; }
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()])); }
private List<SchemeData> getAllSchemeData(DrmInitData drmInitData) { ArrayList<SchemeData> schemeDatas = new ArrayList<>(); for (int i = 0; i < drmInitData.schemeDataCount; i++) { schemeDatas.add(drmInitData.get(i)); } return schemeDatas; }
/** * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */ public DrmInitData(List<SchemeData> schemeDatas) { this(false, schemeDatas.toArray(new SchemeData[schemeDatas.size()])); }
/** * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */ public DrmInitData(SchemeData... schemeDatas) { this(true, schemeDatas); }
DrmInitData(Parcel in) { schemeDatas = in.createTypedArray(SchemeData.CREATOR); schemeDataCount = schemeDatas.length; }
@Override public int compare(SchemeData first, SchemeData second) { return C.UUID_NIL.equals(first.uuid) ? (C.UUID_NIL.equals(second.uuid) ? 0 : 1) : first.uuid.compareTo(second.uuid); }
SchemeData(Parcel in) { uuid = new UUID(in.readLong(), in.readLong()); mimeType = in.readString(); data = in.createByteArray(); requiresSecureDecryption = in.readByte() != 0; }