From 6e5ae4eddca8a364a1ba95544cc50b3412f2cad8 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 4 May 2016 05:41:06 -0700 Subject: [PATCH] Simplify DrmInitData. - This moves to a single DrmInitData implementation that supports both specific and universal UUID matching. You can also do a combination of the two, which was not previously possible. - The object model is simplified as a result. This is a precursor to a change where DrmInitData will be included directly in the Format to enable key rotation. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=121472592 --- .../exoplayer/drm/DrmInitDataTest.java | 119 +++++++--- .../extractor/mkv/MatroskaExtractorTest.java | 6 +- .../java/com/google/android/exoplayer/C.java | 8 + .../exoplayer/FrameworkSampleSource.java | 11 +- .../exoplayer/dash/DashChunkSource.java | 16 +- .../exoplayer/dash/mpd/ContentProtection.java | 28 +-- .../MediaPresentationDescriptionParser.java | 22 +- .../android/exoplayer/drm/DrmInitData.java | 208 +++++++----------- .../drm/StreamingDrmSessionManager.java | 18 +- .../extractor/mkv/MatroskaExtractor.java | 6 +- .../extractor/mp4/FragmentedMp4Extractor.java | 16 +- .../SmoothStreamingSampleSource.java | 9 +- 12 files changed, 236 insertions(+), 231 deletions(-) diff --git a/library/src/androidTest/java/com/google/android/exoplayer/drm/DrmInitDataTest.java b/library/src/androidTest/java/com/google/android/exoplayer/drm/DrmInitDataTest.java index 8538c18012..af39352c64 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/drm/DrmInitDataTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/drm/DrmInitDataTest.java @@ -19,8 +19,8 @@ import static com.google.android.exoplayer.drm.StreamingDrmSessionManager.PLAYRE import static com.google.android.exoplayer.drm.StreamingDrmSessionManager.WIDEVINE_UUID; import static com.google.android.exoplayer.util.MimeTypes.VIDEO_MP4; -import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData; -import com.google.android.exoplayer.drm.DrmInitData.UuidSchemeInitDataTuple; +import com.google.android.exoplayer.C; +import com.google.android.exoplayer.drm.DrmInitData.SchemeData; import com.google.android.exoplayer.testutil.TestUtil; import android.test.MoreAsserts; @@ -32,53 +32,110 @@ import junit.framework.TestCase; */ public class DrmInitDataTest extends TestCase { - private static final SchemeInitData DATA_1 = - new SchemeInitData(VIDEO_MP4, TestUtil.buildTestData(128, 1 /* data seed */)); - private static final SchemeInitData DATA_2 = - new SchemeInitData(VIDEO_MP4, TestUtil.buildTestData(128, 2 /* data seed */)); + 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 */)); - public void testMappedEquals() { - DrmInitData.Mapped drmInitData = new DrmInitData.Mapped( - new UuidSchemeInitDataTuple(WIDEVINE_UUID, DATA_1), - new UuidSchemeInitDataTuple(PLAYREADY_UUID, DATA_2)); + public void testEquals() { + DrmInitData drmInitData = new DrmInitData(DATA_1, DATA_2); // Basic non-referential equality test. - DrmInitData.Mapped testInitData = new DrmInitData.Mapped( - new UuidSchemeInitDataTuple(WIDEVINE_UUID, DATA_1), - new UuidSchemeInitDataTuple(PLAYREADY_UUID, DATA_2)); + DrmInitData testInitData = new DrmInitData(DATA_1, DATA_2); assertEquals(drmInitData, testInitData); assertEquals(drmInitData.hashCode(), testInitData.hashCode()); - // Passing the tuples in reverse order shouldn't affect equality. - testInitData = new DrmInitData.Mapped( - new UuidSchemeInitDataTuple(PLAYREADY_UUID, DATA_2), - new UuidSchemeInitDataTuple(WIDEVINE_UUID, DATA_1)); + // Basic non-referential equality test with non-referential scheme data. + testInitData = new DrmInitData(DATA_1B, DATA_2B); + assertEquals(drmInitData, testInitData); + assertEquals(drmInitData.hashCode(), testInitData.hashCode()); + + // Passing the scheme data in reverse order shouldn't affect equality. + testInitData = new DrmInitData(DATA_2, DATA_1); + assertEquals(drmInitData, testInitData); + assertEquals(drmInitData.hashCode(), testInitData.hashCode()); + + // Ditto. + testInitData = new DrmInitData(DATA_2B, DATA_1B); assertEquals(drmInitData, testInitData); assertEquals(drmInitData.hashCode(), testInitData.hashCode()); // Different number of tuples should affect equality. - testInitData = new DrmInitData.Mapped( - new UuidSchemeInitDataTuple(WIDEVINE_UUID, DATA_1)); + testInitData = new DrmInitData(DATA_1); MoreAsserts.assertNotEqual(drmInitData, testInitData); // Different data in one of the tuples should affect equality. - testInitData = new DrmInitData.Mapped( - new UuidSchemeInitDataTuple(WIDEVINE_UUID, DATA_1), - new UuidSchemeInitDataTuple(PLAYREADY_UUID, DATA_1)); + testInitData = new DrmInitData(DATA_1, DATA_1); MoreAsserts.assertNotEqual(drmInitData, testInitData); } - public void testUniversalEquals() { - DrmInitData.Universal drmInitData = new DrmInitData.Universal(DATA_1); + public void testGet() { + // Basic matching. + DrmInitData testInitData = new DrmInitData(DATA_1, DATA_2); + assertEquals(DATA_1, testInitData.get(WIDEVINE_UUID)); + assertEquals(DATA_2, testInitData.get(PLAYREADY_UUID)); + assertNull(testInitData.get(C.UUID_NIL)); - // Basic non-referential equality test. - DrmInitData.Universal testInitData = new DrmInitData.Universal(DATA_1); - assertEquals(drmInitData, testInitData); - assertEquals(drmInitData.hashCode(), testInitData.hashCode()); + // Basic matching including universal data. + testInitData = new DrmInitData(DATA_1, DATA_2, DATA_UNIVERSAL); + assertEquals(DATA_1, testInitData.get(WIDEVINE_UUID)); + assertEquals(DATA_2, testInitData.get(PLAYREADY_UUID)); + assertEquals(DATA_UNIVERSAL, testInitData.get(C.UUID_NIL)); - // Different data should affect equality. - testInitData = new DrmInitData.Universal(DATA_2); - MoreAsserts.assertNotEqual(drmInitData, testInitData); + // Passing the scheme data in reverse order shouldn't affect equality. + testInitData = new DrmInitData(DATA_UNIVERSAL, DATA_2, DATA_1); + assertEquals(DATA_1, testInitData.get(WIDEVINE_UUID)); + assertEquals(DATA_2, testInitData.get(PLAYREADY_UUID)); + assertEquals(DATA_UNIVERSAL, testInitData.get(C.UUID_NIL)); + + // Universal data should be returned in the absence of a specific match. + testInitData = new DrmInitData(DATA_1, DATA_UNIVERSAL); + assertEquals(DATA_1, testInitData.get(WIDEVINE_UUID)); + assertEquals(DATA_UNIVERSAL, testInitData.get(PLAYREADY_UUID)); + assertEquals(DATA_UNIVERSAL, testInitData.get(C.UUID_NIL)); + } + + public void testDuplicateSchemeDataRejected() { + try { + new DrmInitData(DATA_1, DATA_1); + fail(); + } catch (IllegalArgumentException e) { + // Expected. + } + + try { + new DrmInitData(DATA_1, DATA_1B); + fail(); + } catch (IllegalArgumentException e) { + // Expected. + } + + try { + new DrmInitData(DATA_1, DATA_2, DATA_1B); + fail(); + } catch (IllegalArgumentException e) { + // Expected. + } + } + + public void testSchemeDataMatches() { + assertTrue(DATA_1.matches(WIDEVINE_UUID)); + assertFalse(DATA_1.matches(PLAYREADY_UUID)); + assertFalse(DATA_2.matches(C.UUID_NIL)); + + assertFalse(DATA_2.matches(WIDEVINE_UUID)); + assertTrue(DATA_2.matches(PLAYREADY_UUID)); + assertFalse(DATA_2.matches(C.UUID_NIL)); + + assertTrue(DATA_UNIVERSAL.matches(WIDEVINE_UUID)); + assertTrue(DATA_UNIVERSAL.matches(PLAYREADY_UUID)); + assertTrue(DATA_UNIVERSAL.matches(C.UUID_NIL)); } } diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractorTest.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractorTest.java index 1cca044273..227e5886c6 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractorTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractorTest.java @@ -21,7 +21,7 @@ import com.google.android.exoplayer.C; import com.google.android.exoplayer.Format; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.drm.DrmInitData; -import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData; +import com.google.android.exoplayer.drm.DrmInitData.SchemeData; import com.google.android.exoplayer.extractor.ChunkIndex; import com.google.android.exoplayer.extractor.mkv.StreamBuilder.ContentEncodingSettings; import com.google.android.exoplayer.testutil.FakeExtractorOutput; @@ -237,10 +237,10 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { assertSeekMap(DEFAULT_TIMECODE_SCALE, 1); DrmInitData drmInitData = extractorOutput.drmInitData; assertNotNull(drmInitData); - SchemeInitData widevineInitData = drmInitData.get(WIDEVINE_UUID); + SchemeData widevineInitData = drmInitData.get(WIDEVINE_UUID); assertEquals(MimeTypes.VIDEO_WEBM, widevineInitData.mimeType); android.test.MoreAsserts.assertEquals(TEST_ENCRYPTION_KEY_ID, widevineInitData.data); - SchemeInitData zeroInitData = drmInitData.get(ZERO_UUID); + SchemeData zeroInitData = drmInitData.get(ZERO_UUID); assertEquals(MimeTypes.VIDEO_WEBM, zeroInitData.mimeType); android.test.MoreAsserts.assertEquals(TEST_ENCRYPTION_KEY_ID, zeroInitData.data); } diff --git a/library/src/main/java/com/google/android/exoplayer/C.java b/library/src/main/java/com/google/android/exoplayer/C.java index f9b089a1a2..1120d5ae20 100644 --- a/library/src/main/java/com/google/android/exoplayer/C.java +++ b/library/src/main/java/com/google/android/exoplayer/C.java @@ -20,6 +20,8 @@ import com.google.android.exoplayer.util.Util; import android.media.AudioFormat; import android.media.MediaCodec; +import java.util.UUID; + /** * Defines constants that are generally useful throughout the library. */ @@ -175,6 +177,12 @@ public final class C { public static final int DEFAULT_MUXED_BUFFER_SIZE = DEFAULT_VIDEO_BUFFER_SIZE + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE; + /** + * The Nil UUID as defined by + * RFC4122. + */ + public static final UUID UUID_NIL = new UUID(0L, 0L); + private C() {} } diff --git a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java index 30bb92848f..9d68157eb4 100644 --- a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java @@ -16,8 +16,7 @@ package com.google.android.exoplayer; import com.google.android.exoplayer.drm.DrmInitData; -import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData; -import com.google.android.exoplayer.drm.DrmInitData.UuidSchemeInitDataTuple; +import com.google.android.exoplayer.drm.DrmInitData.SchemeData; import com.google.android.exoplayer.extractor.ExtractorSampleSource; import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil; import com.google.android.exoplayer.util.Assertions; @@ -287,15 +286,13 @@ public final class FrameworkSampleSource implements SampleSource { if (psshInfo == null || psshInfo.isEmpty()) { return null; } - UuidSchemeInitDataTuple[] uuidSchemeInitDataTuples = - new UuidSchemeInitDataTuple[psshInfo.size()]; + SchemeData[] schemeDatas = new SchemeData[psshInfo.size()]; int count = 0; for (UUID uuid : psshInfo.keySet()) { byte[] psshAtom = PsshAtomUtil.buildPsshAtom(uuid, psshInfo.get(uuid)); - SchemeInitData schemeData = new SchemeInitData(MimeTypes.VIDEO_MP4, psshAtom); - uuidSchemeInitDataTuples[count++] = new UuidSchemeInitDataTuple(uuid, schemeData); + schemeDatas[count++] = new SchemeData(uuid, MimeTypes.VIDEO_MP4, psshAtom); } - return new DrmInitData.Mapped(uuidSchemeInitDataTuples); + return new DrmInitData(schemeDatas); } private void seekToUsInternal(long positionUs, boolean force) { diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index fc656757c4..7614a6e1c7 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -37,7 +37,7 @@ import com.google.android.exoplayer.dash.mpd.Period; import com.google.android.exoplayer.dash.mpd.RangedUri; import com.google.android.exoplayer.dash.mpd.Representation; import com.google.android.exoplayer.drm.DrmInitData; -import com.google.android.exoplayer.drm.DrmInitData.UuidSchemeInitDataTuple; +import com.google.android.exoplayer.drm.DrmInitData.SchemeData; import com.google.android.exoplayer.extractor.ChunkIndex; import com.google.android.exoplayer.extractor.SeekMap; import com.google.android.exoplayer.extractor.mkv.MatroskaExtractor; @@ -344,19 +344,17 @@ public class DashChunkSource implements ChunkSource { } private static DrmInitData getDrmInitData(AdaptationSet adaptationSet) { - ArrayList uuidSchemeInitDataTuples = null; + ArrayList schemeDatas = null; for (int i = 0; i < adaptationSet.contentProtections.size(); i++) { ContentProtection contentProtection = adaptationSet.contentProtections.get(i); - if (contentProtection.uuid != null && contentProtection.data != null) { - if (uuidSchemeInitDataTuples == null) { - uuidSchemeInitDataTuples = new ArrayList(); + if (contentProtection.schemeData != null) { + if (schemeDatas == null) { + schemeDatas = new ArrayList(); } - uuidSchemeInitDataTuples.add( - new UuidSchemeInitDataTuple(contentProtection.uuid, contentProtection.data)); + schemeDatas.add(contentProtection.schemeData); } } - return uuidSchemeInitDataTuples == null ? null - : new DrmInitData.Mapped(uuidSchemeInitDataTuples); + return schemeDatas == null ? null : new DrmInitData(schemeDatas); } private static long getPeriodDurationUs(MediaPresentationDescription manifest, int index) { diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/ContentProtection.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/ContentProtection.java index 48ce051c99..0055f4c52e 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/ContentProtection.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/ContentProtection.java @@ -15,12 +15,10 @@ */ package com.google.android.exoplayer.dash.mpd; -import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData; +import com.google.android.exoplayer.drm.DrmInitData.SchemeData; import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Util; -import java.util.UUID; - /** * Represents a ContentProtection tag in an AdaptationSet. */ @@ -31,25 +29,18 @@ public class ContentProtection { */ public final String schemeUriId; - /** - * The UUID of the protection scheme. May be null. - */ - public final UUID uuid; - /** * Protection scheme specific initialization data. May be null. */ - public final SchemeInitData data; + public final SchemeData schemeData; /** * @param schemeUriId Identifies the content protection scheme. - * @param uuid The UUID of the protection scheme, if known. May be null. - * @param data Protection scheme specific initialization data. May be null. + * @param schemeData Protection scheme specific initialization data. May be null. */ - public ContentProtection(String schemeUriId, UUID uuid, SchemeInitData data) { + public ContentProtection(String schemeUriId, SchemeData schemeData) { this.schemeUriId = Assertions.checkNotNull(schemeUriId); - this.uuid = uuid; - this.data = data; + this.schemeData = schemeData; } @Override @@ -62,17 +53,12 @@ public class ContentProtection { } ContentProtection other = (ContentProtection) obj; - return schemeUriId.equals(other.schemeUriId) - && Util.areEqual(uuid, other.uuid) - && Util.areEqual(data, other.data); + return schemeUriId.equals(other.schemeUriId) && Util.areEqual(schemeData, other.schemeData); } @Override public int hashCode() { - int hashCode = schemeUriId.hashCode(); - hashCode = (31 * hashCode) + (uuid != null ? uuid.hashCode() : 0); - hashCode = (31 * hashCode) + (data != null ? data.hashCode() : 0); - return hashCode; + return (31 * schemeUriId.hashCode()) + (schemeData != null ? schemeData.hashCode() : 0); } } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java index e7f37dc0d5..926526b095 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java @@ -22,7 +22,7 @@ import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentList; import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate; import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement; import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase; -import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData; +import com.google.android.exoplayer.drm.DrmInitData.SchemeData; import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil; import com.google.android.exoplayer.upstream.UriLoadable; import com.google.android.exoplayer.util.Assertions; @@ -314,29 +314,29 @@ public class MediaPresentationDescriptionParser extends DefaultHandler protected ContentProtection parseContentProtection(XmlPullParser xpp) throws XmlPullParserException, IOException { String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); - UUID uuid = null; - SchemeInitData data = null; + SchemeData schemeData = null; boolean seenPsshElement = false; do { xpp.next(); // The cenc:pssh element is defined in 23001-7:2015. if (ParserUtil.isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) { seenPsshElement = true; - data = new SchemeInitData(MimeTypes.VIDEO_MP4, - Base64.decode(xpp.getText(), Base64.DEFAULT)); - uuid = PsshAtomUtil.parseUuid(data.data); + byte[] data = Base64.decode(xpp.getText(), Base64.DEFAULT); + UUID uuid = PsshAtomUtil.parseUuid(data); + if (uuid != null) { + schemeData = new SchemeData(uuid, MimeTypes.VIDEO_MP4, data); + } } } while (!ParserUtil.isEndTag(xpp, "ContentProtection")); - if (seenPsshElement && uuid == null) { + if (seenPsshElement && schemeData == null) { Log.w(TAG, "Skipped unsupported ContentProtection element"); return null; } - return buildContentProtection(schemeIdUri, uuid, data); + return buildContentProtection(schemeIdUri, schemeData); } - protected ContentProtection buildContentProtection(String schemeIdUri, UUID uuid, - SchemeInitData data) { - return new ContentProtection(schemeIdUri, uuid, data); + protected ContentProtection buildContentProtection(String schemeIdUri, SchemeData schemeData) { + return new ContentProtection(schemeIdUri, schemeData); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/drm/DrmInitData.java b/library/src/main/java/com/google/android/exoplayer/drm/DrmInitData.java index e05959231d..5c3cf9061a 100644 --- a/library/src/main/java/com/google/android/exoplayer/drm/DrmInitData.java +++ b/library/src/main/java/com/google/android/exoplayer/drm/DrmInitData.java @@ -15,119 +15,103 @@ */ package com.google.android.exoplayer.drm; +import com.google.android.exoplayer.C; +import com.google.android.exoplayer.drm.DrmInitData.SchemeData; import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Util; import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.UUID; /** * Encapsulates DRM initialization data for possibly multiple DRM schemes. */ -public interface DrmInitData { +public final class DrmInitData implements Comparator { - /** - * Retrieves initialization data for a given DRM scheme, specified by its UUID. - * - * @param schemeUuid The DRM scheme's UUID. - * @return The initialization data for the scheme, or null if the scheme is not supported. - */ - SchemeInitData get(UUID schemeUuid); + private final SchemeData[] schemeDatas; - /** - * A {@link DrmInitData} implementation that maps UUID onto scheme specific data. - */ - final class Mapped implements DrmInitData { + // Lazily initialized hashcode. + private int hashCode; - private final UuidSchemeInitDataTuple[] schemeDatas; + public DrmInitData(List schemeDatas) { + this(false, schemeDatas.toArray(new SchemeData[schemeDatas.size()])); + } - // Lazily initialized hashcode. - private int hashCode; + public DrmInitData(SchemeData... schemeDatas) { + this(true, schemeDatas); + } - public Mapped(UuidSchemeInitDataTuple... schemeDatas) { - this.schemeDatas = schemeDatas.clone(); - Arrays.sort(this.schemeDatas); // Required for correct equals and hashcode implementations. + private DrmInitData(boolean cloneSchemeDatas, SchemeData... schemeDatas) { + if (cloneSchemeDatas) { + schemeDatas = schemeDatas.clone(); } - - public Mapped(List schemeDatas) { - this.schemeDatas = schemeDatas.toArray(new UuidSchemeInitDataTuple[schemeDatas.size()]); - Arrays.sort(this.schemeDatas); // Required for correct equals and hashcode implementations. - } - - @Override - public SchemeInitData get(UUID schemeUuid) { - for (UuidSchemeInitDataTuple schemeData : schemeDatas) { - if (schemeUuid.equals(schemeData.uuid)) { - return schemeData.data; - } + // 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); } - return null; } - - @Override - public int hashCode() { - if (hashCode == 0) { - hashCode = Arrays.hashCode(schemeDatas); - } - return hashCode; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - return Arrays.equals(schemeDatas, ((Mapped) obj).schemeDatas); - } - + this.schemeDatas = schemeDatas; } /** - * A {@link DrmInitData} implementation that returns the same data for all schemes. + * 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. */ - final class Universal implements DrmInitData { - - private final SchemeInitData data; - - public Universal(SchemeInitData data) { - this.data = data; - } - - @Override - public SchemeInitData get(UUID schemeUuid) { - return data; - } - - @Override - public int hashCode() { - return data == null ? 0 : data.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; + public SchemeData get(UUID uuid) { + for (SchemeData schemeData : schemeDatas) { + if (schemeData.matches(uuid)) { + return schemeData; } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - return Util.areEqual(data, ((Universal) obj).data); } + return null; + } + @Override + public int hashCode() { + if (hashCode == 0) { + hashCode = Arrays.hashCode(schemeDatas); + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + return Arrays.equals(schemeDatas, ((DrmInitData) obj).schemeDatas); + } + + @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); } /** * Scheme initialization data. */ - final class SchemeInitData { + public static final class SchemeData { // Lazily initialized hashcode. private int hashCode; + /** + * The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is universal (i.e. + * applies to all schemes). + */ + private final UUID uuid; /** * The mimeType of {@link #data}. */ @@ -138,73 +122,51 @@ public interface DrmInitData { public final 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. */ - public SchemeInitData(String mimeType, byte[] data) { + public SchemeData(UUID uuid, String mimeType, byte[] data) { + this.uuid = Assertions.checkNotNull(uuid); this.mimeType = Assertions.checkNotNull(mimeType); this.data = Assertions.checkNotNull(data); } + /** + * Returns whether this initialization data applies to the specified scheme. + * + * @param schemeUuid The scheme {@link UUID}. + * @return True if this initialization data applies to the specified scheme. False otherwise. + */ + public boolean matches(UUID schemeUuid) { + return C.UUID_NIL.equals(uuid) || schemeUuid.equals(uuid); + } + @Override public boolean equals(Object obj) { - if (!(obj instanceof SchemeInitData)) { + if (!(obj instanceof SchemeData)) { return false; } if (obj == this) { return true; } - SchemeInitData other = (SchemeInitData) obj; - return mimeType.equals(other.mimeType) && Arrays.equals(data, other.data); + SchemeData other = (SchemeData) obj; + return mimeType.equals(other.mimeType) && Util.areEqual(uuid, other.uuid) + && Arrays.equals(data, other.data); } @Override public int hashCode() { if (hashCode == 0) { - hashCode = mimeType.hashCode() + 31 * Arrays.hashCode(data); + int result = ((uuid == null) ? 0 : uuid.hashCode()); + result = 31 * result + mimeType.hashCode(); + result = 31 * result + Arrays.hashCode(data); + hashCode = result; } return hashCode; } } - /** - * A tuple consisting of a {@link UUID} and a {@link SchemeInitData}. - *

- * Implements {@link Comparable} based on {@link UUID} ordering. - */ - final class UuidSchemeInitDataTuple implements Comparable { - - public final UUID uuid; - public final SchemeInitData data; - - public UuidSchemeInitDataTuple(UUID uuid, SchemeInitData data) { - this.uuid = Assertions.checkNotNull(uuid); - this.data = Assertions.checkNotNull(data); - } - - @Override - public int compareTo(UuidSchemeInitDataTuple another) { - return uuid.compareTo(another.uuid); - } - - @Override - public int hashCode() { - return uuid.hashCode() + 31 * data.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - UuidSchemeInitDataTuple other = (UuidSchemeInitDataTuple) obj; - return Util.areEqual(uuid, other.uuid) && Util.areEqual(data, other.data); - } - - } - } diff --git a/library/src/main/java/com/google/android/exoplayer/drm/StreamingDrmSessionManager.java b/library/src/main/java/com/google/android/exoplayer/drm/StreamingDrmSessionManager.java index be22d4c6c8..59539ee67c 100644 --- a/library/src/main/java/com/google/android/exoplayer/drm/StreamingDrmSessionManager.java +++ b/library/src/main/java/com/google/android/exoplayer/drm/StreamingDrmSessionManager.java @@ -15,7 +15,7 @@ */ package com.google.android.exoplayer.drm; -import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData; +import com.google.android.exoplayer.drm.DrmInitData.SchemeData; import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil; import com.google.android.exoplayer.util.Util; @@ -104,7 +104,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager { private int state; private MediaCrypto mediaCrypto; private Exception lastException; - private SchemeInitData schemeInitData; + private SchemeData schemeData; private byte[] sessionId; /** @@ -273,19 +273,19 @@ public class StreamingDrmSessionManager implements DrmSessionManager { requestHandlerThread.start(); postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper()); } - if (schemeInitData == null) { - schemeInitData = drmInitData.get(uuid); - if (schemeInitData == null) { + if (schemeData == null) { + schemeData = drmInitData.get(uuid); + if (schemeData == null) { onError(new IllegalStateException("Media does not support uuid: " + uuid)); return; } 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.data, WIDEVINE_UUID); + byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(schemeData.data, WIDEVINE_UUID); if (psshData == null) { // Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged. } else { - schemeInitData = new SchemeInitData(schemeInitData.mimeType, psshData); + schemeData = new SchemeData(WIDEVINE_UUID, schemeData.mimeType, psshData); } } } @@ -306,7 +306,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager { postRequestHandler = null; requestHandlerThread.quit(); requestHandlerThread = null; - schemeInitData = null; + schemeData = null; mediaCrypto = null; lastException = null; if (sessionId != null) { @@ -368,7 +368,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager { private void postKeyRequest() { KeyRequest keyRequest; try { - keyRequest = mediaDrm.getKeyRequest(sessionId, schemeInitData.data, schemeInitData.mimeType, + keyRequest = mediaDrm.getKeyRequest(sessionId, schemeData.data, schemeData.mimeType, MediaDrm.KEY_TYPE_STREAMING, optionalKeyRequestParameters); postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget(); } catch (NotProvisionedException e) { diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractor.java index a13927969d..4851acd749 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractor.java @@ -19,7 +19,7 @@ import com.google.android.exoplayer.C; import com.google.android.exoplayer.Format; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.drm.DrmInitData; -import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData; +import com.google.android.exoplayer.drm.DrmInitData.SchemeData; import com.google.android.exoplayer.extractor.ChunkIndex; import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.ExtractorInput; @@ -484,8 +484,8 @@ public final class MatroskaExtractor implements Extractor { throw new ParserException("Encrypted Track found but ContentEncKeyID was not found"); } if (!sentDrmInitData) { - extractorOutput.drmInitData(new DrmInitData.Universal( - new SchemeInitData(MimeTypes.VIDEO_WEBM, currentTrack.encryptionKeyId))); + extractorOutput.drmInitData(new DrmInitData( + new SchemeData(C.UUID_NIL, MimeTypes.VIDEO_WEBM, currentTrack.encryptionKeyId))); sentDrmInitData = true; } } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java index 7e32adcef4..9af79a015a 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java @@ -18,8 +18,7 @@ package com.google.android.exoplayer.extractor.mp4; import com.google.android.exoplayer.C; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.drm.DrmInitData; -import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData; -import com.google.android.exoplayer.drm.DrmInitData.UuidSchemeInitDataTuple; +import com.google.android.exoplayer.drm.DrmInitData.SchemeData; import com.google.android.exoplayer.extractor.ChunkIndex; import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.ExtractorInput; @@ -320,25 +319,24 @@ public final class FragmentedMp4Extractor implements Extractor { List moovLeafChildren = moov.leafChildren; int moovLeafChildrenSize = moovLeafChildren.size(); - ArrayList uuidSchemeInitDataTuples = null; + ArrayList schemeDatas = null; for (int i = 0; i < moovLeafChildrenSize; i++) { LeafAtom child = moovLeafChildren.get(i); if (child.type == Atom.TYPE_pssh) { - if (uuidSchemeInitDataTuples == null) { - uuidSchemeInitDataTuples = new ArrayList(); + 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 { - uuidSchemeInitDataTuples.add(new UuidSchemeInitDataTuple(uuid, - new SchemeInitData(MimeTypes.VIDEO_MP4, psshData))); + schemeDatas.add(new SchemeData(uuid, MimeTypes.VIDEO_MP4, psshData)); } } } - if (uuidSchemeInitDataTuples != null) { - extractorOutput.drmInitData(new DrmInitData.Mapped(uuidSchemeInitDataTuples)); + if (schemeDatas != null) { + extractorOutput.drmInitData(new DrmInitData(schemeDatas)); } // Read declaration of track fragments in the Moov box. diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java index 2669489b72..1a4ac05773 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingSampleSource.java @@ -29,8 +29,7 @@ import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener; import com.google.android.exoplayer.chunk.FormatEvaluator; import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; import com.google.android.exoplayer.drm.DrmInitData; -import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData; -import com.google.android.exoplayer.drm.DrmInitData.UuidSchemeInitDataTuple; +import com.google.android.exoplayer.drm.DrmInitData.SchemeData; import com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement; @@ -70,7 +69,7 @@ public final class SmoothStreamingSampleSource implements SampleSource { private long durationUs; private SmoothStreamingManifest currentManifest; private TrackEncryptionBox[] trackEncryptionBoxes; - private DrmInitData.Mapped drmInitData; + private DrmInitData drmInitData; private TrackGroupArray trackGroups; private int[] trackGroupElementIndices; private boolean pendingReset; @@ -120,8 +119,8 @@ public final class SmoothStreamingSampleSource implements SampleSource { byte[] keyId = getProtectionElementKeyId(protectionElement.data); trackEncryptionBoxes = new TrackEncryptionBox[1]; trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId); - drmInitData = new DrmInitData.Mapped(new UuidSchemeInitDataTuple(protectionElement.uuid, - new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data))); + drmInitData = new DrmInitData( + new SchemeData(protectionElement.uuid, MimeTypes.VIDEO_MP4, protectionElement.data)); } return true; }