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
This commit is contained in:
olly 2016-05-04 05:41:06 -07:00 committed by Oliver Woodman
parent 686816a610
commit 6e5ae4eddc
12 changed files with 236 additions and 231 deletions

View File

@ -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.drm.StreamingDrmSessionManager.WIDEVINE_UUID;
import static com.google.android.exoplayer.util.MimeTypes.VIDEO_MP4; import static com.google.android.exoplayer.util.MimeTypes.VIDEO_MP4;
import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.drm.DrmInitData.UuidSchemeInitDataTuple; import com.google.android.exoplayer.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer.testutil.TestUtil; import com.google.android.exoplayer.testutil.TestUtil;
import android.test.MoreAsserts; import android.test.MoreAsserts;
@ -32,53 +32,110 @@ import junit.framework.TestCase;
*/ */
public class DrmInitDataTest extends TestCase { public class DrmInitDataTest extends TestCase {
private static final SchemeInitData DATA_1 = private static final SchemeData DATA_1 =
new SchemeInitData(VIDEO_MP4, TestUtil.buildTestData(128, 1 /* data seed */)); new SchemeData(WIDEVINE_UUID, VIDEO_MP4, TestUtil.buildTestData(128, 1 /* data seed */));
private static final SchemeInitData DATA_2 = private static final SchemeData DATA_2 =
new SchemeInitData(VIDEO_MP4, TestUtil.buildTestData(128, 2 /* data seed */)); 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() { public void testEquals() {
DrmInitData.Mapped drmInitData = new DrmInitData.Mapped( DrmInitData drmInitData = new DrmInitData(DATA_1, DATA_2);
new UuidSchemeInitDataTuple(WIDEVINE_UUID, DATA_1),
new UuidSchemeInitDataTuple(PLAYREADY_UUID, DATA_2));
// Basic non-referential equality test. // Basic non-referential equality test.
DrmInitData.Mapped testInitData = new DrmInitData.Mapped( DrmInitData testInitData = new DrmInitData(DATA_1, DATA_2);
new UuidSchemeInitDataTuple(WIDEVINE_UUID, DATA_1),
new UuidSchemeInitDataTuple(PLAYREADY_UUID, DATA_2));
assertEquals(drmInitData, testInitData); assertEquals(drmInitData, testInitData);
assertEquals(drmInitData.hashCode(), testInitData.hashCode()); assertEquals(drmInitData.hashCode(), testInitData.hashCode());
// Passing the tuples in reverse order shouldn't affect equality. // Basic non-referential equality test with non-referential scheme data.
testInitData = new DrmInitData.Mapped( testInitData = new DrmInitData(DATA_1B, DATA_2B);
new UuidSchemeInitDataTuple(PLAYREADY_UUID, DATA_2), assertEquals(drmInitData, testInitData);
new UuidSchemeInitDataTuple(WIDEVINE_UUID, DATA_1)); 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, testInitData);
assertEquals(drmInitData.hashCode(), testInitData.hashCode()); assertEquals(drmInitData.hashCode(), testInitData.hashCode());
// Different number of tuples should affect equality. // Different number of tuples should affect equality.
testInitData = new DrmInitData.Mapped( testInitData = new DrmInitData(DATA_1);
new UuidSchemeInitDataTuple(WIDEVINE_UUID, DATA_1));
MoreAsserts.assertNotEqual(drmInitData, testInitData); MoreAsserts.assertNotEqual(drmInitData, testInitData);
// Different data in one of the tuples should affect equality. // Different data in one of the tuples should affect equality.
testInitData = new DrmInitData.Mapped( testInitData = new DrmInitData(DATA_1, DATA_1);
new UuidSchemeInitDataTuple(WIDEVINE_UUID, DATA_1),
new UuidSchemeInitDataTuple(PLAYREADY_UUID, DATA_1));
MoreAsserts.assertNotEqual(drmInitData, testInitData); MoreAsserts.assertNotEqual(drmInitData, testInitData);
} }
public void testUniversalEquals() { public void testGet() {
DrmInitData.Universal drmInitData = new DrmInitData.Universal(DATA_1); // 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. // Basic matching including universal data.
DrmInitData.Universal testInitData = new DrmInitData.Universal(DATA_1); testInitData = new DrmInitData(DATA_1, DATA_2, DATA_UNIVERSAL);
assertEquals(drmInitData, testInitData); assertEquals(DATA_1, testInitData.get(WIDEVINE_UUID));
assertEquals(drmInitData.hashCode(), testInitData.hashCode()); assertEquals(DATA_2, testInitData.get(PLAYREADY_UUID));
assertEquals(DATA_UNIVERSAL, testInitData.get(C.UUID_NIL));
// Different data should affect equality. // Passing the scheme data in reverse order shouldn't affect equality.
testInitData = new DrmInitData.Universal(DATA_2); testInitData = new DrmInitData(DATA_UNIVERSAL, DATA_2, DATA_1);
MoreAsserts.assertNotEqual(drmInitData, testInitData); 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));
} }
} }

