mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Push DrmInitData into Format.
- This means DrmInitData is propagated through sample queues (i.e. is effectively attached to every sample, so we can see when it changes when reading from the queue). - It also allows different DrmInitData per track, which is possible in muxed MKV/WebM, and per Representation for DASH, although we wont be able to seamlessly adapt in the latter case. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=121821928
This commit is contained in:
parent
e284d6166a
commit
9f2707cfa3
47 changed files with 250 additions and 507 deletions
|
|
@ -105,8 +105,8 @@ public final class FlacExtractor implements Extractor {
|
|||
});
|
||||
|
||||
Format mediaFormat = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW,
|
||||
streamInfo.bitRate(), Format.NO_VALUE,
|
||||
streamInfo.channels, streamInfo.sampleRate, null, null);
|
||||
streamInfo.bitRate(), Format.NO_VALUE, streamInfo.channels, streamInfo.sampleRate, null,
|
||||
null, null);
|
||||
trackOutput.format(mediaFormat);
|
||||
|
||||
outputBuffer = new ParsableByteArray(streamInfo.maxDecodedFrameSize());
|
||||
|
|
|
|||
|
|
@ -46,17 +46,17 @@ public final class FormatTest extends TestCase {
|
|||
initData.add(initData2);
|
||||
|
||||
testConversionToFrameworkMediaFormatV16(Format.createVideoSampleFormat(
|
||||
null, "video/xyz", 5000, 102400, 1280, 720, 30, initData));
|
||||
null, "video/xyz", 5000, 102400, 1280, 720, 30, initData, null));
|
||||
testConversionToFrameworkMediaFormatV16(Format.createVideoSampleFormat(
|
||||
null, "video/xyz", 5000, Format.NO_VALUE, 1280, 720, 30, null));
|
||||
null, "video/xyz", 5000, Format.NO_VALUE, 1280, 720, 30, null, null));
|
||||
testConversionToFrameworkMediaFormatV16(Format.createAudioSampleFormat(
|
||||
null, "audio/xyz", 500, 128, 5, 44100, initData, null));
|
||||
null, "audio/xyz", 500, 128, 5, 44100, initData, null, null));
|
||||
testConversionToFrameworkMediaFormatV16(Format.createAudioSampleFormat(
|
||||
null, "audio/xyz", 500, Format.NO_VALUE, 5, 44100, null, null));
|
||||
null, "audio/xyz", 500, Format.NO_VALUE, 5, 44100, null, null, null));
|
||||
testConversionToFrameworkMediaFormatV16(
|
||||
Format.createTextSampleFormat(null, "text/xyz", Format.NO_VALUE, "eng"));
|
||||
Format.createTextSampleFormat(null, "text/xyz", Format.NO_VALUE, "eng", null));
|
||||
testConversionToFrameworkMediaFormatV16(
|
||||
Format.createTextSampleFormat(null, "text/xyz", Format.NO_VALUE, null));
|
||||
Format.createTextSampleFormat(null, "text/xyz", Format.NO_VALUE, null, null));
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase {
|
|||
private static final byte SECOND_AUDIO_TRACK_NUMBER = 0x05;
|
||||
|
||||
private static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
|
||||
private static final UUID ZERO_UUID = new UUID(0, 0);
|
||||
private static final String MATROSKA_DOC_TYPE = "matroska";
|
||||
private static final String WEBM_DOC_TYPE = "webm";
|
||||
|
||||
|
|
@ -234,15 +233,8 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase {
|
|||
|
||||
assertTracksEnded();
|
||||
assertVp9VideoFormat(VIDEO_TRACK_NUMBER);
|
||||
assertDrmInitData(VIDEO_TRACK_NUMBER);
|
||||
assertSeekMap(DEFAULT_TIMECODE_SCALE, 1);
|
||||
DrmInitData drmInitData = extractorOutput.drmInitData;
|
||||
assertNotNull(drmInitData);
|
||||
SchemeData widevineInitData = drmInitData.get(WIDEVINE_UUID);
|
||||
assertEquals(MimeTypes.VIDEO_WEBM, widevineInitData.mimeType);
|
||||
android.test.MoreAsserts.assertEquals(TEST_ENCRYPTION_KEY_ID, widevineInitData.data);
|
||||
SchemeData zeroInitData = drmInitData.get(ZERO_UUID);
|
||||
assertEquals(MimeTypes.VIDEO_WEBM, zeroInitData.mimeType);
|
||||
android.test.MoreAsserts.assertEquals(TEST_ENCRYPTION_KEY_ID, zeroInitData.data);
|
||||
}
|
||||
|
||||
public void testPrepareThreeCuePoints() throws IOException, InterruptedException {
|
||||
|
|
@ -755,6 +747,17 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
private void assertDrmInitData(int trackNumber) {
|
||||
DrmInitData drmInitData = getTrackOutput(trackNumber).format.drmInitData;
|
||||
assertNotNull(drmInitData);
|
||||
SchemeData widevineInitData = drmInitData.get(WIDEVINE_UUID);
|
||||
assertEquals(MimeTypes.VIDEO_WEBM, widevineInitData.mimeType);
|
||||
android.test.MoreAsserts.assertEquals(TEST_ENCRYPTION_KEY_ID, widevineInitData.data);
|
||||
SchemeData zeroInitData = drmInitData.get(C.UUID_NIL);
|
||||
assertEquals(MimeTypes.VIDEO_WEBM, zeroInitData.mimeType);
|
||||
android.test.MoreAsserts.assertEquals(TEST_ENCRYPTION_KEY_ID, zeroInitData.data);
|
||||
}
|
||||
|
||||
private void assertSeekMap(int timecodeScale, int cuePointCount) {
|
||||
ChunkIndex index = (ChunkIndex) extractorOutput.seekMap;
|
||||
assertEquals(cuePointCount, index.length);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.testutil;
|
||||
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
||||
import com.google.android.exoplayer.extractor.SeekMap;
|
||||
|
||||
|
|
@ -36,7 +35,6 @@ public final class FakeExtractorOutput implements ExtractorOutput {
|
|||
public int numberOfTracks;
|
||||
public boolean tracksEnded;
|
||||
public SeekMap seekMap;
|
||||
public DrmInitData drmInitData;
|
||||
|
||||
public FakeExtractorOutput() {
|
||||
this(false);
|
||||
|
|
@ -70,11 +68,6 @@ public final class FakeExtractorOutput implements ExtractorOutput {
|
|||
this.seekMap = seekMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drmInitData(DrmInitData drmInitData) {
|
||||
this.drmInitData = drmInitData;
|
||||
}
|
||||
|
||||
public void assertEquals(FakeExtractorOutput expected) {
|
||||
Assert.assertEquals(expected.numberOfTracks, numberOfTracks);
|
||||
Assert.assertEquals(expected.tracksEnded, tracksEnded);
|
||||
|
|
@ -87,13 +80,6 @@ public final class FakeExtractorOutput implements ExtractorOutput {
|
|||
Assert.assertEquals(expected.seekMap.isSeekable(), seekMap.isSeekable());
|
||||
Assert.assertEquals(expected.seekMap.getPosition(0), seekMap.getPosition(0));
|
||||
}
|
||||
if (expected.drmInitData == null) {
|
||||
Assert.assertNull(drmInitData);
|
||||
} else {
|
||||
// TODO: Bulk up this check if possible.
|
||||
Assert.assertNotNull(drmInitData);
|
||||
Assert.assertEquals(expected.drmInitData.getClass(), drmInitData.getClass());
|
||||
}
|
||||
for (int i = 0; i < numberOfTracks; i++) {
|
||||
Assert.assertEquals(expected.trackOutputs.keyAt(i), trackOutputs.keyAt(i));
|
||||
trackOutputs.valueAt(i).assertEquals(expected.trackOutputs.valueAt(i));
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package com.google.android.exoplayer;
|
||||
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
|
@ -89,6 +90,10 @@ public final class Format {
|
|||
* if initialization data is not required.
|
||||
*/
|
||||
public final List<byte[]> initializationData;
|
||||
/**
|
||||
* DRM initialization data if the stream is protected, or null otherwise.
|
||||
*/
|
||||
public final DrmInitData drmInitData;
|
||||
|
||||
// Video specific.
|
||||
|
||||
|
|
@ -163,21 +168,22 @@ public final class Format {
|
|||
List<byte[]> initializationData) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, width, height,
|
||||
frameRate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null,
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, false);
|
||||
}
|
||||
|
||||
public static Format createVideoSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
int maxInputSize, int width, int height, float frameRate, List<byte[]> initializationData) {
|
||||
return createVideoSampleFormat(id, sampleMimeType, bitrate, maxInputSize, width, height,
|
||||
frameRate, initializationData, NO_VALUE, NO_VALUE);
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, null, false);
|
||||
}
|
||||
|
||||
public static Format createVideoSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
int maxInputSize, int width, int height, float frameRate, List<byte[]> initializationData,
|
||||
int rotationDegrees, float pixelWidthHeightRatio) {
|
||||
DrmInitData drmInitData) {
|
||||
return createVideoSampleFormat(id, sampleMimeType, bitrate, maxInputSize, width, height,
|
||||
frameRate, initializationData, NO_VALUE, NO_VALUE, drmInitData);
|
||||
}
|
||||
|
||||
public static Format createVideoSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
int maxInputSize, int width, int height, float frameRate, List<byte[]> initializationData,
|
||||
int rotationDegrees, float pixelWidthHeightRatio, DrmInitData drmInitData) {
|
||||
return new Format(id, null, sampleMimeType, bitrate, maxInputSize, width, height, frameRate,
|
||||
rotationDegrees, pixelWidthHeightRatio, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null,
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, false);
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false);
|
||||
}
|
||||
|
||||
// Audio.
|
||||
|
|
@ -187,22 +193,22 @@ public final class Format {
|
|||
List<byte[]> initializationData, String language) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, channelCount, sampleRate, NO_VALUE, NO_VALUE, language,
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, false);
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, null, false);
|
||||
}
|
||||
|
||||
public static Format createAudioSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
int maxInputSize, int channelCount, int sampleRate, List<byte[]> initializationData,
|
||||
String language) {
|
||||
String language, DrmInitData drmInitData) {
|
||||
return createAudioSampleFormat(id, sampleMimeType, bitrate, maxInputSize, channelCount,
|
||||
sampleRate, NO_VALUE, NO_VALUE, initializationData, language);
|
||||
sampleRate, NO_VALUE, NO_VALUE, initializationData, language, drmInitData);
|
||||
}
|
||||
|
||||
public static Format createAudioSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
int maxInputSize, int channelCount, int sampleRate, int encoderDelay, int encoderPadding,
|
||||
List<byte[]> initializationData, String language) {
|
||||
List<byte[]> initializationData, String language, DrmInitData drmInitData) {
|
||||
return new Format(id, null, sampleMimeType, bitrate, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, channelCount, sampleRate, encoderDelay, encoderPadding, language,
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, false);
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false);
|
||||
}
|
||||
|
||||
// Text.
|
||||
|
|
@ -211,28 +217,29 @@ public final class Format {
|
|||
String sampleMimeType, int bitrate, String language) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language,
|
||||
OFFSET_SAMPLE_RELATIVE, null, false);
|
||||
OFFSET_SAMPLE_RELATIVE, null, null, false);
|
||||
}
|
||||
|
||||
public static Format createTextSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
String language) {
|
||||
return createTextSampleFormat(id, sampleMimeType, bitrate, language, OFFSET_SAMPLE_RELATIVE);
|
||||
String language, DrmInitData drmInitData) {
|
||||
return createTextSampleFormat(id, sampleMimeType, bitrate, language, drmInitData,
|
||||
OFFSET_SAMPLE_RELATIVE);
|
||||
}
|
||||
|
||||
public static Format createTextSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
String language, long subsampleOffsetUs) {
|
||||
String language, DrmInitData drmInitData, long subsampleOffsetUs) {
|
||||
return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language,
|
||||
subsampleOffsetUs, null, false);
|
||||
subsampleOffsetUs, null, drmInitData, false);
|
||||
}
|
||||
|
||||
// Image.
|
||||
|
||||
public static Format createImageSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
List<byte[]> initializationData, String language) {
|
||||
List<byte[]> initializationData, String language, DrmInitData drmInitData) {
|
||||
return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language,
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, false);
|
||||
OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, false);
|
||||
}
|
||||
|
||||
// Generic.
|
||||
|
|
@ -241,20 +248,21 @@ public final class Format {
|
|||
String sampleMimeType, int bitrate) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null,
|
||||
OFFSET_SAMPLE_RELATIVE, null, false);
|
||||
OFFSET_SAMPLE_RELATIVE, null, null, false);
|
||||
}
|
||||
|
||||
public static Format createSampleFormat(String id, String sampleMimeType, int bitrate) {
|
||||
public static Format createSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||
DrmInitData drmInitData) {
|
||||
return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE,
|
||||
null, false);
|
||||
null, drmInitData, false);
|
||||
}
|
||||
|
||||
/* package */ Format(String id, String containerMimeType, String sampleMimeType,
|
||||
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
|
||||
float pixelWidthHeightRatio, int channelCount, int sampleRate, int encoderDelay,
|
||||
int encoderPadding, String language, long subsampleOffsetUs, List<byte[]> initializationData,
|
||||
boolean requiresSecureDecryption) {
|
||||
DrmInitData drmInitData, boolean requiresSecureDecryption) {
|
||||
this.id = id;
|
||||
this.containerMimeType = containerMimeType;
|
||||
this.sampleMimeType = sampleMimeType;
|
||||
|
|
@ -273,6 +281,7 @@ public final class Format {
|
|||
this.subsampleOffsetUs = subsampleOffsetUs;
|
||||
this.initializationData = initializationData == null ? Collections.<byte[]>emptyList()
|
||||
: initializationData;
|
||||
this.drmInitData = drmInitData;
|
||||
this.requiresSecureDecryption = requiresSecureDecryption;
|
||||
}
|
||||
|
||||
|
|
@ -280,14 +289,14 @@ public final class Format {
|
|||
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
|
||||
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
|
||||
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
|
||||
requiresSecureDecryption);
|
||||
drmInitData, requiresSecureDecryption);
|
||||
}
|
||||
|
||||
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
|
||||
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
|
||||
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
|
||||
requiresSecureDecryption);
|
||||
drmInitData, requiresSecureDecryption);
|
||||
}
|
||||
|
||||
public Format copyWithContainerInfo(String id, int bitrate, int width, int height,
|
||||
|
|
@ -295,14 +304,21 @@ public final class Format {
|
|||
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
|
||||
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
|
||||
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
|
||||
requiresSecureDecryption);
|
||||
drmInitData, requiresSecureDecryption);
|
||||
}
|
||||
|
||||
public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
|
||||
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
|
||||
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
|
||||
requiresSecureDecryption);
|
||||
drmInitData, requiresSecureDecryption);
|
||||
}
|
||||
|
||||
public Format copyWithDrmInitData(DrmInitData drmInitData) {
|
||||
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
|
||||
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
|
||||
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
|
||||
drmInitData, requiresSecureDecryption);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -366,6 +382,7 @@ public final class Format {
|
|||
result = 31 * result + encoderDelay;
|
||||
result = 31 * result + encoderPadding;
|
||||
result = 31 * result + (language == null ? 0 : language.hashCode());
|
||||
result = 31 * result + (drmInitData == null ? 0 : drmInitData.hashCode());
|
||||
hashCode = result;
|
||||
}
|
||||
return hashCode;
|
||||
|
|
@ -391,6 +408,7 @@ public final class Format {
|
|||
|| !Util.areEqual(id, other.id) || !Util.areEqual(language, other.language)
|
||||
|| !Util.areEqual(containerMimeType, other.containerMimeType)
|
||||
|| !Util.areEqual(sampleMimeType, other.sampleMimeType)
|
||||
|| !Util.areEqual(drmInitData, other.drmInitData)
|
||||
|| initializationData.size() != other.initializationData.size()) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@
|
|||
*/
|
||||
package com.google.android.exoplayer;
|
||||
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
|
||||
/**
|
||||
* Holds a {@link Format} and corresponding drm scheme initialization data.
|
||||
*/
|
||||
|
|
@ -26,9 +24,5 @@ public final class FormatHolder {
|
|||
* The format of the media.
|
||||
*/
|
||||
public Format format;
|
||||
/**
|
||||
* Initialization data for drm schemes supported by the media. Null if the media is not encrypted.
|
||||
*/
|
||||
public DrmInitData drmInitData;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,12 +136,13 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||
}
|
||||
trackStates = new int[extractor.getTrackCount()];
|
||||
TrackGroup[] trackArray = new TrackGroup[trackStates.length];
|
||||
DrmInitData drmInitData = Util.SDK_INT >= 18 ? getDrmInitDataV18() : null;
|
||||
for (int i = 0; i < trackStates.length; i++) {
|
||||
MediaFormat format = extractor.getTrackFormat(i);
|
||||
if (format.containsKey(MediaFormat.KEY_DURATION)) {
|
||||
durationUs = Math.max(durationUs, format.getLong(MediaFormat.KEY_DURATION));
|
||||
}
|
||||
trackArray[i] = new TrackGroup(createFormat(i, format));
|
||||
trackArray[i] = new TrackGroup(createFormat(i, format, drmInitData));
|
||||
}
|
||||
tracks = new TrackGroupArray(trackArray);
|
||||
prepared = true;
|
||||
|
|
@ -244,7 +245,6 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||
}
|
||||
if (trackStates[track] != TRACK_STATE_FORMAT_SENT) {
|
||||
formatHolder.format = tracks.get(track).getFormat(0);
|
||||
formatHolder.drmInitData = Util.SDK_INT >= 18 ? getDrmInitDataV18() : null;
|
||||
trackStates[track] = TRACK_STATE_FORMAT_SENT;
|
||||
return TrackStream.FORMAT_READ;
|
||||
}
|
||||
|
|
@ -307,7 +307,7 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private static Format createFormat(int index, MediaFormat mediaFormat) {
|
||||
private static Format createFormat(int index, MediaFormat mediaFormat, DrmInitData drmInitData) {
|
||||
String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
|
||||
String language = getOptionalStringV16(mediaFormat, MediaFormat.KEY_LANGUAGE);
|
||||
int maxInputSize = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE);
|
||||
|
|
@ -336,7 +336,7 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||
Format format = new Format(Integer.toString(index), null, mimeType, Format.NO_VALUE,
|
||||
maxInputSize, width, height, frameRate, rotationDegrees, Format.NO_VALUE, channelCount,
|
||||
sampleRate, encoderDelay, encoderPadding, language, Format.OFFSET_SAMPLE_RELATIVE,
|
||||
initializationData, false);
|
||||
initializationData, drmInitData, false);
|
||||
format.setFrameworkMediaFormatV16(mediaFormat);
|
||||
return format;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
package com.google.android.exoplayer;
|
||||
|
||||
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
import com.google.android.exoplayer.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.NalUnitUtil;
|
||||
|
|
@ -171,7 +170,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
|
|||
protected final Handler eventHandler;
|
||||
|
||||
private Format format;
|
||||
private DrmInitData drmInitData;
|
||||
private MediaCodec codec;
|
||||
private boolean codecIsAdaptive;
|
||||
private boolean codecNeedsDiscardToSpsWorkaround;
|
||||
|
|
@ -286,13 +284,13 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
|
|||
String mimeType = format.sampleMimeType;
|
||||
MediaCrypto mediaCrypto = null;
|
||||
boolean requiresSecureDecoder = false;
|
||||
if (drmInitData != null) {
|
||||
if (format.drmInitData != null) {
|
||||
if (drmSessionManager == null) {
|
||||
throw ExoPlaybackException.createForRenderer(
|
||||
new IllegalStateException("Media requires a DrmSessionManager"), getIndex());
|
||||
}
|
||||
if (!openedDrmSession) {
|
||||
drmSessionManager.open(drmInitData);
|
||||
drmSessionManager.open(format.drmInitData);
|
||||
openedDrmSession = true;
|
||||
}
|
||||
int drmSessionState = drmSessionManager.getState();
|
||||
|
|
@ -376,7 +374,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
|
|||
@Override
|
||||
protected void onDisabled() {
|
||||
format = null;
|
||||
drmInitData = null;
|
||||
try {
|
||||
releaseCodec();
|
||||
} finally {
|
||||
|
|
@ -665,7 +662,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
|
|||
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
||||
Format oldFormat = format;
|
||||
format = formatHolder.format;
|
||||
drmInitData = formatHolder.drmInitData;
|
||||
if (codec != null && canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) {
|
||||
codecReconfigured = true;
|
||||
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
package com.google.android.exoplayer.chunk;
|
||||
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSpec;
|
||||
|
|
@ -64,13 +63,6 @@ public abstract class BaseMediaChunk extends MediaChunk {
|
|||
return firstSampleIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link DrmInitData} corresponding to the chunk.
|
||||
*
|
||||
* @return The {@link DrmInitData} corresponding to this chunk.
|
||||
*/
|
||||
public abstract DrmInitData getDrmInitData();
|
||||
|
||||
/**
|
||||
* Returns the track output most recently passed to {@link #init(DefaultTrackOutput)}.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -45,14 +45,11 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
|
|||
*/
|
||||
void seekMap(SeekMap seekMap);
|
||||
|
||||
/**
|
||||
* @see ExtractorOutput#drmInitData(DrmInitData)
|
||||
*/
|
||||
void drmInitData(DrmInitData drmInitData);
|
||||
|
||||
}
|
||||
|
||||
private final Extractor extractor;
|
||||
private final DrmInitData drmInitData;
|
||||
|
||||
private boolean extractorInitialized;
|
||||
private SingleTrackMetadataOutput metadataOutput;
|
||||
private TrackOutput trackOutput;
|
||||
|
|
@ -62,9 +59,12 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
|
|||
|
||||
/**
|
||||
* @param extractor The extractor to wrap.
|
||||
* @param drmInitData {@link DrmInitData} that should be added to any format extracted from the
|
||||
* stream. If set, overrides any {@link DrmInitData} extracted from the stream.
|
||||
*/
|
||||
public ChunkExtractorWrapper(Extractor extractor) {
|
||||
public ChunkExtractorWrapper(Extractor extractor, DrmInitData drmInitData) {
|
||||
this.extractor = extractor;
|
||||
this.drmInitData = drmInitData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -118,15 +118,13 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
|
|||
metadataOutput.seekMap(seekMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drmInitData(DrmInitData drmInitData) {
|
||||
metadataOutput.drmInitData(drmInitData);
|
||||
}
|
||||
|
||||
// TrackOutput implementation.
|
||||
|
||||
@Override
|
||||
public void format(Format format) {
|
||||
if (drmInitData != null) {
|
||||
format = format.copyWithDrmInitData(drmInitData);
|
||||
}
|
||||
trackOutput.format(format);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -226,11 +226,7 @@ public class ChunkTrackStream implements TrackStream, Loader.Callback {
|
|||
}
|
||||
downstreamFormat = format;
|
||||
|
||||
int result = sampleQueue.readData(formatHolder, buffer, loadingFinished, lastSeekPositionUs);
|
||||
if (result == FORMAT_READ) {
|
||||
formatHolder.drmInitData = currentChunk.getDrmInitData();
|
||||
}
|
||||
return result;
|
||||
return sampleQueue.readData(formatHolder, buffer, loadingFinished, lastSeekPositionUs);
|
||||
}
|
||||
|
||||
// Loader.Callback implementation.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer.chunk;
|
|||
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackMetadataOutput;
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
|
||||
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
|
||||
import com.google.android.exoplayer.extractor.Extractor;
|
||||
|
|
@ -38,7 +37,6 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
|
|||
private final long sampleOffsetUs;
|
||||
private final Format sampleFormat;
|
||||
|
||||
private volatile DrmInitData drmInitData;
|
||||
private volatile int bytesLoaded;
|
||||
private volatile boolean loadCanceled;
|
||||
|
||||
|
|
@ -54,17 +52,14 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
|
|||
* @param extractorWrapper A wrapped extractor to use for parsing the data.
|
||||
* @param sampleFormat The {@link Format} of the samples in the chunk, if known. May be null if
|
||||
* the data is known to define its own sample format.
|
||||
* @param drmInitData The {@link DrmInitData} for the chunk. Null if the media is not drm
|
||||
* protected. May also be null if the data is known to define its own initialization data.
|
||||
*/
|
||||
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||
long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs,
|
||||
ChunkExtractorWrapper extractorWrapper, Format sampleFormat, DrmInitData drmInitData) {
|
||||
ChunkExtractorWrapper extractorWrapper, Format sampleFormat) {
|
||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex);
|
||||
this.extractorWrapper = extractorWrapper;
|
||||
this.sampleOffsetUs = sampleOffsetUs;
|
||||
this.sampleFormat = sampleFormat;
|
||||
this.drmInitData = drmInitData;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -72,11 +67,6 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
|
|||
return bytesLoaded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final DrmInitData getDrmInitData() {
|
||||
return drmInitData;
|
||||
}
|
||||
|
||||
// SingleTrackMetadataOutput implementation.
|
||||
|
||||
@Override
|
||||
|
|
@ -84,11 +74,6 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
|
|||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void drmInitData(DrmInitData drmInitData) {
|
||||
this.drmInitData = drmInitData;
|
||||
}
|
||||
|
||||
// Loadable implementation.
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer.chunk;
|
|||
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackMetadataOutput;
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
|
||||
import com.google.android.exoplayer.extractor.Extractor;
|
||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||
|
|
@ -42,7 +41,6 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad
|
|||
// has completed. These variables do not need to be volatile, since a memory barrier must occur
|
||||
// for the reading thread to know that loading has completed.
|
||||
private Format sampleFormat;
|
||||
private DrmInitData drmInitData;
|
||||
private SeekMap seekMap;
|
||||
|
||||
private volatile int bytesLoaded;
|
||||
|
|
@ -77,15 +75,6 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad
|
|||
return sampleFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link DrmInitData} parsed from the chunk, or null.
|
||||
* <p>
|
||||
* Should be called after loading has completed.
|
||||
*/
|
||||
public DrmInitData getDrmInitData() {
|
||||
return drmInitData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link SeekMap} parsed from the chunk, or null.
|
||||
* <p>
|
||||
|
|
@ -102,11 +91,6 @@ public final class InitializationChunk extends Chunk implements SingleTrackMetad
|
|||
this.seekMap = seekMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drmInitData(DrmInitData drmInitData) {
|
||||
this.drmInitData = drmInitData;
|
||||
}
|
||||
|
||||
// TrackOutput implementation.
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer.chunk;
|
|||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
|
||||
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
|
||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||
|
|
@ -33,7 +32,6 @@ import java.io.IOException;
|
|||
public final class SingleSampleMediaChunk extends BaseMediaChunk {
|
||||
|
||||
private final Format sampleFormat;
|
||||
private final DrmInitData sampleDrmInitData;
|
||||
|
||||
private volatile int bytesLoaded;
|
||||
private volatile boolean loadCanceled;
|
||||
|
|
@ -47,15 +45,11 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
|
|||
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
|
||||
* @param chunkIndex The index of the chunk.
|
||||
* @param sampleFormat The format of the sample.
|
||||
* @param sampleDrmInitData The {@link DrmInitData} for the sample. Null if the sample is not drm
|
||||
* protected.
|
||||
*/
|
||||
public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger,
|
||||
Format format, long startTimeUs, long endTimeUs, int chunkIndex, Format sampleFormat,
|
||||
DrmInitData sampleDrmInitData) {
|
||||
Format format, long startTimeUs, long endTimeUs, int chunkIndex, Format sampleFormat) {
|
||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex);
|
||||
this.sampleFormat = sampleFormat;
|
||||
this.sampleDrmInitData = sampleDrmInitData;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -63,11 +57,6 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
|
|||
return bytesLoaded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DrmInitData getDrmInitData() {
|
||||
return sampleDrmInitData;
|
||||
}
|
||||
|
||||
// Loadable implementation.
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -31,13 +31,11 @@ import com.google.android.exoplayer.chunk.InitializationChunk;
|
|||
import com.google.android.exoplayer.chunk.MediaChunk;
|
||||
import com.google.android.exoplayer.chunk.SingleSampleMediaChunk;
|
||||
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
|
||||
import com.google.android.exoplayer.dash.mpd.ContentProtection;
|
||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
|
||||
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.SchemeData;
|
||||
import com.google.android.exoplayer.extractor.ChunkIndex;
|
||||
import com.google.android.exoplayer.extractor.SeekMap;
|
||||
import com.google.android.exoplayer.extractor.mkv.MatroskaExtractor;
|
||||
|
|
@ -50,7 +48,6 @@ import com.google.android.exoplayer.util.Util;
|
|||
import android.os.SystemClock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -70,7 +67,6 @@ public class DashChunkSource implements ChunkSource {
|
|||
private final Evaluation evaluation;
|
||||
|
||||
private MediaPresentationDescription manifest;
|
||||
private DrmInitData drmInitData;
|
||||
|
||||
private boolean lastChunkWasInitialization;
|
||||
private IOException fatalError;
|
||||
|
|
@ -100,7 +96,6 @@ public class DashChunkSource implements ChunkSource {
|
|||
Period period = manifest.getPeriod(0);
|
||||
long periodDurationUs = getPeriodDurationUs(manifest, 0);
|
||||
AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex);
|
||||
drmInitData = getDrmInitData(adaptationSet);
|
||||
|
||||
List<Representation> representations = adaptationSet.representations;
|
||||
representationHolders = new RepresentationHolder[representations.size()];
|
||||
|
|
@ -257,7 +252,7 @@ public class DashChunkSource implements ChunkSource {
|
|||
representationHolders[getTrackIndex(initializationChunk.format)];
|
||||
Format sampleFormat = initializationChunk.getSampleFormat();
|
||||
if (sampleFormat != null) {
|
||||
representationHolder.sampleFormat = sampleFormat;
|
||||
representationHolder.setSampleFormat(sampleFormat);
|
||||
}
|
||||
// The null check avoids overwriting an index obtained from the manifest with one obtained
|
||||
// from the stream. If the manifest defines an index then the stream shouldn't, but in cases
|
||||
|
|
@ -269,11 +264,6 @@ public class DashChunkSource implements ChunkSource {
|
|||
initializationChunk.dataSpec.uri.toString());
|
||||
}
|
||||
}
|
||||
// The null check avoids overwriting drmInitData obtained from the manifest with drmInitData
|
||||
// obtained from the stream, as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
|
||||
if (drmInitData == null) {
|
||||
drmInitData = initializationChunk.getDrmInitData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -324,12 +314,12 @@ public class DashChunkSource implements ChunkSource {
|
|||
|
||||
if (representationHolder.extractorWrapper == null) {
|
||||
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, trackFormat,
|
||||
startTimeUs, endTimeUs, segmentNum, trackFormat, null);
|
||||
startTimeUs, endTimeUs, segmentNum, trackFormat);
|
||||
} else {
|
||||
long sampleOffsetUs = -representation.presentationTimeOffsetUs;
|
||||
return new ContainerMediaChunk(dataSource, dataSpec, trigger, trackFormat, startTimeUs,
|
||||
endTimeUs, segmentNum, sampleOffsetUs, representationHolder.extractorWrapper,
|
||||
sampleFormat, drmInitData);
|
||||
sampleFormat);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -343,20 +333,6 @@ public class DashChunkSource implements ChunkSource {
|
|||
throw new IllegalStateException("Invalid format: " + format);
|
||||
}
|
||||
|
||||
private static DrmInitData getDrmInitData(AdaptationSet adaptationSet) {
|
||||
ArrayList<SchemeData> schemeDatas = null;
|
||||
for (int i = 0; i < adaptationSet.contentProtections.size(); i++) {
|
||||
ContentProtection contentProtection = adaptationSet.contentProtections.get(i);
|
||||
if (contentProtection.schemeData != null) {
|
||||
if (schemeDatas == null) {
|
||||
schemeDatas = new ArrayList<SchemeData>();
|
||||
}
|
||||
schemeDatas.add(contentProtection.schemeData);
|
||||
}
|
||||
}
|
||||
return schemeDatas == null ? null : new DrmInitData(schemeDatas);
|
||||
}
|
||||
|
||||
private static long getPeriodDurationUs(MediaPresentationDescription manifest, int index) {
|
||||
long durationMs = manifest.getPeriodDuration(index);
|
||||
if (durationMs == -1) {
|
||||
|
|
@ -385,10 +361,20 @@ public class DashChunkSource implements ChunkSource {
|
|||
String containerMimeType = representation.format.containerMimeType;
|
||||
extractorWrapper = mimeTypeIsRawText(containerMimeType) ? null : new ChunkExtractorWrapper(
|
||||
mimeTypeIsWebm(containerMimeType) ? new MatroskaExtractor()
|
||||
: new FragmentedMp4Extractor());
|
||||
: new FragmentedMp4Extractor(), representation.format.drmInitData);
|
||||
segmentIndex = representation.getIndex();
|
||||
}
|
||||
|
||||
public void setSampleFormat(Format sampleFormat) {
|
||||
DrmInitData manifestDrmInitData = representation.format.drmInitData;
|
||||
if (manifestDrmInitData != null) {
|
||||
// Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream,
|
||||
// as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
|
||||
sampleFormat = sampleFormat.copyWithDrmInitData(manifestDrmInitData);
|
||||
}
|
||||
this.sampleFormat = sampleFormat;
|
||||
}
|
||||
|
||||
public void updateRepresentation(long newPeriodDurationUs, Representation newRepresentation)
|
||||
throws BehindLiveWindowException{
|
||||
DashSegmentIndex oldIndex = representation.getIndex();
|
||||
|
|
|
|||
|
|
@ -28,26 +28,11 @@ public class AdaptationSet {
|
|||
public final int type;
|
||||
|
||||
public final List<Representation> representations;
|
||||
public final List<ContentProtection> contentProtections;
|
||||
|
||||
public AdaptationSet(int id, int type, List<Representation> representations,
|
||||
List<ContentProtection> contentProtections) {
|
||||
public AdaptationSet(int id, int type, List<Representation> representations) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.representations = Collections.unmodifiableList(representations);
|
||||
if (contentProtections == null) {
|
||||
this.contentProtections = Collections.emptyList();
|
||||
} else {
|
||||
this.contentProtections = Collections.unmodifiableList(contentProtections);
|
||||
}
|
||||
}
|
||||
|
||||
public AdaptationSet(int id, int type, List<Representation> representations) {
|
||||
this(id, type, representations, null);
|
||||
}
|
||||
|
||||
public boolean hasContentProtection() {
|
||||
return !contentProtections.isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.dash.mpd;
|
||||
|
||||
import com.google.android.exoplayer.drm.DrmInitData.SchemeData;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
/**
|
||||
* Represents a ContentProtection tag in an AdaptationSet.
|
||||
*/
|
||||
public class ContentProtection {
|
||||
|
||||
/**
|
||||
* Identifies the content protection scheme.
|
||||
*/
|
||||
public final String schemeUriId;
|
||||
|
||||
/**
|
||||
* Protection scheme specific initialization data. May be null.
|
||||
*/
|
||||
public final SchemeData schemeData;
|
||||
|
||||
/**
|
||||
* @param schemeUriId Identifies the content protection scheme.
|
||||
* @param schemeData Protection scheme specific initialization data. May be null.
|
||||
*/
|
||||
public ContentProtection(String schemeUriId, SchemeData schemeData) {
|
||||
this.schemeUriId = Assertions.checkNotNull(schemeUriId);
|
||||
this.schemeData = schemeData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof ContentProtection)) {
|
||||
return false;
|
||||
}
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ContentProtection other = (ContentProtection) obj;
|
||||
return schemeUriId.equals(other.schemeUriId) && Util.areEqual(schemeData, other.schemeData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (31 * schemeUriId.hashCode()) + (schemeData != null ? schemeData.hashCode() : 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -22,6 +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;
|
||||
import com.google.android.exoplayer.drm.DrmInitData.SchemeData;
|
||||
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
|
||||
import com.google.android.exoplayer.upstream.UriLoadable;
|
||||
|
|
@ -46,8 +47,6 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
|
|
@ -234,9 +233,9 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
int audioChannels = -1;
|
||||
int audioSamplingRate = parseInt(xpp, "audioSamplingRate", -1);
|
||||
String language = xpp.getAttributeValue(null, "lang");
|
||||
ArrayList<SchemeData> drmSchemeDatas = new ArrayList<>();
|
||||
List<RepresentationInfo> representationInfos = new ArrayList<>();
|
||||
|
||||
ContentProtectionsBuilder contentProtectionsBuilder = new ContentProtectionsBuilder();
|
||||
List<Representation> representations = new ArrayList<>();
|
||||
boolean seenFirstBaseUrl = false;
|
||||
do {
|
||||
xpp.next();
|
||||
|
|
@ -246,20 +245,19 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
seenFirstBaseUrl = true;
|
||||
}
|
||||
} else if (ParserUtil.isStartTag(xpp, "ContentProtection")) {
|
||||
ContentProtection contentProtection = parseContentProtection(xpp);
|
||||
SchemeData contentProtection = parseContentProtection(xpp);
|
||||
if (contentProtection != null) {
|
||||
contentProtectionsBuilder.addAdaptationSetProtection(contentProtection);
|
||||
drmSchemeDatas.add(contentProtection);
|
||||
}
|
||||
} else if (ParserUtil.isStartTag(xpp, "ContentComponent")) {
|
||||
language = checkLanguageConsistency(language, xpp.getAttributeValue(null, "lang"));
|
||||
contentType = checkContentTypeConsistency(contentType, parseContentType(xpp));
|
||||
} else if (ParserUtil.isStartTag(xpp, "Representation")) {
|
||||
Representation representation = parseRepresentation(xpp, baseUrl, mimeType, codecs, width,
|
||||
height, frameRate, audioChannels, audioSamplingRate, language, segmentBase,
|
||||
contentProtectionsBuilder);
|
||||
contentProtectionsBuilder.endRepresentation();
|
||||
contentType = checkContentTypeConsistency(contentType, getContentType(representation));
|
||||
representations.add(representation);
|
||||
RepresentationInfo representationInfo = parseRepresentation(xpp, baseUrl, mimeType, codecs,
|
||||
width, height, frameRate, audioChannels, audioSamplingRate, language, segmentBase);
|
||||
contentType = checkContentTypeConsistency(contentType,
|
||||
getContentType(representationInfo.format));
|
||||
representationInfos.add(representationInfo);
|
||||
} else if (ParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) {
|
||||
audioChannels = parseAudioChannelConfiguration(xpp);
|
||||
} else if (ParserUtil.isStartTag(xpp, "SegmentBase")) {
|
||||
|
|
@ -273,12 +271,18 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
}
|
||||
} while (!ParserUtil.isEndTag(xpp, "AdaptationSet"));
|
||||
|
||||
return buildAdaptationSet(id, contentType, representations, contentProtectionsBuilder.build());
|
||||
List<Representation> representations = new ArrayList<>(representationInfos.size());
|
||||
for (int i = 0; i < representationInfos.size(); i++) {
|
||||
representations.add(buildRepresentation(representationInfos.get(i), contentId,
|
||||
drmSchemeDatas));
|
||||
}
|
||||
|
||||
return buildAdaptationSet(id, contentType, representations);
|
||||
}
|
||||
|
||||
protected AdaptationSet buildAdaptationSet(int id, int contentType,
|
||||
List<Representation> representations, List<ContentProtection> contentProtections) {
|
||||
return new AdaptationSet(id, contentType, representations, contentProtections);
|
||||
List<Representation> representations) {
|
||||
return new AdaptationSet(id, contentType, representations);
|
||||
}
|
||||
|
||||
protected int parseContentType(XmlPullParser xpp) {
|
||||
|
|
@ -290,8 +294,8 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
: C.TRACK_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
protected int getContentType(Representation representation) {
|
||||
String sampleMimeType = representation.format.sampleMimeType;
|
||||
protected int getContentType(Format format) {
|
||||
String sampleMimeType = format.sampleMimeType;
|
||||
if (TextUtils.isEmpty(sampleMimeType)) {
|
||||
return C.TRACK_TYPE_UNKNOWN;
|
||||
} else if (MimeTypes.isVideo(sampleMimeType)) {
|
||||
|
|
@ -305,15 +309,15 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses a {@link ContentProtection} element.
|
||||
* Parses a ContentProtection element.
|
||||
*
|
||||
* @throws XmlPullParserException If an error occurs parsing the element.
|
||||
* @throws IOException If an error occurs reading the element.
|
||||
* @return The parsed {@link ContentProtection} element, or null if the element is unsupported.
|
||||
* @return {@link SchemeData} parsed from the ContentProtection element, or null if the element is
|
||||
* unsupported.
|
||||
**/
|
||||
protected ContentProtection parseContentProtection(XmlPullParser xpp)
|
||||
protected SchemeData parseContentProtection(XmlPullParser xpp)
|
||||
throws XmlPullParserException, IOException {
|
||||
String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri");
|
||||
SchemeData schemeData = null;
|
||||
boolean seenPsshElement = false;
|
||||
do {
|
||||
|
|
@ -332,11 +336,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
Log.w(TAG, "Skipped unsupported ContentProtection element");
|
||||
return null;
|
||||
}
|
||||
return buildContentProtection(schemeIdUri, schemeData);
|
||||
}
|
||||
|
||||
protected ContentProtection buildContentProtection(String schemeIdUri, SchemeData schemeData) {
|
||||
return new ContentProtection(schemeIdUri, schemeData);
|
||||
return schemeData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -353,11 +353,10 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
|
||||
// Representation parsing.
|
||||
|
||||
protected Representation parseRepresentation(XmlPullParser xpp, String baseUrl,
|
||||
protected RepresentationInfo parseRepresentation(XmlPullParser xpp, String baseUrl,
|
||||
String adaptationSetMimeType, String adaptationSetCodecs, int adaptationSetWidth,
|
||||
int adaptationSetHeight, float adaptationSetFrameRate, int adaptationSetAudioChannels,
|
||||
int adaptationSetAudioSamplingRate, String adaptationSetLanguage, SegmentBase segmentBase,
|
||||
ContentProtectionsBuilder contentProtectionsBuilder)
|
||||
int adaptationSetAudioSamplingRate, String adaptationSetLanguage, SegmentBase segmentBase)
|
||||
throws XmlPullParserException, IOException {
|
||||
String id = xpp.getAttributeValue(null, "id");
|
||||
int bandwidth = parseInt(xpp, "bandwidth");
|
||||
|
|
@ -369,6 +368,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
float frameRate = parseFrameRate(xpp, adaptationSetFrameRate);
|
||||
int audioChannels = adaptationSetAudioChannels;
|
||||
int audioSamplingRate = parseInt(xpp, "audioSamplingRate", adaptationSetAudioSamplingRate);
|
||||
ArrayList<SchemeData> drmSchemeDatas = new ArrayList<>();
|
||||
|
||||
boolean seenFirstBaseUrl = false;
|
||||
do {
|
||||
|
|
@ -387,17 +387,18 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
} else if (ParserUtil.isStartTag(xpp, "SegmentTemplate")) {
|
||||
segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase);
|
||||
} else if (ParserUtil.isStartTag(xpp, "ContentProtection")) {
|
||||
ContentProtection contentProtection = parseContentProtection(xpp);
|
||||
SchemeData contentProtection = parseContentProtection(xpp);
|
||||
if (contentProtection != null) {
|
||||
contentProtectionsBuilder.addAdaptationSetProtection(contentProtection);
|
||||
drmSchemeDatas.add(contentProtection);
|
||||
}
|
||||
}
|
||||
} while (!ParserUtil.isEndTag(xpp, "Representation"));
|
||||
|
||||
Format format = buildFormat(id, mimeType, width, height, frameRate, audioChannels,
|
||||
audioSamplingRate, bandwidth, adaptationSetLanguage, codecs);
|
||||
return buildRepresentation(contentId, -1, format,
|
||||
segmentBase != null ? segmentBase : new SingleSegmentBase(baseUrl));
|
||||
segmentBase = segmentBase != null ? segmentBase : new SingleSegmentBase(baseUrl);
|
||||
|
||||
return new RepresentationInfo(format, segmentBase, drmSchemeDatas);
|
||||
}
|
||||
|
||||
protected Format buildFormat(String id, String containerMimeType, int width, int height,
|
||||
|
|
@ -422,9 +423,15 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
}
|
||||
}
|
||||
|
||||
protected Representation buildRepresentation(String contentId, int revisionId, Format format,
|
||||
SegmentBase segmentBase) {
|
||||
return Representation.newInstance(contentId, revisionId, format, segmentBase);
|
||||
protected Representation buildRepresentation(RepresentationInfo representationInfo,
|
||||
String contentId, ArrayList<SchemeData> extraDrmSchemeDatas) {
|
||||
Format format = representationInfo.format;
|
||||
ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas;
|
||||
drmSchemeDatas.addAll(extraDrmSchemeDatas);
|
||||
if (!drmSchemeDatas.isEmpty()) {
|
||||
format = format.copyWithDrmInitData(new DrmInitData(drmSchemeDatas));
|
||||
}
|
||||
return Representation.newInstance(contentId, -1, format, representationInfo.segmentBase);
|
||||
}
|
||||
|
||||
// SegmentBase, SegmentList and SegmentTemplate parsing.
|
||||
|
|
@ -772,118 +779,17 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a list of {@link ContentProtection} elements for an {@link AdaptationSet}.
|
||||
* <p>
|
||||
* If child Representation elements contain ContentProtection elements, then it is required that
|
||||
* they all define the same ones. If they do, the ContentProtection elements are bubbled up to the
|
||||
* AdaptationSet. Child Representation elements defining different ContentProtection elements is
|
||||
* considered an error.
|
||||
*/
|
||||
protected static final class ContentProtectionsBuilder implements Comparator<ContentProtection> {
|
||||
private static final class RepresentationInfo {
|
||||
|
||||
private ArrayList<ContentProtection> adaptationSetProtections;
|
||||
private ArrayList<ContentProtection> representationProtections;
|
||||
private ArrayList<ContentProtection> currentRepresentationProtections;
|
||||
public final Format format;
|
||||
public final SegmentBase segmentBase;
|
||||
public final ArrayList<SchemeData> drmSchemeDatas;
|
||||
|
||||
private boolean representationProtectionsSet;
|
||||
|
||||
/**
|
||||
* Adds a {@link ContentProtection} found in the AdaptationSet element.
|
||||
*
|
||||
* @param contentProtection The {@link ContentProtection} to add.
|
||||
*/
|
||||
public void addAdaptationSetProtection(ContentProtection contentProtection) {
|
||||
if (adaptationSetProtections == null) {
|
||||
adaptationSetProtections = new ArrayList<>();
|
||||
}
|
||||
maybeAddContentProtection(adaptationSetProtections, contentProtection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link ContentProtection} found in a child Representation element.
|
||||
*
|
||||
* @param contentProtection The {@link ContentProtection} to add.
|
||||
*/
|
||||
public void addRepresentationProtection(ContentProtection contentProtection) {
|
||||
if (currentRepresentationProtections == null) {
|
||||
currentRepresentationProtections = new ArrayList<>();
|
||||
}
|
||||
maybeAddContentProtection(currentRepresentationProtections, contentProtection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be invoked after processing each child Representation element, in order to apply
|
||||
* consistency checks.
|
||||
*/
|
||||
public void endRepresentation() {
|
||||
if (!representationProtectionsSet) {
|
||||
if (currentRepresentationProtections != null) {
|
||||
Collections.sort(currentRepresentationProtections, this);
|
||||
}
|
||||
representationProtections = currentRepresentationProtections;
|
||||
representationProtectionsSet = true;
|
||||
} else {
|
||||
// Assert that each Representation element defines the same ContentProtection elements.
|
||||
if (currentRepresentationProtections == null) {
|
||||
Assertions.checkState(representationProtections == null);
|
||||
} else {
|
||||
Collections.sort(currentRepresentationProtections, this);
|
||||
Assertions.checkState(currentRepresentationProtections.equals(representationProtections));
|
||||
}
|
||||
}
|
||||
currentRepresentationProtections = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the final list of consistent {@link ContentProtection} elements.
|
||||
*/
|
||||
public ArrayList<ContentProtection> build() {
|
||||
if (adaptationSetProtections == null) {
|
||||
return representationProtections;
|
||||
} else if (representationProtections == null) {
|
||||
return adaptationSetProtections;
|
||||
} else {
|
||||
// Bubble up ContentProtection elements found in the child Representation elements.
|
||||
for (int i = 0; i < representationProtections.size(); i++) {
|
||||
maybeAddContentProtection(adaptationSetProtections, representationProtections.get(i));
|
||||
}
|
||||
return adaptationSetProtections;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a ContentProtection for consistency with the given list, adding it if necessary.
|
||||
* <ul>
|
||||
* <li>If the new ContentProtection matches another in the list, it's consistent and is not
|
||||
* added to the list.
|
||||
* <li>If the new ContentProtection has the same schemeUriId as another ContentProtection in the
|
||||
* list, but its other attributes do not match, then it's inconsistent and an
|
||||
* {@link IllegalStateException} is thrown.
|
||||
* <li>Else the new ContentProtection has a unique schemeUriId, it's consistent and is added.
|
||||
* </ul>
|
||||
*
|
||||
* @param contentProtections The list of ContentProtection elements currently known.
|
||||
* @param contentProtection The ContentProtection to add.
|
||||
*/
|
||||
private void maybeAddContentProtection(List<ContentProtection> contentProtections,
|
||||
ContentProtection contentProtection) {
|
||||
if (!contentProtections.contains(contentProtection)) {
|
||||
for (int i = 0; i < contentProtections.size(); i++) {
|
||||
// If contains returned false (no complete match), but find a matching schemeUriId, then
|
||||
// the MPD contains inconsistent ContentProtection data.
|
||||
Assertions.checkState(
|
||||
!contentProtections.get(i).schemeUriId.equals(contentProtection.schemeUriId));
|
||||
}
|
||||
contentProtections.add(contentProtection);
|
||||
}
|
||||
}
|
||||
|
||||
// Comparator implementation.
|
||||
|
||||
@Override
|
||||
public int compare(ContentProtection first, ContentProtection second) {
|
||||
return first.schemeUriId.compareTo(second.schemeUriId);
|
||||
public RepresentationInfo(Format format, SegmentBase segmentBase,
|
||||
ArrayList<SchemeData> drmSchemeDatas) {
|
||||
this.format = format;
|
||||
this.segmentBase = segmentBase;
|
||||
this.drmSchemeDatas = drmSchemeDatas;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -637,6 +637,8 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
* buffer is stored in {@code extrasHolder}, along with an encryption id if present and the
|
||||
* absolute position of the first byte that may still be required after the current sample
|
||||
* has been read.
|
||||
* @param downstreamFormat The current downstream {@link Format}. If the format of the next
|
||||
* sample is different to the current downstream format then a format will be read.
|
||||
* @param extrasHolder The holder into which extra sample information should be written.
|
||||
* @return The result, which can be {@link TrackStream#NOTHING_READ},
|
||||
* {@link TrackStream#FORMAT_READ} or {@link TrackStream#BUFFER_READ}.
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.extractor;
|
||||
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
|
||||
/**
|
||||
* Receives stream level data extracted by an {@link Extractor}.
|
||||
*/
|
||||
|
|
@ -45,11 +43,4 @@ public interface ExtractorOutput {
|
|||
*/
|
||||
void seekMap(SeekMap seekMap);
|
||||
|
||||
/**
|
||||
* Invoked when {@link DrmInitData} has been extracted from the stream.
|
||||
*
|
||||
* @param drmInitData The extracted {@link DrmInitData}.
|
||||
*/
|
||||
void drmInitData(DrmInitData drmInitData);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import com.google.android.exoplayer.TrackGroup;
|
|||
import com.google.android.exoplayer.TrackGroupArray;
|
||||
import com.google.android.exoplayer.TrackSelection;
|
||||
import com.google.android.exoplayer.TrackStream;
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
import com.google.android.exoplayer.upstream.Allocator;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSpec;
|
||||
|
|
@ -211,7 +210,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
|
||||
private volatile boolean tracksBuilt;
|
||||
private volatile SeekMap seekMap;
|
||||
private volatile DrmInitData drmInitData;
|
||||
|
||||
private boolean prepared;
|
||||
private boolean seenFirstTrackSelection;
|
||||
|
|
@ -471,12 +469,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
return TrackStream.NOTHING_READ;
|
||||
}
|
||||
|
||||
int result = sampleQueues[track].readData(formatHolder, buffer, loadingFinished,
|
||||
lastSeekPositionUs);
|
||||
if (result == TrackStream.FORMAT_READ) {
|
||||
formatHolder.drmInitData = drmInitData;
|
||||
}
|
||||
return result;
|
||||
return sampleQueues[track].readData(formatHolder, buffer, loadingFinished, lastSeekPositionUs);
|
||||
}
|
||||
|
||||
// Loader.Callback implementation.
|
||||
|
|
@ -530,11 +523,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
this.seekMap = seekMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drmInitData(DrmInitData drmInitData) {
|
||||
this.drmInitData = drmInitData;
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private void seekToInternal(long positionUs) {
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ import java.util.Collections;
|
|||
audioSpecifiConfig);
|
||||
Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC,
|
||||
Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
|
||||
Collections.singletonList(audioSpecifiConfig), null);
|
||||
Collections.singletonList(audioSpecifiConfig), null, null);
|
||||
output.format(format);
|
||||
hasOutputFormat = true;
|
||||
} else if (packetType == AAC_PACKET_TYPE_AAC_RAW) {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ import java.util.List;
|
|||
Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264,
|
||||
Format.NO_VALUE, Format.NO_VALUE, avcData.width, avcData.height,
|
||||
Format.NO_VALUE, avcData.initializationData, Format.NO_VALUE,
|
||||
avcData.pixelWidthAspectRatio);
|
||||
avcData.pixelWidthAspectRatio, null);
|
||||
output.format(format);
|
||||
hasOutputFormat = true;
|
||||
} else if (packetType == AVC_PACKET_TYPE_AVC_NALU) {
|
||||
|
|
|
|||
|
|
@ -220,8 +220,7 @@ public final class MatroskaExtractor implements Extractor {
|
|||
// The track corresponding to the current TrackEntry element, or null.
|
||||
private Track currentTrack;
|
||||
|
||||
// Whether drm init data has been sent to the output.
|
||||
private boolean sentDrmInitData;
|
||||
// Whether a seek map has been sent to the output.
|
||||
private boolean sentSeekMap;
|
||||
|
||||
// Master seek entry related elements.
|
||||
|
|
@ -483,11 +482,8 @@ public final class MatroskaExtractor implements Extractor {
|
|||
if (currentTrack.encryptionKeyId == null) {
|
||||
throw new ParserException("Encrypted Track found but ContentEncKeyID was not found");
|
||||
}
|
||||
if (!sentDrmInitData) {
|
||||
extractorOutput.drmInitData(new DrmInitData(
|
||||
new SchemeData(C.UUID_NIL, MimeTypes.VIDEO_WEBM, currentTrack.encryptionKeyId)));
|
||||
sentDrmInitData = true;
|
||||
}
|
||||
currentTrack.drmInitData = new DrmInitData(
|
||||
new SchemeData(C.UUID_NIL, MimeTypes.VIDEO_WEBM, currentTrack.encryptionKeyId));
|
||||
}
|
||||
return;
|
||||
case ID_CONTENT_ENCODINGS:
|
||||
|
|
@ -1182,6 +1178,7 @@ public final class MatroskaExtractor implements Extractor {
|
|||
public byte[] sampleStrippedBytes;
|
||||
public byte[] encryptionKeyId;
|
||||
public byte[] codecPrivate;
|
||||
public DrmInitData drmInitData;
|
||||
|
||||
// Video elements.
|
||||
public int width = Format.NO_VALUE;
|
||||
|
|
@ -1323,7 +1320,8 @@ public final class MatroskaExtractor implements Extractor {
|
|||
// into the trackId passed when creating the formats.
|
||||
if (MimeTypes.isAudio(mimeType)) {
|
||||
format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType,
|
||||
Format.NO_VALUE, maxInputSize, channelCount, sampleRate, initializationData, language);
|
||||
Format.NO_VALUE, maxInputSize, channelCount, sampleRate, initializationData, language,
|
||||
drmInitData);
|
||||
} else if (MimeTypes.isVideo(mimeType)) {
|
||||
if (displayUnit == Track.DISPLAY_UNIT_PIXELS) {
|
||||
displayWidth = displayWidth == Format.NO_VALUE ? width : displayWidth;
|
||||
|
|
@ -1335,14 +1333,14 @@ public final class MatroskaExtractor implements Extractor {
|
|||
}
|
||||
format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType,
|
||||
Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData,
|
||||
Format.NO_VALUE, pixelWidthHeightRatio);
|
||||
Format.NO_VALUE, pixelWidthHeightRatio, drmInitData);
|
||||
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
|
||||
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, Format.NO_VALUE,
|
||||
language);
|
||||
language, drmInitData);
|
||||
} else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType)
|
||||
|| MimeTypes.APPLICATION_PGS.equals(mimeType)) {
|
||||
format = Format.createImageSampleFormat(Integer.toString(trackId), mimeType,
|
||||
Format.NO_VALUE, initializationData, language);
|
||||
Format.NO_VALUE, initializationData, language, drmInitData);
|
||||
} else {
|
||||
throw new ParserException("Unexpected MIME type.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ public final class Mp3Extractor implements Extractor {
|
|||
trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType,
|
||||
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels,
|
||||
synchronizedHeader.sampleRate, gaplessInfoHolder.encoderDelay,
|
||||
gaplessInfoHolder.encoderPadding, null, null));
|
||||
gaplessInfoHolder.encoderPadding, null, null, null));
|
||||
}
|
||||
return readSample(input);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer.extractor.mp4;
|
|||
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.extractor.GaplessInfoHolder;
|
||||
import com.google.android.exoplayer.util.Ac3Util;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
|
|
@ -49,10 +50,12 @@ import java.util.List;
|
|||
*
|
||||
* @param trak Atom to parse.
|
||||
* @param mvhd Movie header atom, used to get the timescale.
|
||||
* @param drmInitData {@link DrmInitData} to be included in the format.
|
||||
* @param isQuickTime True for QuickTime media. False otherwise.
|
||||
* @return A {@link Track} instance, or {@code null} if the track's type isn't supported.
|
||||
*/
|
||||
public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, boolean isQuickTime) {
|
||||
public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd,
|
||||
DrmInitData drmInitData, boolean isQuickTime) {
|
||||
Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
|
||||
int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data);
|
||||
if (trackType == C.TRACK_TYPE_UNKNOWN) {
|
||||
|
|
@ -73,7 +76,7 @@ import java.util.List;
|
|||
|
||||
Pair<Long, String> mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
|
||||
StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id,
|
||||
tkhdData.rotationDegrees, mdhdData.second, isQuickTime);
|
||||
tkhdData.rotationDegrees, mdhdData.second, drmInitData, isQuickTime);
|
||||
Pair<long[], long[]> edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts));
|
||||
return stsdData.format == null ? null
|
||||
: new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs,
|
||||
|
|
@ -560,11 +563,12 @@ import java.util.List;
|
|||
* @param trackId The track's identifier in its container.
|
||||
* @param rotationDegrees The rotation of the track in degrees.
|
||||
* @param language The language of the track.
|
||||
* @param drmInitData {@link DrmInitData} to be included in the format.
|
||||
* @param isQuickTime True for QuickTime media. False otherwise.
|
||||
* @return An object containing the parsed data.
|
||||
*/
|
||||
private static StsdData parseStsd(ParsableByteArray stsd, int trackId, int rotationDegrees,
|
||||
String language, boolean isQuickTime) {
|
||||
String language, DrmInitData drmInitData, boolean isQuickTime) {
|
||||
stsd.setPosition(Atom.FULL_HEADER_SIZE);
|
||||
int numberOfEntries = stsd.readInt();
|
||||
StsdData out = new StsdData(numberOfEntries);
|
||||
|
|
@ -578,7 +582,7 @@ import java.util.List;
|
|||
|| childAtomType == Atom.TYPE_hvc1 || childAtomType == Atom.TYPE_hev1
|
||||
|| childAtomType == Atom.TYPE_s263) {
|
||||
parseVideoSampleEntry(stsd, childStartPosition, childAtomSize, trackId, rotationDegrees,
|
||||
out, i);
|
||||
drmInitData, out, i);
|
||||
} else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca
|
||||
|| childAtomType == Atom.TYPE_ac_3 || childAtomType == Atom.TYPE_ec_3
|
||||
|| childAtomType == Atom.TYPE_dtsc || childAtomType == Atom.TYPE_dtse
|
||||
|
|
@ -586,19 +590,19 @@ import java.util.List;
|
|||
|| childAtomType == Atom.TYPE_samr || childAtomType == Atom.TYPE_sawb
|
||||
|| childAtomType == Atom.TYPE_lpcm || childAtomType == Atom.TYPE_sowt) {
|
||||
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
|
||||
language, isQuickTime, out, i);
|
||||
language, isQuickTime, drmInitData, out, i);
|
||||
} else if (childAtomType == Atom.TYPE_TTML) {
|
||||
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
||||
MimeTypes.APPLICATION_TTML, Format.NO_VALUE, language);
|
||||
MimeTypes.APPLICATION_TTML, Format.NO_VALUE, language, drmInitData);
|
||||
} else if (childAtomType == Atom.TYPE_tx3g) {
|
||||
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
||||
MimeTypes.APPLICATION_TX3G, Format.NO_VALUE, language);
|
||||
MimeTypes.APPLICATION_TX3G, Format.NO_VALUE, language, drmInitData);
|
||||
} else if (childAtomType == Atom.TYPE_wvtt) {
|
||||
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
||||
MimeTypes.APPLICATION_MP4VTT, Format.NO_VALUE, language);
|
||||
MimeTypes.APPLICATION_MP4VTT, Format.NO_VALUE, language, drmInitData);
|
||||
} else if (childAtomType == Atom.TYPE_stpp) {
|
||||
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
||||
MimeTypes.APPLICATION_TTML, Format.NO_VALUE, language,
|
||||
MimeTypes.APPLICATION_TTML, Format.NO_VALUE, language, drmInitData,
|
||||
0 /* subsample timing is absolute */);
|
||||
}
|
||||
stsd.setPosition(childStartPosition + childAtomSize);
|
||||
|
|
@ -607,7 +611,7 @@ import java.util.List;
|
|||
}
|
||||
|
||||
private static void parseVideoSampleEntry(ParsableByteArray parent, int position, int size,
|
||||
int trackId, int rotationDegrees, StsdData out, int entryIndex) {
|
||||
int trackId, int rotationDegrees, DrmInitData drmInitData, StsdData out, int entryIndex) {
|
||||
parent.setPosition(position + Atom.HEADER_SIZE);
|
||||
|
||||
parent.skipBytes(24);
|
||||
|
|
@ -671,7 +675,7 @@ import java.util.List;
|
|||
|
||||
out.format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType,
|
||||
Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE, initializationData,
|
||||
rotationDegrees, pixelWidthHeightRatio);
|
||||
rotationDegrees, pixelWidthHeightRatio, drmInitData);
|
||||
}
|
||||
|
||||
private static AvcCData parseAvcCFromParent(ParsableByteArray parent, int position) {
|
||||
|
|
@ -830,7 +834,8 @@ import java.util.List;
|
|||
}
|
||||
|
||||
private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position,
|
||||
int size, int trackId, String language, boolean isQuickTime, StsdData out, int entryIndex) {
|
||||
int size, int trackId, String language, boolean isQuickTime, DrmInitData drmInitData,
|
||||
StsdData out, int entryIndex) {
|
||||
parent.setPosition(position + Atom.HEADER_SIZE);
|
||||
|
||||
int quickTimeSoundDescriptionVersion = 0;
|
||||
|
|
@ -922,17 +927,20 @@ import java.util.List;
|
|||
// TODO: Choose the right AC-3 track based on the contents of dac3/dec3.
|
||||
// TODO: Add support for encryption (by setting out.trackEncryptionBoxes).
|
||||
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
|
||||
out.format = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId), language);
|
||||
out.format = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId), language,
|
||||
drmInitData);
|
||||
return;
|
||||
} else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
|
||||
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
|
||||
out.format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language);
|
||||
out.format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language,
|
||||
drmInitData);
|
||||
return;
|
||||
} else if ((atomType == Atom.TYPE_dtsc || atomType == Atom.TYPE_dtse
|
||||
|| atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl)
|
||||
&& childAtomType == Atom.TYPE_ddts) {
|
||||
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType,
|
||||
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, language);
|
||||
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, language,
|
||||
drmInitData);
|
||||
return;
|
||||
}
|
||||
childAtomPosition += childAtomSize;
|
||||
|
|
@ -946,7 +954,7 @@ import java.util.List;
|
|||
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType,
|
||||
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate,
|
||||
initializationData == null ? null : Collections.singletonList(initializationData),
|
||||
language);
|
||||
language, drmInitData);
|
||||
}
|
||||
|
||||
/** Returns the position of the esds box within a parent, or -1 if no esds box is found */
|
||||
|
|
|
|||
|
|
@ -335,9 +335,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (schemeDatas != null) {
|
||||
extractorOutput.drmInitData(new DrmInitData(schemeDatas));
|
||||
}
|
||||
DrmInitData drmInitData = schemeDatas == null ? null : new DrmInitData(schemeDatas);
|
||||
|
||||
// Read declaration of track fragments in the Moov box.
|
||||
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
|
||||
|
|
@ -357,7 +355,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
for (int i = 0; i < moovContainerChildrenSize; i++) {
|
||||
Atom.ContainerAtom atom = moov.containerChildren.get(i);
|
||||
if (atom.type == Atom.TYPE_trak) {
|
||||
Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), false);
|
||||
Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd),
|
||||
drmInitData, false);
|
||||
if (track != null) {
|
||||
tracks.put(track.id, track);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||
continue;
|
||||
}
|
||||
|
||||
Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd),
|
||||
Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), null,
|
||||
isQuickTime);
|
||||
if (track == null) {
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ import java.util.List;
|
|||
List<byte[]> initializationData = Collections.singletonList(metadata);
|
||||
trackOutput.format(Format.createAudioSampleFormat(null, MimeTypes.AUDIO_FLAC,
|
||||
streamInfo.bitRate(), Format.NO_VALUE, streamInfo.channels, streamInfo.sampleRate,
|
||||
initializationData, null));
|
||||
initializationData, null, null));
|
||||
} else if (data[0] == AUDIO_PACKET_TYPE) {
|
||||
if (!firstAudioPacketProcessed) {
|
||||
if (seekTable != null) {
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ import java.util.ArrayList;
|
|||
trackOutput.format(Format.createAudioSampleFormat(null, MimeTypes.AUDIO_VORBIS,
|
||||
this.vorbisSetup.idHeader.bitrateNominal, OggParser.OGG_MAX_SEGMENT_SIZE * 255,
|
||||
this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate,
|
||||
codecInitialisationData, null));
|
||||
codecInitialisationData, null, null));
|
||||
|
||||
if (inputLength != C.LENGTH_UNBOUNDED) {
|
||||
oggSeeker.setup(inputLength - audioStartPosition, totalSamples);
|
||||
|
|
|
|||
|
|
@ -162,9 +162,8 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||
*/
|
||||
private void parseHeader() {
|
||||
if (format == null) {
|
||||
format = isEac3
|
||||
? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, null)
|
||||
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, null);
|
||||
format = isEac3 ? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, null, null)
|
||||
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, null, null);
|
||||
output.format(format);
|
||||
}
|
||||
sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data)
|
||||
|
|
|
|||
|
|
@ -84,7 +84,8 @@ import java.util.Collections;
|
|||
public AdtsReader(TrackOutput output, TrackOutput id3Output) {
|
||||
super(output);
|
||||
this.id3Output = id3Output;
|
||||
id3Output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, Format.NO_VALUE));
|
||||
id3Output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, Format.NO_VALUE,
|
||||
null));
|
||||
adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]);
|
||||
id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE));
|
||||
setFindingSampleState();
|
||||
|
|
@ -275,7 +276,7 @@ import java.util.Collections;
|
|||
|
||||
Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, Format.NO_VALUE,
|
||||
Format.NO_VALUE, audioParams.second, audioParams.first,
|
||||
Collections.singletonList(audioSpecificConfig), null);
|
||||
Collections.singletonList(audioSpecificConfig), null, null);
|
||||
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the
|
||||
// number of PCM audio samples per second.
|
||||
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate;
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||
private void parseHeader() {
|
||||
byte[] frameData = headerScratchBytes.data;
|
||||
if (format == null) {
|
||||
format = DtsUtil.parseDtsFormat(frameData, null, null);
|
||||
format = DtsUtil.parseDtsFormat(frameData, null, null, null);
|
||||
output.format(format);
|
||||
}
|
||||
sampleSize = DtsUtil.getDtsFrameSize(frameData);
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ import java.util.Collections;
|
|||
|
||||
Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_MPEG2, Format.NO_VALUE,
|
||||
Format.NO_VALUE, width, height, Format.NO_VALUE, Collections.singletonList(csdData),
|
||||
Format.NO_VALUE, pixelWidthHeightRatio);
|
||||
Format.NO_VALUE, pixelWidthHeightRatio, null);
|
||||
|
||||
long frameDurationUs = 0;
|
||||
int frameRateCodeMinusOne = (csdData[7] & 0x0F) - 1;
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ import java.util.List;
|
|||
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength);
|
||||
output.format(Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, Format.NO_VALUE,
|
||||
Format.NO_VALUE, spsData.width, spsData.height, Format.NO_VALUE, initializationData,
|
||||
Format.NO_VALUE, spsData.pixelWidthAspectRatio));
|
||||
Format.NO_VALUE, spsData.pixelWidthAspectRatio, null));
|
||||
hasOutputFormat = true;
|
||||
sampleReader.putSps(spsData);
|
||||
sampleReader.putPps(ppsData);
|
||||
|
|
|
|||
|
|
@ -309,7 +309,7 @@ import java.util.Collections;
|
|||
|
||||
return Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H265, Format.NO_VALUE,
|
||||
Format.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples, Format.NO_VALUE,
|
||||
Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio);
|
||||
Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||
|
||||
public Id3Reader(TrackOutput output) {
|
||||
super(output);
|
||||
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, Format.NO_VALUE));
|
||||
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, Format.NO_VALUE,
|
||||
null));
|
||||
id3Header = new ParsableByteArray(ID3_HEADER_SIZE);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -163,7 +163,8 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||
if (!hasOutputFormat) {
|
||||
frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate;
|
||||
Format format = Format.createAudioSampleFormat(null, header.mimeType, Format.NO_VALUE,
|
||||
MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate, null, null);
|
||||
MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate, null, null,
|
||||
null);
|
||||
output.format(format);
|
||||
hasOutputFormat = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||
public SeiReader(TrackOutput output) {
|
||||
this.output = output;
|
||||
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608, Format.NO_VALUE,
|
||||
null));
|
||||
null, null));
|
||||
}
|
||||
|
||||
public void consume(long pesTimeUs, ParsableByteArray seiBuffer) {
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ public final class WavExtractor implements Extractor, SeekMap {
|
|||
}
|
||||
Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW,
|
||||
wavHeader.getBitrate(), MAX_INPUT_SIZE, wavHeader.getNumChannels(),
|
||||
wavHeader.getSampleRateHz(), null, null);
|
||||
wavHeader.getSampleRateHz(), null, null, null);
|
||||
trackOutput.format(format);
|
||||
bytesPerFrame = wavHeader.getBytesPerFrame();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.hls;
|
||||
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
|
||||
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
||||
import com.google.android.exoplayer.extractor.SeekMap;
|
||||
|
|
@ -117,9 +116,4 @@ import android.util.SparseArray;
|
|||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drmInitData(DrmInitData drmInitData) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ import java.util.regex.Pattern;
|
|||
private TrackOutput buildTrackOutput(long subsampleOffsetUs) {
|
||||
TrackOutput trackOutput = output.track(0);
|
||||
trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.TEXT_VTT, Format.NO_VALUE,
|
||||
language, subsampleOffsetUs));
|
||||
language, null, subsampleOffsetUs));
|
||||
output.endTracks();
|
||||
return trackOutput;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import com.google.android.exoplayer.chunk.ContainerMediaChunk;
|
|||
import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation;
|
||||
import com.google.android.exoplayer.chunk.MediaChunk;
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
import com.google.android.exoplayer.extractor.mp4.FragmentedMp4Extractor;
|
||||
import com.google.android.exoplayer.extractor.mp4.Track;
|
||||
import com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox;
|
||||
|
|
@ -58,7 +57,6 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||
private final FormatEvaluator adaptiveFormatEvaluator;
|
||||
|
||||
private SmoothStreamingManifest manifest;
|
||||
private DrmInitData drmInitData;
|
||||
private int currentManifestChunkOffset;
|
||||
private boolean needManifestRefresh;
|
||||
|
||||
|
|
@ -72,18 +70,15 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
||||
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
||||
* @param trackEncryptionBoxes Track encryption boxes for the stream.
|
||||
* @param drmInitData Drm initialization data for the stream.
|
||||
*/
|
||||
public SmoothStreamingChunkSource(SmoothStreamingManifest manifest, int elementIndex,
|
||||
TrackGroup trackGroup, int[] tracks, DataSource dataSource,
|
||||
FormatEvaluator adaptiveFormatEvaluator, TrackEncryptionBox[] trackEncryptionBoxes,
|
||||
DrmInitData drmInitData) {
|
||||
FormatEvaluator adaptiveFormatEvaluator, TrackEncryptionBox[] trackEncryptionBoxes) {
|
||||
this.manifest = manifest;
|
||||
this.elementIndex = elementIndex;
|
||||
this.trackGroup = trackGroup;
|
||||
this.dataSource = dataSource;
|
||||
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
||||
this.drmInitData = drmInitData;
|
||||
this.evaluation = new Evaluation();
|
||||
|
||||
StreamElement streamElement = manifest.streamElements[elementIndex];
|
||||
|
|
@ -97,7 +92,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
|
||||
FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
|
||||
| FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track);
|
||||
extractorWrappers[j] = new ChunkExtractorWrapper(extractor);
|
||||
extractorWrappers[j] = new ChunkExtractorWrapper(extractor, formats[j].drmInitData);
|
||||
}
|
||||
|
||||
enabledFormats = new Format[tracks.length];
|
||||
|
|
@ -224,8 +219,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
|
||||
|
||||
out.chunk = newMediaChunk(selectedFormat, dataSource, uri, null, currentAbsoluteChunkIndex,
|
||||
chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger, extractorWrapper, drmInitData,
|
||||
selectedFormat);
|
||||
chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger, extractorWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -274,13 +268,13 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||
|
||||
private static MediaChunk newMediaChunk(Format format, DataSource dataSource, Uri uri,
|
||||
String cacheKey, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, int trigger,
|
||||
ChunkExtractorWrapper extractorWrapper, DrmInitData drmInitData, Format sampleFormat) {
|
||||
ChunkExtractorWrapper extractorWrapper) {
|
||||
DataSpec dataSpec = new DataSpec(uri, 0, -1, cacheKey);
|
||||
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
|
||||
// To convert them the absolute timestamps, we need to set sampleOffsetUs to chunkStartTimeUs.
|
||||
long sampleOffsetUs = chunkStartTimeUs;
|
||||
return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, chunkStartTimeUs,
|
||||
chunkEndTimeUs, chunkIndex, sampleOffsetUs, extractorWrapper, sampleFormat, drmInitData);
|
||||
chunkEndTimeUs, chunkIndex, sampleOffsetUs, extractorWrapper, format);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ package com.google.android.exoplayer.smoothstreaming;
|
|||
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.SchemeData;
|
||||
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
|
||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
|
||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
|
||||
|
|
@ -375,6 +377,15 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
|
|||
public Object build() {
|
||||
StreamElement[] streamElementArray = new StreamElement[streamElements.size()];
|
||||
streamElements.toArray(streamElementArray);
|
||||
if (protectionElement != null) {
|
||||
DrmInitData drmInitData = new DrmInitData(new SchemeData(protectionElement.uuid,
|
||||
MimeTypes.VIDEO_MP4, protectionElement.data));
|
||||
for (StreamElement streamElement : streamElementArray) {
|
||||
for (int i = 0; i < streamElement.formats.length; i++) {
|
||||
streamElement.formats[i] = streamElement.formats[i].copyWithDrmInitData(drmInitData);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new SmoothStreamingManifest(majorVersion, minorVersion, timescale, duration,
|
||||
dvrWindowLength, lookAheadCount, isLive, protectionElement, streamElementArray);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@ import com.google.android.exoplayer.chunk.ChunkTrackStream;
|
|||
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.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;
|
||||
|
|
@ -38,7 +36,6 @@ import com.google.android.exoplayer.upstream.DataSource;
|
|||
import com.google.android.exoplayer.upstream.DataSourceFactory;
|
||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.net.Uri;
|
||||
|
|
@ -69,7 +66,6 @@ public final class SmoothStreamingSampleSource implements SampleSource {
|
|||
private long durationUs;
|
||||
private SmoothStreamingManifest currentManifest;
|
||||
private TrackEncryptionBox[] trackEncryptionBoxes;
|
||||
private DrmInitData drmInitData;
|
||||
private TrackGroupArray trackGroups;
|
||||
private int[] trackGroupElementIndices;
|
||||
private boolean pendingReset;
|
||||
|
|
@ -117,10 +113,8 @@ public final class SmoothStreamingSampleSource implements SampleSource {
|
|||
ProtectionElement protectionElement = currentManifest.protectionElement;
|
||||
if (protectionElement != null) {
|
||||
byte[] keyId = getProtectionElementKeyId(protectionElement.data);
|
||||
trackEncryptionBoxes = new TrackEncryptionBox[1];
|
||||
trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId);
|
||||
drmInitData = new DrmInitData(
|
||||
new SchemeData(protectionElement.uuid, MimeTypes.VIDEO_MP4, protectionElement.data));
|
||||
trackEncryptionBoxes = new TrackEncryptionBox[] {
|
||||
new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId)};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -279,7 +273,7 @@ public final class SmoothStreamingSampleSource implements SampleSource {
|
|||
DataSource dataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||
SmoothStreamingChunkSource chunkSource = new SmoothStreamingChunkSource(currentManifest,
|
||||
streamElementIndex, trackGroups.get(selection.group), selectedTracks, dataSource,
|
||||
adaptiveEvaluator, trackEncryptionBoxes, drmInitData);
|
||||
adaptiveEvaluator, trackEncryptionBoxes);
|
||||
ChunkTrackStream trackStream = new ChunkTrackStream(chunkSource, loadControl, bufferSize,
|
||||
positionUs, eventHandler, eventListener, streamElementType);
|
||||
return Pair.create(chunkSource, trackStream);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
package com.google.android.exoplayer.util;
|
||||
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
|
|
@ -66,10 +67,11 @@ public final class Ac3Util {
|
|||
* @param data The AC3SpecificBox to parse.
|
||||
* @param trackId The track identifier to set on the format, or null.
|
||||
* @param language The language to set on the format.
|
||||
* @param drmInitData {@link DrmInitData} to be included in the format.
|
||||
* @return The AC-3 format parsed from data in the header.
|
||||
*/
|
||||
public static Format parseAc3AnnexFFormat(ParsableByteArray data, String trackId,
|
||||
String language) {
|
||||
String language, DrmInitData drmInitData) {
|
||||
int fscod = (data.readUnsignedByte() & 0xC0) >> 6;
|
||||
int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
|
||||
int nextByte = data.readUnsignedByte();
|
||||
|
|
@ -78,7 +80,7 @@ public final class Ac3Util {
|
|||
channelCount++;
|
||||
}
|
||||
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, Format.NO_VALUE,
|
||||
Format.NO_VALUE, channelCount, sampleRate, null, language);
|
||||
Format.NO_VALUE, channelCount, sampleRate, null, language, drmInitData);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -88,10 +90,11 @@ public final class Ac3Util {
|
|||
* @param data The EC3SpecificBox to parse.
|
||||
* @param trackId The track identifier to set on the format, or null.
|
||||
* @param language The language to set on the format.
|
||||
* @param drmInitData {@link DrmInitData} to be included in the format.
|
||||
* @return The E-AC-3 format parsed from data in the header.
|
||||
*/
|
||||
public static Format parseEAc3AnnexFFormat(ParsableByteArray data, String trackId,
|
||||
String language) {
|
||||
String language, DrmInitData drmInitData) {
|
||||
data.skipBytes(2); // data_rate, num_ind_sub
|
||||
|
||||
// Read only the first substream.
|
||||
|
|
@ -104,7 +107,7 @@ public final class Ac3Util {
|
|||
channelCount++;
|
||||
}
|
||||
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, Format.NO_VALUE,
|
||||
Format.NO_VALUE, channelCount, sampleRate, null, language);
|
||||
Format.NO_VALUE, channelCount, sampleRate, null, language, drmInitData);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -114,10 +117,11 @@ public final class Ac3Util {
|
|||
* @param data The data to parse, positioned at the start of the syncframe.
|
||||
* @param trackId The track identifier to set on the format, or null.
|
||||
* @param language The language to set on the format.
|
||||
* @param drmInitData {@link DrmInitData} to be included in the format.
|
||||
* @return The AC-3 format parsed from data in the header.
|
||||
*/
|
||||
public static Format parseAc3SyncframeFormat(ParsableBitArray data, String trackId,
|
||||
String language) {
|
||||
String language, DrmInitData drmInitData) {
|
||||
data.skipBits(16 + 16); // syncword, crc1
|
||||
int fscod = data.readBits(2);
|
||||
data.skipBits(6 + 5 + 3); // frmsizecod, bsid, bsmod
|
||||
|
|
@ -134,7 +138,7 @@ public final class Ac3Util {
|
|||
boolean lfeon = data.readBit();
|
||||
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, Format.NO_VALUE,
|
||||
Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0),
|
||||
SAMPLE_RATE_BY_FSCOD[fscod], null, language);
|
||||
SAMPLE_RATE_BY_FSCOD[fscod], null, language, drmInitData);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -144,10 +148,11 @@ public final class Ac3Util {
|
|||
* @param data The data to parse, positioned at the start of the syncframe.
|
||||
* @param trackId The track identifier to set on the format, or null.
|
||||
* @param language The language to set on the format.
|
||||
* @param drmInitData {@link DrmInitData} to be included in the format.
|
||||
* @return The E-AC-3 format parsed from data in the header.
|
||||
*/
|
||||
public static Format parseEac3SyncframeFormat(ParsableBitArray data, String trackId,
|
||||
String language) {
|
||||
String language, DrmInitData drmInitData) {
|
||||
data.skipBits(16 + 2 + 3 + 11); // syncword, strmtype, substreamid, frmsiz
|
||||
int sampleRate;
|
||||
int fscod = data.readBits(2);
|
||||
|
|
@ -161,7 +166,7 @@ public final class Ac3Util {
|
|||
boolean lfeon = data.readBit();
|
||||
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, Format.NO_VALUE,
|
||||
Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), sampleRate, null,
|
||||
language);
|
||||
language, drmInitData);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
package com.google.android.exoplayer.util;
|
||||
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
|
|
@ -54,9 +55,11 @@ public final class DtsUtil {
|
|||
* @param frame The DTS frame to parse.
|
||||
* @param trackId The track identifier to set on the format, or null.
|
||||
* @param language The language to set on the format.
|
||||
* @param drmInitData {@link DrmInitData} to be included in the format.
|
||||
* @return The DTS format parsed from data in the header.
|
||||
*/
|
||||
public static Format parseDtsFormat(byte[] frame, String trackId, String language) {
|
||||
public static Format parseDtsFormat(byte[] frame, String trackId, String language,
|
||||
DrmInitData drmInitData) {
|
||||
ParsableBitArray frameBits = SCRATCH_BITS;
|
||||
frameBits.reset(frame);
|
||||
frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE
|
||||
|
|
@ -70,7 +73,7 @@ public final class DtsUtil {
|
|||
frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF
|
||||
channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF
|
||||
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_DTS, bitrate, Format.NO_VALUE,
|
||||
channelCount, sampleRate, null, language);
|
||||
channelCount, sampleRate, null, language, drmInitData);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue