SchemeData schemeData = uuid != null ? new SchemeData( uuid, licenseServerUrl, MimeTypes.VIDEO_MP4, data, requiresSecureDecoder) : null;
@Override public boolean canAcquireSession(@NonNull DrmInitData drmInitData) { if (offlineLicenseKeySetId != null) { // An offline license can be restored so a session can always be acquired. return true; } List<SchemeData> schemeDatas = getSchemeDatas(drmInitData, uuid, true); if (schemeDatas.isEmpty()) { 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)) { // 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)) { // API support for AES-CBC and pattern encryption was added in API 24. However, the // implementation was not stable until API 25. return Util.SDK_INT >= 25; } // Unknown schemes, assume one of them is supported. return true; }
@Test public void givenDrmDataContainsDrmScheme_whenCheckingCanAcquireSession_thenReturnsTrue() { DrmInitData.SchemeData recognisedSchemeData = new DrmInitData.SchemeData( DRM_SCHEME, "ANY_MIME_TYPE", new byte[]{} ); DrmInitData drmInitData = new DrmInitData(Collections.singletonList(recognisedSchemeData)); boolean canAcquireSession = localDrmSessionManager.canAcquireSession(drmInitData); assertThat(canAcquireSession).isTrue(); }
@Test public void givenDrmDataDoesNotContainDrmScheme_whenCheckingCanAcquireSession_thenReturnsFalse() { DrmInitData.SchemeData unrecognisedSchemeData = new DrmInitData.SchemeData( UUID.randomUUID(), "ANY_MIME_TYPE", new byte[]{} ); DrmInitData drmInitData = new DrmInitData(Collections.singletonList(unrecognisedSchemeData)); boolean canAcquireSession = localDrmSessionManager.canAcquireSession(drmInitData); assertThat(canAcquireSession).isFalse(); }
SchemeData[] playlistSchemeDatas = new SchemeData[schemeDatas.length]; for (int i = 0; i < schemeDatas.length; i++) { playlistSchemeDatas[i] = schemeDatas[i].copyWithData(null);
throw new ParserException("Encrypted Track found but ContentEncKeyID was not found"); currentTrack.drmInitData = new DrmInitData(new SchemeData(C.UUID_NIL, MimeTypes.VIDEO_WEBM, currentTrack.cryptoData.encryptionKey));
assertThat(playlist.protectionSchemes.get(0).matches(C.PLAYREADY_UUID)).isTrue(); assertThat(playlist.protectionSchemes.get(0).hasData()).isFalse(); assertThat(playlist.protectionSchemes.get(1).matches(C.WIDEVINE_UUID)).isTrue(); assertThat(playlist.protectionSchemes.get(1).hasData()).isFalse(); assertThat(playlist.segments.get(1).drmInitData.get(0).matches(C.PLAYREADY_UUID)).isTrue(); assertThat(playlist.segments.get(1).drmInitData.get(0).hasData()).isTrue(); assertThat(playlist.segments.get(1).drmInitData.get(1).matches(C.WIDEVINE_UUID)).isTrue(); assertThat(playlist.segments.get(1).drmInitData.get(1).hasData()).isTrue(); assertThat(playlist.segments.get(2).drmInitData) .isNotEqualTo(playlist.segments.get(3).drmInitData); assertThat(playlist.segments.get(3).drmInitData.get(0).matches(C.PLAYREADY_UUID)).isTrue(); assertThat(playlist.segments.get(3).drmInitData.get(0).hasData()).isTrue(); assertThat(playlist.segments.get(3).drmInitData.get(1).matches(C.WIDEVINE_UUID)).isTrue(); assertThat(playlist.segments.get(3).drmInitData.get(1).hasData()).isTrue();
@Test public void testParcelable() { DrmInitData.SchemeData drmData1 = new DrmInitData.SchemeData(WIDEVINE_UUID, VIDEO_MP4, TestUtil.buildTestData(128, 1 /* data seed */)); DrmInitData.SchemeData drmData2 = new DrmInitData.SchemeData(C.UUID_NIL, VIDEO_WEBM, TestUtil.buildTestData(128, 1 /* data seed */)); DrmInitData drmInitData = new DrmInitData(drmData1, drmData2);
concatenatedDataPosition += schemeDataLength; return firstSchemeData.copyWithData(concatenatedData);
@Test public void testParseSampleAesMethod() throws Exception { Uri playlistUri = Uri.parse("https://example.com/test.m3u8"); String playlistString = "#EXTM3U\n" + "#EXT-X-MEDIA-SEQUENCE:0\n" + "#EXTINF:8,\n" + "https://priv.example.com/1.ts\n" + "\n" + "#EXT-X-KEY:METHOD=SAMPLE-AES,URI=" + "\"data:text/plain;base64,VGhpcyBpcyBhbiBlYXN0ZXIgZWdn\"," + "IV=0x9358382AEB449EE23C3D809DA0B9CCD3,KEYFORMATVERSIONS=\"1\"," + "KEYFORMAT=\"com.widevine\",IV=0x1566B\n" + "#EXTINF:8,\n" + "https://priv.example.com/2.ts\n" + "#EXT-X-ENDLIST\n"; InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString)); HlsMediaPlaylist playlist = (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream); assertThat(playlist.protectionSchemes.schemeType).isEqualTo(C.CENC_TYPE_cbcs); assertThat(playlist.protectionSchemes.get(0).matches(C.WIDEVINE_UUID)).isTrue(); assertThat(playlist.protectionSchemes.get(0).hasData()).isFalse(); assertThat(playlist.segments.get(0).drmInitData).isNull(); assertThat(playlist.segments.get(1).drmInitData.get(0).matches(C.WIDEVINE_UUID)).isTrue(); assertThat(playlist.segments.get(1).drmInitData.get(0).hasData()).isTrue(); }
@Test public void testParseSampleAesCtrMethod() throws Exception { Uri playlistUri = Uri.parse("https://example.com/test.m3u8"); String playlistString = "#EXTM3U\n" + "#EXT-X-MEDIA-SEQUENCE:0\n" + "#EXTINF:8,\n" + "https://priv.example.com/1.ts\n" + "\n" + "#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI=" + "\"data:text/plain;base64,VGhpcyBpcyBhbiBlYXN0ZXIgZWdn\"," + "IV=0x9358382AEB449EE23C3D809DA0B9CCD3,KEYFORMATVERSIONS=\"1\"," + "KEYFORMAT=\"com.widevine\",IV=0x1566B\n" + "#EXTINF:8,\n" + "https://priv.example.com/2.ts\n" + "#EXT-X-ENDLIST\n"; InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString)); HlsMediaPlaylist playlist = (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream); assertThat(playlist.protectionSchemes.schemeType).isEqualTo(C.CENC_TYPE_cenc); assertThat(playlist.protectionSchemes.get(0).matches(C.WIDEVINE_UUID)).isTrue(); assertThat(playlist.protectionSchemes.get(0).hasData()).isFalse(); }
/** 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); }
/** * Extracts {@link SchemeData} instances 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} instances, or an empty list if no suitable data is * present. */ private static List<SchemeData> getSchemeDatas( 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); } } return matchingSchemeDatas; }
@Test public void testParseSampleAesCencMethod() throws Exception { Uri playlistUri = Uri.parse("https://example.com/test.m3u8"); String playlistString = "#EXTM3U\n" + "#EXT-X-MEDIA-SEQUENCE:0\n" + "#EXTINF:8,\n" + "https://priv.example.com/1.ts\n" + "\n" + "#EXT-X-KEY:URI=\"data:text/plain;base64,VGhpcyBpcyBhbiBlYXN0ZXIgZWdn\"," + "IV=0x9358382AEB449EE23C3D809DA0B9CCD3,KEYFORMATVERSIONS=\"1\"," + "KEYFORMAT=\"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\"," + "IV=0x1566B,METHOD=SAMPLE-AES-CENC \n" + "#EXTINF:8,\n" + "https://priv.example.com/2.ts\n" + "#EXT-X-ENDLIST\n"; InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString)); HlsMediaPlaylist playlist = (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream); assertThat(playlist.protectionSchemes.schemeType).isEqualTo(C.CENC_TYPE_cenc); assertThat(playlist.protectionSchemes.get(0).matches(C.WIDEVINE_UUID)).isTrue(); assertThat(playlist.protectionSchemes.get(0).hasData()).isFalse(); }
private static @Nullable SchemeData parseWidevineSchemeData( String line, String keyFormat, Map<String, String> variableDefinitions) throws ParserException { if (KEYFORMAT_WIDEVINE_PSSH_BINARY.equals(keyFormat)) { String uriString = parseStringAttr(line, REGEX_URI, variableDefinitions); 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; }
@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) { int type = streamElement.type; if (type == C.TRACK_TYPE_VIDEO || type == C.TRACK_TYPE_AUDIO) { Format[] formats = streamElement.formats; for (int i = 0; i < formats.length; i++) { formats[i] = formats[i].copyWithDrmInitData(drmInitData); } } } } return new SsManifest(majorVersion, minorVersion, timescale, duration, dvrWindowLength, lookAheadCount, isLive, protectionElement, streamElementArray); }
/** * 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; } } } } }
/** * 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; }
private static @Nullable SchemeData parsePlayReadySchemeData( String line, Map<String, String> variableDefinitions) throws ParserException { String keyFormatVersions = parseOptionalStringAttr(line, REGEX_KEYFORMATVERSIONS, "1", variableDefinitions); if (!"1".equals(keyFormatVersions)) { // Not supported. return null; } String uriString = parseStringAttr(line, REGEX_URI, variableDefinitions); byte[] data = Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT); byte[] psshData = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID, data); return new SchemeData(C.PLAYREADY_UUID, MimeTypes.VIDEO_MP4, psshData); }