View File

@ -21,7 +21,7 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format; import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.drm.DrmInitData; 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.ChunkIndex;
import com.google.android.exoplayer.extractor.mkv.StreamBuilder.ContentEncodingSettings; import com.google.android.exoplayer.extractor.mkv.StreamBuilder.ContentEncodingSettings;
import com.google.android.exoplayer.testutil.FakeExtractorOutput; import com.google.android.exoplayer.testutil.FakeExtractorOutput;
@ -237,10 +237,10 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase {
assertSeekMap(DEFAULT_TIMECODE_SCALE, 1); assertSeekMap(DEFAULT_TIMECODE_SCALE, 1);
DrmInitData drmInitData = extractorOutput.drmInitData; DrmInitData drmInitData = extractorOutput.drmInitData;
assertNotNull(drmInitData); assertNotNull(drmInitData);
SchemeInitData widevineInitData = drmInitData.get(WIDEVINE_UUID); SchemeData widevineInitData = drmInitData.get(WIDEVINE_UUID);
assertEquals(MimeTypes.VIDEO_WEBM, widevineInitData.mimeType); assertEquals(MimeTypes.VIDEO_WEBM, widevineInitData.mimeType);
android.test.MoreAsserts.assertEquals(TEST_ENCRYPTION_KEY_ID, widevineInitData.data); 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); assertEquals(MimeTypes.VIDEO_WEBM, zeroInitData.mimeType);
android.test.MoreAsserts.assertEquals(TEST_ENCRYPTION_KEY_ID, zeroInitData.data); android.test.MoreAsserts.assertEquals(TEST_ENCRYPTION_KEY_ID, zeroInitData.data);
} }

View File

@ -20,6 +20,8 @@ import com.google.android.exoplayer.util.Util;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.MediaCodec; import android.media.MediaCodec;
import java.util.UUID;
/** /**
* Defines constants that are generally useful throughout the library. * 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 public static final int DEFAULT_MUXED_BUFFER_SIZE = DEFAULT_VIDEO_BUFFER_SIZE
+ DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE; + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE;
/**
* The Nil UUID as defined by
* <a href="https://tools.ietf.org/html/rfc4122#section-4.1.7">RFC4122</a>.
*/
public static final UUID UUID_NIL = new UUID(0L, 0L);
private C() {} private C() {}
} }

View File

