From 317a9944997d3bfaccb1deae1ac50acb2b8bf1e4 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 20 Jun 2017 02:52:55 -0700 Subject: [PATCH] Add type to DrmInitData.SchemeData At the moment, only CENC-defined scheme types are known values. This will allow having more information about the encryption scheme through the format, which in turn will allow more informed decisions on format support. Issue:#1661 Issue:#1989 Issue:#2089 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=159538907 --- .../google/android/exoplayer2/FormatTest.java | 4 +- .../exoplayer2/drm/DrmInitDataTest.java | 20 +++---- .../drm/OfflineLicenseHelperTest.java | 2 +- .../android/exoplayer2/drm/DrmInitData.java | 60 +++++++++++++++++-- .../extractor/mkv/MatroskaExtractor.java | 2 +- .../exoplayer2/extractor/mp4/AtomParsers.java | 51 +++++++++++----- .../extractor/mp4/FragmentedMp4Extractor.java | 28 +++++---- .../exoplayer2/extractor/mp4/Track.java | 22 +++++-- .../extractor/mp4/TrackEncryptionBox.java | 19 ++++-- .../exoplayer2/source/dash/DashUtilTest.java | 2 +- .../dash/manifest/DashManifestParser.java | 5 +- .../source/smoothstreaming/SsMediaPeriod.java | 2 +- .../manifest/SsManifestParser.java | 2 +- 13 files changed, 158 insertions(+), 61 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java index a47a3fb12d6..316fb11e9a5 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java @@ -53,9 +53,9 @@ public final class FormatTest extends TestCase { } public void testParcelable() { - DrmInitData.SchemeData DRM_DATA_1 = new DrmInitData.SchemeData(WIDEVINE_UUID, VIDEO_MP4, + DrmInitData.SchemeData DRM_DATA_1 = new DrmInitData.SchemeData(WIDEVINE_UUID, "cenc", VIDEO_MP4, TestUtil.buildTestData(128, 1 /* data seed */)); - DrmInitData.SchemeData DRM_DATA_2 = new DrmInitData.SchemeData(C.UUID_NIL, VIDEO_WEBM, + DrmInitData.SchemeData DRM_DATA_2 = new DrmInitData.SchemeData(C.UUID_NIL, null, VIDEO_WEBM, TestUtil.buildTestData(128, 1 /* data seed */)); DrmInitData drmInitData = new DrmInitData(DRM_DATA_1, DRM_DATA_2); byte[] projectionData = new byte[] {1, 2, 3}; diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java index df2e8756a5e..b7f1cd1ed64 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java @@ -31,16 +31,16 @@ */ public class DrmInitDataTest extends TestCase { - private static final SchemeData DATA_1 = - new SchemeData(WIDEVINE_UUID, VIDEO_MP4, TestUtil.buildTestData(128, 1 /* data seed */)); - private static final SchemeData DATA_2 = - new SchemeData(PLAYREADY_UUID, VIDEO_MP4, TestUtil.buildTestData(128, 2 /* data seed */)); - private static final SchemeData DATA_1B = - new SchemeData(WIDEVINE_UUID, VIDEO_MP4, TestUtil.buildTestData(128, 1 /* data seed */)); - private static final SchemeData DATA_2B = - new SchemeData(PLAYREADY_UUID, VIDEO_MP4, TestUtil.buildTestData(128, 2 /* data seed */)); - private static final SchemeData DATA_UNIVERSAL = - new SchemeData(C.UUID_NIL, VIDEO_MP4, TestUtil.buildTestData(128, 3 /* data seed */)); + private static final SchemeData DATA_1 = new SchemeData(WIDEVINE_UUID, "cbc1", VIDEO_MP4, + TestUtil.buildTestData(128, 1 /* data seed */)); + private static final SchemeData DATA_2 = new SchemeData(PLAYREADY_UUID, null, VIDEO_MP4, + TestUtil.buildTestData(128, 2 /* data seed */)); + private static final SchemeData DATA_1B = new SchemeData(WIDEVINE_UUID, "cens", VIDEO_MP4, + TestUtil.buildTestData(128, 1 /* data seed */)); + private static final SchemeData DATA_2B = new SchemeData(PLAYREADY_UUID, null, VIDEO_MP4, + TestUtil.buildTestData(128, 2 /* data seed */)); + private static final SchemeData DATA_UNIVERSAL = new SchemeData(C.UUID_NIL, null, VIDEO_MP4, + TestUtil.buildTestData(128, 3 /* data seed */)); public void testParcelable() { DrmInitData drmInitDataToParcel = new DrmInitData(DATA_1, DATA_2); diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java index afd690762b6..9f5b067b5ef 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java @@ -154,7 +154,7 @@ private void setStubLicenseAndPlaybackDurationValues(long licenseDuration, } private static DrmInitData newDrmInitData() { - return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "mimeType", + return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "cenc", "mimeType", new byte[] {1, 4, 7, 0, 3, 6})); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java index 5126628dd9c..9fa6547a00b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java @@ -17,6 +17,7 @@ import android.os.Parcel; import android.os.Parcelable; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.util.Assertions; @@ -102,6 +103,33 @@ public SchemeData get(int index) { return schemeDatas[index]; } + /** + * Returns a copy of the {@link DrmInitData} instance whose {@link SchemeData}s have been updated + * to have the specified scheme type. + * + * @param schemeType A protection scheme type. May be null. + * @return A copy of the {@link DrmInitData} instance whose {@link SchemeData}s have been updated + * to have the specified scheme type. + */ + public DrmInitData copyWithSchemeType(@Nullable String schemeType) { + boolean isCopyRequired = false; + for (SchemeData schemeData : schemeDatas) { + if (!Util.areEqual(schemeData.type, schemeType)) { + isCopyRequired = true; + break; + } + } + if (isCopyRequired) { + SchemeData[] schemeDatas = new SchemeData[this.schemeDatas.length]; + for (int i = 0; i < schemeDatas.length; i++) { + schemeDatas[i] = this.schemeDatas[i].copyWithSchemeType(schemeType); + } + return new DrmInitData(schemeDatas); + } else { + return this; + } + } + @Override public int hashCode() { if (hashCode == 0) { @@ -167,6 +195,10 @@ public static final class SchemeData implements Parcelable { * applies to all schemes). */ private final UUID uuid; + /** + * The protection scheme type, or null if not applicable or unknown. + */ + @Nullable public final String type; /** * The mimeType of {@link #data}. */ @@ -183,22 +215,26 @@ 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 type The type of the protection scheme, or null if not applicable or unknown. * @param mimeType The mimeType of the initialization data. * @param data The initialization data. */ - public SchemeData(UUID uuid, String mimeType, byte[] data) { - this(uuid, mimeType, data, false); + public SchemeData(UUID uuid, @Nullable String type, String mimeType, byte[] data) { + this(uuid, type, mimeType, data, false); } /** * @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 type The type of the protection scheme, or null if not applicable or unknown. * @param mimeType The mimeType of the initialization data. * @param data The initialization data. * @param requiresSecureDecryption Whether secure decryption is required. */ - public SchemeData(UUID uuid, String mimeType, byte[] data, boolean requiresSecureDecryption) { + public SchemeData(UUID uuid, @Nullable String type, String mimeType, byte[] data, + boolean requiresSecureDecryption) { this.uuid = Assertions.checkNotNull(uuid); + this.type = type; this.mimeType = Assertions.checkNotNull(mimeType); this.data = Assertions.checkNotNull(data); this.requiresSecureDecryption = requiresSecureDecryption; @@ -206,6 +242,7 @@ public SchemeData(UUID uuid, String mimeType, byte[] data, boolean requiresSecur /* package */ SchemeData(Parcel in) { uuid = new UUID(in.readLong(), in.readLong()); + type = in.readString(); mimeType = in.readString(); data = in.createByteArray(); requiresSecureDecryption = in.readByte() != 0; @@ -221,6 +258,19 @@ public boolean matches(UUID schemeUuid) { return C.UUID_NIL.equals(uuid) || schemeUuid.equals(uuid); } + /** + * Returns a copy of the {@link SchemeData} instance with the given scheme type. + * + * @param type A protection scheme type. + * @return A copy of the {@link SchemeData} instance with the given scheme type. + */ + public SchemeData copyWithSchemeType(String type) { + if (Util.areEqual(this.type, type)) { + return this; + } + return new SchemeData(uuid, type, mimeType, data, requiresSecureDecryption); + } + @Override public boolean equals(Object obj) { if (!(obj instanceof SchemeData)) { @@ -231,13 +281,14 @@ public boolean equals(Object obj) { } SchemeData other = (SchemeData) obj; return mimeType.equals(other.mimeType) && Util.areEqual(uuid, other.uuid) - && Arrays.equals(data, other.data); + && Util.areEqual(type, other.type) && Arrays.equals(data, other.data); } @Override public int hashCode() { if (hashCode == 0) { int result = uuid.hashCode(); + result = 31 * result + (type == null ? 0 : type.hashCode()); result = 31 * result + mimeType.hashCode(); result = 31 * result + Arrays.hashCode(data); hashCode = result; @@ -256,6 +307,7 @@ public int describeContents() { public void writeToParcel(Parcel dest, int flags) { dest.writeLong(uuid.getMostSignificantBits()); dest.writeLong(uuid.getLeastSignificantBits()); + dest.writeString(type); dest.writeString(mimeType); dest.writeByteArray(data); dest.writeByte((byte) (requiresSecureDecryption ? 1 : 0)); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 591c56f525e..4c8ca177e0f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -586,7 +586,7 @@ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOExce if (currentTrack.cryptoData == null) { throw new ParserException("Encrypted Track found but ContentEncKeyID was not found"); } - currentTrack.drmInitData = new DrmInitData(new SchemeData(C.UUID_NIL, + currentTrack.drmInitData = new DrmInitData(new SchemeData(C.UUID_NIL, null, MimeTypes.VIDEO_WEBM, currentTrack.cryptoData.encryptionKey)); } break; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 474ba65d869..65a3d87b452 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -615,10 +615,10 @@ private static StsdData parseStsd(ParsableByteArray stsd, int trackId, int rotat || childAtomType == Atom.TYPE_wvtt || childAtomType == Atom.TYPE_stpp || childAtomType == Atom.TYPE_c608) { parseTextSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, - language, drmInitData, out); + language, out); } else if (childAtomType == Atom.TYPE_camm) { out.format = Format.createSampleFormat(Integer.toString(trackId), - MimeTypes.APPLICATION_CAMERA_MOTION, null, Format.NO_VALUE, drmInitData); + MimeTypes.APPLICATION_CAMERA_MOTION, null, Format.NO_VALUE, null); } stsd.setPosition(childStartPosition + childAtomSize); } @@ -626,8 +626,7 @@ private static StsdData parseStsd(ParsableByteArray stsd, int trackId, int rotat } private static void parseTextSampleEntry(ParsableByteArray parent, int atomType, int position, - int atomSize, int trackId, String language, DrmInitData drmInitData, StsdData out) - throws ParserException { + int atomSize, int trackId, String language, StsdData out) throws ParserException { parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); // Default values. @@ -658,8 +657,7 @@ private static void parseTextSampleEntry(ParsableByteArray parent, int atomType, } out.format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, - Format.NO_VALUE, 0, language, Format.NO_VALUE, drmInitData, subsampleOffsetUs, - initializationData); + Format.NO_VALUE, 0, language, Format.NO_VALUE, null, subsampleOffsetUs, initializationData); } private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position, @@ -677,7 +675,14 @@ private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType int childPosition = parent.getPosition(); if (atomType == Atom.TYPE_encv) { atomType = parseSampleEntryEncryptionData(parent, position, size, out, entryIndex); + TrackEncryptionBox encryptionBox = out.trackEncryptionBoxes[entryIndex]; + String schemeType = encryptionBox != null ? encryptionBox.schemeType : null; + if (schemeType != null) { + drmInitData = drmInitData.copyWithSchemeType(schemeType); + } parent.setPosition(childPosition); + } else { + drmInitData = null; } List initializationData = null; @@ -846,7 +851,14 @@ private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType int childPosition = parent.getPosition(); if (atomType == Atom.TYPE_enca) { atomType = parseSampleEntryEncryptionData(parent, position, size, out, entryIndex); + TrackEncryptionBox encryptionBox = out.trackEncryptionBoxes[entryIndex]; + String schemeType = encryptionBox != null ? encryptionBox.schemeType : null; + if (schemeType != null) { + drmInitData = drmInitData.copyWithSchemeType(schemeType); + } parent.setPosition(childPosition); + } else { + drmInitData = null; } // If the atom type determines a MIME type, set it immediately. @@ -1051,9 +1063,9 @@ private static int parseSampleEntryEncryptionData(ParsableByteArray parent, int private static Pair parseSinfFromParent(ParsableByteArray parent, int position, int size) { int childPosition = position + Atom.HEADER_SIZE; - - boolean isCencScheme = false; - TrackEncryptionBox trackEncryptionBox = null; + int schemeInformationBoxPosition = C.POSITION_UNSET; + int schemeInformationBoxSize = 0; + String schemeType = null; Integer dataFormat = null; while (childPosition - position < size) { parent.setPosition(childPosition); @@ -1063,24 +1075,30 @@ private static Pair parseSinfFromParent(ParsableByt dataFormat = parent.readInt(); } else if (childAtomType == Atom.TYPE_schm) { parent.skipBytes(4); - isCencScheme = parent.readInt() == TYPE_cenc; + // scheme_type field. Defined in ISO/IEC 23001-7:2016, section 4.1. + schemeType = parent.readString(4); } else if (childAtomType == Atom.TYPE_schi) { - trackEncryptionBox = parseSchiFromParent(parent, childPosition, childAtomSize); + schemeInformationBoxPosition = childPosition; + schemeInformationBoxSize = childAtomSize; } childPosition += childAtomSize; } - if (isCencScheme) { + if (schemeType != null) { Assertions.checkArgument(dataFormat != null, "frma atom is mandatory"); - Assertions.checkArgument(trackEncryptionBox != null, "schi->tenc atom is mandatory"); - return Pair.create(dataFormat, trackEncryptionBox); + Assertions.checkArgument(schemeInformationBoxPosition != C.POSITION_UNSET, + "schi atom is mandatory"); + TrackEncryptionBox encryptionBox = parseSchiFromParent(parent, schemeInformationBoxPosition, + schemeInformationBoxSize, schemeType); + Assertions.checkArgument(encryptionBox != null, "tenc atom is mandatory"); + return Pair.create(dataFormat, encryptionBox); } else { return null; } } private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position, - int size) { + int size, String schemeType) { int childPosition = position + Atom.HEADER_SIZE; while (childPosition - position < size) { parent.setPosition(childPosition); @@ -1092,7 +1110,8 @@ private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int defaultInitVectorSize = parent.readUnsignedByte(); byte[] defaultKeyId = new byte[16]; parent.readBytes(defaultKeyId, 0, defaultKeyId.length); - return new TrackEncryptionBox(defaultIsEncrypted, defaultInitVectorSize, defaultKeyId); + return new TrackEncryptionBox(defaultIsEncrypted, schemeType, defaultInitVectorSize, + defaultKeyId); } childPosition += childAtomSize; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index fe1d4b04af6..5d44e718807 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -559,11 +559,12 @@ private static void parseTraf(ContainerAtom traf, SparseArray track parseTruns(traf, trackBundle, decodeTime, flags); + TrackEncryptionBox encryptionBox = trackBundle.track + .getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex); + LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz); if (saiz != null) { - TrackEncryptionBox trackEncryptionBox = trackBundle.track - .sampleDescriptionEncryptionBoxes[fragment.header.sampleDescriptionIndex]; - parseSaiz(trackEncryptionBox, saiz.data, fragment); + parseSaiz(encryptionBox, saiz.data, fragment); } LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio); @@ -579,7 +580,8 @@ private static void parseTraf(ContainerAtom traf, SparseArray track LeafAtom sbgp = traf.getLeafAtomOfType(Atom.TYPE_sbgp); LeafAtom sgpd = traf.getLeafAtomOfType(Atom.TYPE_sgpd); if (sbgp != null && sgpd != null) { - parseSgpd(sbgp.data, sgpd.data, fragment); + parseSgpd(sbgp.data, sgpd.data, encryptionBox != null ? encryptionBox.schemeType : null, + fragment); } int leafChildrenSize = traf.leafChildren.size(); @@ -868,8 +870,8 @@ private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out.fillEncryptionData(senc); } - private static void parseSgpd(ParsableByteArray sbgp, ParsableByteArray sgpd, TrackFragment out) - throws ParserException { + private static void parseSgpd(ParsableByteArray sbgp, ParsableByteArray sgpd, String schemeType, + TrackFragment out) throws ParserException { sbgp.setPosition(Atom.HEADER_SIZE); int sbgpFullAtom = sbgp.readInt(); if (sbgp.readInt() != SAMPLE_GROUP_TYPE_seig) { @@ -910,7 +912,7 @@ private static void parseSgpd(ParsableByteArray sbgp, ParsableByteArray sgpd, Tr byte[] keyId = new byte[16]; sgpd.readBytes(keyId, 0, keyId.length); out.definesEncryptionData = true; - out.trackEncryptionBox = new TrackEncryptionBox(isProtected, initVectorSize, keyId); + out.trackEncryptionBox = new TrackEncryptionBox(isProtected, schemeType, initVectorSize, keyId); } /** @@ -1135,7 +1137,7 @@ private boolean readSample(ExtractorInput input) throws IOException, Interrupted if (fragment.definesEncryptionData) { encryptionBox = fragment.trackEncryptionBox != null ? fragment.trackEncryptionBox - : track.sampleDescriptionEncryptionBoxes[fragment.header.sampleDescriptionIndex]; + : track.getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex); if (encryptionBox != currentTrackBundle.cachedEncryptionBox) { cryptoData = new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, encryptionBox.keyId); } else { @@ -1205,7 +1207,7 @@ private int appendSampleEncryptionData(TrackBundle trackBundle) { int sampleDescriptionIndex = trackFragment.header.sampleDescriptionIndex; TrackEncryptionBox encryptionBox = trackFragment.trackEncryptionBox != null ? trackFragment.trackEncryptionBox - : trackBundle.track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex]; + : trackBundle.track.getSampleDescriptionEncryptionBox(sampleDescriptionIndex); int vectorSize = encryptionBox.initializationVectorSize; boolean subsampleEncryption = trackFragment .sampleHasSubsampleEncryptionTable[trackBundle.currentSampleIndex]; @@ -1245,7 +1247,7 @@ private static DrmInitData getDrmInitDataFromAtoms(List leafChild if (uuid == null) { Log.w(TAG, "Skipped pssh atom (failed to extract uuid)"); } else { - schemeDatas.add(new SchemeData(uuid, MimeTypes.VIDEO_MP4, psshData)); + schemeDatas.add(new SchemeData(uuid, null, MimeTypes.VIDEO_MP4, psshData)); } } } @@ -1325,8 +1327,12 @@ public void reset() { } public void updateDrmInitData(DrmInitData drmInitData) { - output.format(track.format.copyWithDrmInitData(drmInitData)); + TrackEncryptionBox encryptionBox = + track.getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex); + String schemeType = encryptionBox != null ? encryptionBox.schemeType : null; + output.format(track.format.copyWithDrmInitData(drmInitData.copyWithSchemeType(schemeType))); } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java index f1c4e99ec10..7ac31587946 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.extractor.mp4; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import java.lang.annotation.Retention; @@ -77,11 +78,6 @@ public final class Track { */ @Transformation public final int sampleTransformation; - /** - * Track encryption boxes for the different track sample descriptions. Entries may be null. - */ - public final TrackEncryptionBox[] sampleDescriptionEncryptionBoxes; - /** * Durations of edit list segments in the movie timescale. Null if there is no edit list. */ @@ -98,9 +94,11 @@ public final class Track { */ public final int nalUnitLengthFieldLength; + @Nullable private final TrackEncryptionBox[] sampleDescriptionEncryptionBoxes; + public Track(int id, int type, long timescale, long movieTimescale, long durationUs, Format format, @Transformation int sampleTransformation, - TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength, + @Nullable TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength, long[] editListDurations, long[] editListMediaTimes) { this.id = id; this.type = type; @@ -115,4 +113,16 @@ public Track(int id, int type, long timescale, long movieTimescale, long duratio this.editListMediaTimes = editListMediaTimes; } + /** + * Returns the {@link TrackEncryptionBox} for the given sample description index. + * + * @param sampleDescriptionIndex The given sample description index + * @return The {@link TrackEncryptionBox} for the given sample description index. Maybe null if no + * such entry exists. + */ + public TrackEncryptionBox getSampleDescriptionEncryptionBox(int sampleDescriptionIndex) { + return sampleDescriptionEncryptionBoxes == null ? null + : sampleDescriptionEncryptionBoxes[sampleDescriptionIndex]; + } + } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java index dde03a85070..d56504f7806 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.extractor.mp4; +import android.support.annotation.Nullable; + /** * Encapsulates information parsed from a track encryption (tenc) box or sample group description * (sgpd) box in an MP4 stream. @@ -26,6 +28,11 @@ public final class TrackEncryptionBox { */ public final boolean isEncrypted; + /** + * The protection scheme type, as defined by the 'schm' box, or null if unknown. + */ + @Nullable public final String schemeType; + /** * The initialization vector size in bytes for the samples in the corresponding sample group. */ @@ -37,13 +44,15 @@ public final class TrackEncryptionBox { public final byte[] keyId; /** - * @param isEncrypted Indicates the encryption state of the samples in the sample group. - * @param initializationVectorSize The initialization vector size in bytes for the samples in the - * corresponding sample group. - * @param keyId The key identifier for the samples in the corresponding sample group. + * @param isEncrypted See {@link #isEncrypted}. + * @param schemeType See {@link #schemeType}. + * @param initializationVectorSize See {@link #initializationVectorSize}. + * @param keyId See {@link #keyId}. */ - public TrackEncryptionBox(boolean isEncrypted, int initializationVectorSize, byte[] keyId) { + public TrackEncryptionBox(boolean isEncrypted, @Nullable String schemeType, + int initializationVectorSize, byte[] keyId) { this.isEncrypted = isEncrypted; + this.schemeType = schemeType; this.initializationVectorSize = initializationVectorSize; this.keyId = keyId; } diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java index d714573a82c..bac1c272e8c 100644 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java +++ b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java @@ -75,7 +75,7 @@ private static Representation newRepresentations(DrmInitData drmInitData) { } private static DrmInitData newDrmInitData() { - return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "mimeType", + return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, null, "mimeType", new byte[]{1, 4, 7, 0, 3, 6})); } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index 0682af5dd6c..53115a7a0ed 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -343,6 +343,7 @@ protected SchemeData parseContentProtection(XmlPullParser xpp) throws XmlPullPar IOException { String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); boolean isPlayReady = "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95".equals(schemeIdUri); + String schemeType = xpp.getAttributeValue(null, "value"); byte[] data = null; UUID uuid = null; boolean requiresSecureDecoder = false; @@ -368,8 +369,8 @@ protected SchemeData parseContentProtection(XmlPullParser xpp) throws XmlPullPar requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW"); } } while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection")); - return data != null ? new SchemeData(uuid, MimeTypes.VIDEO_MP4, data, requiresSecureDecoder) - : null; + return data != null + ? new SchemeData(uuid, schemeType, MimeTypes.VIDEO_MP4, data, requiresSecureDecoder) : null; } /** diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java index 87f9c4d03ba..8322da2471f 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java @@ -69,7 +69,7 @@ public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFacto if (protectionElement != null) { byte[] keyId = getProtectionElementKeyId(protectionElement.data); trackEncryptionBoxes = new TrackEncryptionBox[] { - new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId)}; + new TrackEncryptionBox(true, null, INITIALIZATION_VECTOR_SIZE, keyId)}; } else { trackEncryptionBoxes = null; } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java index 3ca5f8d9972..5784cc7bc6e 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java @@ -375,7 +375,7 @@ public Object build() { StreamElement[] streamElementArray = new StreamElement[streamElements.size()]; streamElements.toArray(streamElementArray); if (protectionElement != null) { - DrmInitData drmInitData = new DrmInitData(new SchemeData(protectionElement.uuid, + DrmInitData drmInitData = new DrmInitData(new SchemeData(protectionElement.uuid, null, MimeTypes.VIDEO_MP4, protectionElement.data)); for (StreamElement streamElement : streamElementArray) { for (int i = 0; i < streamElement.formats.length; i++) {