@ -16,8 +16,7 @@
package com.google.android.exoplayer; package com.google.android.exoplayer;
import com.google.android.exoplayer.drm.DrmInitData; 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.drm.DrmInitData.UuidSchemeInitDataTuple;
import com.google.android.exoplayer.extractor.ExtractorSampleSource; import com.google.android.exoplayer.extractor.ExtractorSampleSource;
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil; import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
@ -287,15 +286,13 @@ public final class FrameworkSampleSource implements SampleSource {
if (psshInfo == null || psshInfo.isEmpty()) { if (psshInfo == null || psshInfo.isEmpty()) {
return null; return null;
} }
UuidSchemeInitDataTuple[] uuidSchemeInitDataTuples = SchemeData[] schemeDatas = new SchemeData[psshInfo.size()];
new UuidSchemeInitDataTuple[psshInfo.size()];
int count = 0; int count = 0;
for (UUID uuid : psshInfo.keySet()) { for (UUID uuid : psshInfo.keySet()) {
byte[] psshAtom = PsshAtomUtil.buildPsshAtom(uuid, psshInfo.get(uuid)); byte[] psshAtom = PsshAtomUtil.buildPsshAtom(uuid, psshInfo.get(uuid));
SchemeInitData schemeData = new SchemeInitData(MimeTypes.VIDEO_MP4, psshAtom); schemeDatas[count++] = new SchemeData(uuid, MimeTypes.VIDEO_MP4, psshAtom);
uuidSchemeInitDataTuples[count++] = new UuidSchemeInitDataTuple(uuid, schemeData);
} }
return new DrmInitData.Mapped(uuidSchemeInitDataTuples); return new DrmInitData(schemeDatas);
} }
private void seekToUsInternal(long positionUs, boolean force) { private void seekToUsInternal(long positionUs, boolean force) {

View File

@ -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.RangedUri;
import com.google.android.exoplayer.dash.mpd.Representation; import com.google.android.exoplayer.dash.mpd.Representation;
import com.google.android.exoplayer.drm.DrmInitData; 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.ChunkIndex;
import com.google.android.exoplayer.extractor.SeekMap; import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer.extractor.mkv.MatroskaExtractor;
@ -344,19 +344,17 @@ public class DashChunkSource implements ChunkSource {
} }
private static DrmInitData getDrmInitData(AdaptationSet adaptationSet) { private static DrmInitData getDrmInitData(AdaptationSet adaptationSet) {
ArrayList<UuidSchemeInitDataTuple> uuidSchemeInitDataTuples = null; ArrayList<SchemeData> schemeDatas = null;
for (int i = 0; i < adaptationSet.contentProtections.size(); i++) { for (int i = 0; i < adaptationSet.contentProtections.size(); i++) {
ContentProtection contentProtection = adaptationSet.contentProtections.get(i); ContentProtection contentProtection = adaptationSet.contentProtections.get(i);
if (contentProtection.uuid != null && contentProtection.data != null) { if (contentProtection.schemeData != null) {
if (uuidSchemeInitDataTuples == null) { if (schemeDatas == null) {
uuidSchemeInitDataTuples = new ArrayList<UuidSchemeInitDataTuple>(); schemeDatas = new ArrayList<SchemeData>();
} }
uuidSchemeInitDataTuples.add( schemeDatas.add(contentProtection.schemeData);
new UuidSchemeInitDataTuple(contentProtection.uuid, contentProtection.data));
} }
} }
return uuidSchemeInitDataTuples == null ? null return schemeDatas == null ? null : new DrmInitData(schemeDatas);
: new DrmInitData.Mapped(uuidSchemeInitDataTuples);
} }
private static long getPeriodDurationUs(MediaPresentationDescription manifest, int index) { private static long getPeriodDurationUs(MediaPresentationDescription manifest, int index) {

View File

@ -15,12 +15,10 @@
*/ */
package com.google.android.exoplayer.dash.mpd; 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.Assertions;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import java.util.UUID;
/** /**
* Represents a ContentProtection tag in an AdaptationSet. * Represents a ContentProtection tag in an AdaptationSet.
*/ */
@ -31,25 +29,18 @@ public class ContentProtection {
*/ */
public final String schemeUriId; 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. * Protection scheme specific initialization data. May be null.
*/ */
public final SchemeInitData data; public final SchemeData schemeData;
/** /**
* @param schemeUriId Identifies the content protection scheme. * @param schemeUriId Identifies the content protection scheme.
* @param uuid The UUID of the protection scheme, if known. May be null. * @param schemeData Protection scheme specific initialization data. May be null.
* @param data 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.schemeUriId = Assertions.checkNotNull(schemeUriId);
this.uuid = uuid; this.schemeData = schemeData;
this.data = data;
} }
@Override @Override
@ -62,17 +53,12 @@ public class ContentProtection {
} }
ContentProtection other = (ContentProtection) obj; ContentProtection other = (ContentProtection) obj;
return schemeUriId.equals(other.schemeUriId) return schemeUriId.equals(other.schemeUriId) && Util.areEqual(schemeData, other.schemeData);
&& Util.areEqual(uuid, other.uuid)
&& Util.areEqual(data, other.data);
} }
@Override @Override
public int hashCode() { public int hashCode() {
int hashCode = schemeUriId.hashCode(); return (31 * schemeUriId.hashCode()) + (schemeData != null ? schemeData.hashCode() : 0);
hashCode = (31 * hashCode) + (uuid != null ? uuid.hashCode() : 0);
hashCode = (31 * hashCode) + (data != null ? data.hashCode() : 0);
return hashCode;
} }
} }

View File

@ -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.SegmentTemplate;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement; import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase; 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.extractor.mp4.PsshAtomUtil;
import com.google.android.exoplayer.upstream.UriLoadable; import com.google.android.exoplayer.upstream.UriLoadable;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
@ -314,29 +314,29 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
protected ContentProtection parseContentProtection(XmlPullParser xpp) protected ContentProtection parseContentProtection(XmlPullParser xpp)
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri");
UUID uuid = null; SchemeData schemeData = null;
SchemeInitData data = null;
boolean seenPsshElement = false; boolean seenPsshElement = false;
do { do {
xpp.next(); xpp.next();
// The cenc:pssh element is defined in 23001-7:2015. // The cenc:pssh element is defined in 23001-7:2015.
if (ParserUtil.isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) { if (ParserUtil.isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) {
seenPsshElement = true; seenPsshElement = true;
data = new SchemeInitData(MimeTypes.VIDEO_MP4, byte[] data = Base64.decode(xpp.getText(), Base64.DEFAULT);
Base64.decode(xpp.getText(), Base64.DEFAULT)); UUID uuid = PsshAtomUtil.parseUuid(data);
uuid = PsshAtomUtil.parseUuid(data.data); if (uuid != null) {
schemeData = new SchemeData(uuid, MimeTypes.VIDEO_MP4, data);
}
} }
} while (!ParserUtil.isEndTag(xpp, "ContentProtection")); } while (!ParserUtil.isEndTag(xpp, "ContentProtection"));
if (seenPsshElement && uuid == null) { if (seenPsshElement && schemeData == null) {
Log.w(TAG, "Skipped unsupported ContentProtection element"); Log.w(TAG, "Skipped unsupported ContentProtection element");
return null; return null;
} }
return buildContentProtection(schemeIdUri, uuid, data); return buildContentProtection(schemeIdUri, schemeData);
} }
protected ContentProtection buildContentProtection(String schemeIdUri, UUID uuid, protected ContentProtection buildContentProtection(String schemeIdUri, SchemeData schemeData) {
SchemeInitData data) { return new ContentProtection(schemeIdUri, schemeData);
return new ContentProtection(schemeIdUri, uuid, data);
} }
/** /**

View File

@ -15,119 +15,103 @@
*/ */
package com.google.android.exoplayer.drm; 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.Assertions;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
/** /**
* Encapsulates DRM initialization data for possibly multiple DRM schemes. * Encapsulates DRM initialization data for possibly multiple DRM schemes.
*/ */
public interface DrmInitData { public final class DrmInitData implements Comparator<SchemeData> {
/** private final SchemeData[] schemeDatas;
* 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);
/** // Lazily initialized hashcode.
* A {@link DrmInitData} implementation that maps UUID onto scheme specific data. private int hashCode;
*/
final class Mapped implements DrmInitData {
private final UuidSchemeInitDataTuple[] schemeDatas; public DrmInitData(List<SchemeData> schemeDatas) {
this(false, schemeDatas.toArray(new SchemeData[schemeDatas.size()]));
}
// Lazily initialized hashcode. public DrmInitData(SchemeData... schemeDatas) {
private int hashCode; this(true, schemeDatas);
}
public Mapped(UuidSchemeInitDataTuple... schemeDatas) { private DrmInitData(boolean cloneSchemeDatas, SchemeData... schemeDatas) {
this.schemeDatas = schemeDatas.clone(); if (cloneSchemeDatas) {
Arrays.sort(this.schemeDatas); // Required for correct equals and hashcode implementations. schemeDatas = schemeDatas.clone();
} }
// Sorting ensures that universal scheme data(i.e. data that applies to all schemes) is matched
public Mapped(List<UuidSchemeInitDataTuple> schemeDatas) { // last. It's also required by the equals and hashcode implementations.
this.schemeDatas = schemeDatas.toArray(new UuidSchemeInitDataTuple[schemeDatas.size()]); Arrays.sort(schemeDatas, this);
Arrays.sort(this.schemeDatas); // Required for correct equals and hashcode implementations. // Check for no duplicates.
} for (int i = 1; i < schemeDatas.length; i++) {
if (schemeDatas[i - 1].uuid.equals(schemeDatas[i].uuid)) {
@Override throw new IllegalArgumentException("Duplicate data for uuid: " + schemeDatas[i].uuid);
public SchemeInitData get(UUID schemeUuid) {
for (UuidSchemeInitDataTuple schemeData : schemeDatas) {
if (schemeUuid.equals(schemeData.uuid)) {
return schemeData.data;
}
} }
return null;
} }
this.schemeDatas = schemeDatas;
@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);
}
} }
/** /**
* 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 { public SchemeData get(UUID uuid) {
for (SchemeData schemeData : schemeDatas) {
private final SchemeInitData data; if (schemeData.matches(uuid)) {
return schemeData;
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;
} }
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. * Scheme initialization data.
*/ */
final class SchemeInitData { public static final class SchemeData {
// Lazily initialized hashcode. // Lazily initialized hashcode.
private int 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}. * The mimeType of {@link #data}.
*/ */
@ -138,73 +122,51 @@ public interface DrmInitData {
public final byte[] data; 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 mimeType The mimeType of the initialization data.
* @param data 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.mimeType = Assertions.checkNotNull(mimeType);
this.data = Assertions.checkNotNull(data); 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 @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (!(obj instanceof SchemeInitData)) { if (!(obj instanceof SchemeData)) {
return false; return false;
} }
if (obj == this) { if (obj == this) {
return true; return true;
} }
SchemeInitData other = (SchemeInitData) obj; SchemeData other = (SchemeData) obj;
return mimeType.equals(other.mimeType) && Arrays.equals(data, other.data); return mimeType.equals(other.mimeType) && Util.areEqual(uuid, other.uuid)
&& Arrays.equals(data, other.data);
} }
@Override @Override
public int hashCode() { public int hashCode() {
if (hashCode == 0) { 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; return hashCode;
} }
} }
/**
* A tuple consisting of a {@link UUID} and a {@link SchemeInitData}.
* <p>
* Implements {@link Comparable} based on {@link UUID} ordering.
*/
final class UuidSchemeInitDataTuple implements Comparable<UuidSchemeInitDataTuple> {
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);
}
}
} }

View File

@ -15,7 +15,7 @@
*/ */
package com.google.android.exoplayer.drm; 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.extractor.mp4.PsshAtomUtil;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
@ -104,7 +104,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
private int state; private int state;
private MediaCrypto mediaCrypto; private MediaCrypto mediaCrypto;
private Exception lastException; private Exception lastException;
private SchemeInitData schemeInitData; private SchemeData schemeData;
private byte[] sessionId; private byte[] sessionId;
/** /**
@ -273,19 +273,19 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
requestHandlerThread.start(); requestHandlerThread.start();
postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper()); postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper());
} }
if (schemeInitData == null) { if (schemeData == null) {
schemeInitData = drmInitData.get(uuid); schemeData = drmInitData.get(uuid);
if (schemeInitData == null) { if (schemeData == null) {
onError(new IllegalStateException("Media does not support uuid: " + uuid)); onError(new IllegalStateException("Media does not support uuid: " + uuid));
return; return;
} }
if (Util.SDK_INT < 21) { if (Util.SDK_INT < 21) {
// Prior to L the Widevine CDM required data to be extracted from the PSSH atom. // 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) { if (psshData == null) {
// Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged. // Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged.
} else { } 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; postRequestHandler = null;
requestHandlerThread.quit(); requestHandlerThread.quit();
requestHandlerThread = null; requestHandlerThread = null;
schemeInitData = null; schemeData = null;
mediaCrypto = null; mediaCrypto = null;
lastException = null; lastException = null;
if (sessionId != null) { if (sessionId != null) {
@ -368,7 +368,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
private void postKeyRequest() { private void postKeyRequest() {
KeyRequest keyRequest; KeyRequest keyRequest;
try { try {
keyRequest = mediaDrm.getKeyRequest(sessionId, schemeInitData.data, schemeInitData.mimeType, keyRequest = mediaDrm.getKeyRequest(sessionId, schemeData.data, schemeData.mimeType,
MediaDrm.KEY_TYPE_STREAMING, optionalKeyRequestParameters); MediaDrm.KEY_TYPE_STREAMING, optionalKeyRequestParameters);
postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget(); postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget();
} catch (NotProvisionedException e) { } catch (NotProvisionedException e) {

View File

@ -19,7 +19,7 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format; import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.drm.DrmInitData; 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.ChunkIndex;
import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput; 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"); throw new ParserException("Encrypted Track found but ContentEncKeyID was not found");
} }
if (!sentDrmInitData) { if (!sentDrmInitData) {
extractorOutput.drmInitData(new DrmInitData.Universal( extractorOutput.drmInitData(new DrmInitData(
new SchemeInitData(MimeTypes.VIDEO_WEBM, currentTrack.encryptionKeyId))); new SchemeData(C.UUID_NIL, MimeTypes.VIDEO_WEBM, currentTrack.encryptionKeyId)));
sentDrmInitData = true; sentDrmInitData = true;
} }
} }

View File

@ -18,8 +18,7 @@ package com.google.android.exoplayer.extractor.mp4;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.drm.DrmInitData; 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.drm.DrmInitData.UuidSchemeInitDataTuple;
import com.google.android.exoplayer.extractor.ChunkIndex; import com.google.android.exoplayer.extractor.ChunkIndex;
import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.ExtractorInput;
@ -320,25 +319,24 @@ public final class FragmentedMp4Extractor implements Extractor {
List<Atom.LeafAtom> moovLeafChildren = moov.leafChildren; List<Atom.LeafAtom> moovLeafChildren = moov.leafChildren;
int moovLeafChildrenSize = moovLeafChildren.size(); int moovLeafChildrenSize = moovLeafChildren.size();
ArrayList<UuidSchemeInitDataTuple> uuidSchemeInitDataTuples = null; ArrayList<SchemeData> schemeDatas = null;
for (int i = 0; i < moovLeafChildrenSize; i++) { for (int i = 0; i < moovLeafChildrenSize; i++) {
LeafAtom child = moovLeafChildren.get(i); LeafAtom child = moovLeafChildren.get(i);
if (child.type == Atom.TYPE_pssh) { if (child.type == Atom.TYPE_pssh) {
if (uuidSchemeInitDataTuples == null) { if (schemeDatas == null) {
uuidSchemeInitDataTuples = new ArrayList<UuidSchemeInitDataTuple>(); schemeDatas = new ArrayList<SchemeData>();
} }
byte[] psshData = child.data.data; byte[] psshData = child.data.data;
UUID uuid = PsshAtomUtil.parseUuid(psshData); UUID uuid = PsshAtomUtil.parseUuid(psshData);
if (uuid == null) { if (uuid == null) {
Log.w(TAG, "Skipped pssh atom (failed to extract uuid)"); Log.w(TAG, "Skipped pssh atom (failed to extract uuid)");
} else { } else {
uuidSchemeInitDataTuples.add(new UuidSchemeInitDataTuple(uuid, schemeDatas.add(new SchemeData(uuid, MimeTypes.VIDEO_MP4, psshData));
new SchemeInitData(MimeTypes.VIDEO_MP4, psshData)));
} }
} }
} }
if (uuidSchemeInitDataTuples != null) { if (schemeDatas != null) {
extractorOutput.drmInitData(new DrmInitData.Mapped(uuidSchemeInitDataTuples)); extractorOutput.drmInitData(new DrmInitData(schemeDatas));
} }
// Read declaration of track fragments in the Moov box. // Read declaration of track fragments in the Moov box.

View File

@ -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;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.drm.DrmInitData; 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.drm.DrmInitData.UuidSchemeInitDataTuple;
import com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox; import com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
@ -70,7 +69,7 @@ public final class SmoothStreamingSampleSource implements SampleSource {
private long durationUs; private long durationUs;
private SmoothStreamingManifest currentManifest; private SmoothStreamingManifest currentManifest;
private TrackEncryptionBox[] trackEncryptionBoxes; private TrackEncryptionBox[] trackEncryptionBoxes;
private DrmInitData.Mapped drmInitData; private DrmInitData drmInitData;
private TrackGroupArray trackGroups; private TrackGroupArray trackGroups;
private int[] trackGroupElementIndices; private int[] trackGroupElementIndices;
private boolean pendingReset; private boolean pendingReset;
@ -120,8 +119,8 @@ public final class SmoothStreamingSampleSource implements SampleSource {
byte[] keyId = getProtectionElementKeyId(protectionElement.data); byte[] keyId = getProtectionElementKeyId(protectionElement.data);
trackEncryptionBoxes = new TrackEncryptionBox[1]; trackEncryptionBoxes = new TrackEncryptionBox[1];
trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId); trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId);
drmInitData = new DrmInitData.Mapped(new UuidSchemeInitDataTuple(protectionElement.uuid, drmInitData = new DrmInitData(
new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data))); new SchemeData(protectionElement.uuid, MimeTypes.VIDEO_MP4, protectionElement.data));
} }
return true; return true;
} }