提交 6314a0ec 编写于 作者: A aquilescanta 提交者: Oliver Woodman

Add support for Widevine encrypted HLS

This includes both cbcs and cenc. Will only work for streams that require a single
pssh.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=169382884
上级 26d789e6
......@@ -58,7 +58,15 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
*/
public DrmInitData(SchemeData... schemeDatas) {
this(null, true, schemeDatas);
this(null, schemeDatas);
}
/**
* @param schemeType The protection scheme type, or null if not applicable or unknown.
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
*/
public DrmInitData(@Nullable String schemeType, SchemeData... schemeDatas) {
this(schemeType, true, schemeDatas);
}
private DrmInitData(@Nullable String schemeType, boolean cloneSchemeDatas,
......
......@@ -120,6 +120,9 @@ public final class FragmentedMp4Extractor implements Extractor {
@Flags private final int flags;
private final Track sideloadedTrack;
// Manifest DRM data.
private final DrmInitData sideloadedDrmInitData;
// Track-linked data bundle, accessible as a whole through trackID.
private final SparseArray<TrackBundle> trackBundles;
......@@ -179,7 +182,7 @@ public final class FragmentedMp4Extractor implements Extractor {
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
*/
public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster) {
this(flags, timestampAdjuster, null);
this(flags, timestampAdjuster, null, null);
}
/**
......@@ -187,12 +190,14 @@ public final class FragmentedMp4Extractor implements Extractor {
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
* @param sideloadedTrack Sideloaded track information, in the case that the extractor
* will not receive a moov box in the input data.
* @param sideloadedDrmInitData The {@link DrmInitData} to use for encrypted tracks.
*/
public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster,
Track sideloadedTrack) {
Track sideloadedTrack, DrmInitData sideloadedDrmInitData) {
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
this.timestampAdjuster = timestampAdjuster;
this.sideloadedTrack = sideloadedTrack;
this.sideloadedDrmInitData = sideloadedDrmInitData;
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalPrefix = new ParsableByteArray(5);
......@@ -402,7 +407,8 @@ public final class FragmentedMp4Extractor implements Extractor {
private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException {
Assertions.checkState(sideloadedTrack == null, "Unexpected moov box.");
DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren);
DrmInitData drmInitData = sideloadedDrmInitData != null ? sideloadedDrmInitData
: getDrmInitDataFromAtoms(moov.leafChildren);
// Read declaration of track fragments in the Moov box.
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
......@@ -456,7 +462,9 @@ public final class FragmentedMp4Extractor implements Extractor {
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
parseMoof(moof, trackBundles, flags, extendedTypeScratch);
DrmInitData drmInitData = getDrmInitDataFromAtoms(moof.leafChildren);
// If drm init data is sideloaded, we ignore pssh boxes.
DrmInitData drmInitData = sideloadedDrmInitData != null ? null
: getDrmInitDataFromAtoms(moof.leafChildren);
if (drmInitData != null) {
int trackCount = trackBundles.size();
for (int i = 0; i < trackCount; i++) {
......
......@@ -85,9 +85,8 @@ public class HlsMediaPlaylistParserTest extends TestCase {
Segment segment = segments.get(0);
assertEquals(4, mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence);
assertEquals(7975000, segment.durationUs);
assertFalse(segment.isEncrypted);
assertEquals(null, segment.encryptionKeyUri);
assertEquals(null, segment.encryptionIV);
assertNull(segment.fullSegmentEncryptionKeyUri);
assertNull(segment.encryptionIV);
assertEquals(51370, segment.byterangeLength);
assertEquals(0, segment.byterangeOffset);
assertEquals("https://priv.example.com/fileSequence2679.ts", segment.url);
......@@ -95,8 +94,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
segment = segments.get(1);
assertEquals(0, segment.relativeDiscontinuitySequence);
assertEquals(7975000, segment.durationUs);
assertTrue(segment.isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2680", segment.encryptionKeyUri);
assertEquals("https://priv.example.com/key.php?r=2680", segment.fullSegmentEncryptionKeyUri);
assertEquals("0x1566B", segment.encryptionIV);
assertEquals(51501, segment.byterangeLength);
assertEquals(2147483648L, segment.byterangeOffset);
......@@ -105,8 +103,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
segment = segments.get(2);
assertEquals(0, segment.relativeDiscontinuitySequence);
assertEquals(7941000, segment.durationUs);
assertFalse(segment.isEncrypted);
assertEquals(null, segment.encryptionKeyUri);
assertNull(segment.fullSegmentEncryptionKeyUri);
assertEquals(null, segment.encryptionIV);
assertEquals(51501, segment.byterangeLength);
assertEquals(2147535149L, segment.byterangeOffset);
......@@ -115,8 +112,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
segment = segments.get(3);
assertEquals(1, segment.relativeDiscontinuitySequence);
assertEquals(7975000, segment.durationUs);
assertTrue(segment.isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2682", segment.encryptionKeyUri);
assertEquals("https://priv.example.com/key.php?r=2682", segment.fullSegmentEncryptionKeyUri);
// 0xA7A == 2682.
assertNotNull(segment.encryptionIV);
assertEquals("A7A", segment.encryptionIV.toUpperCase(Locale.getDefault()));
......@@ -127,8 +123,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
segment = segments.get(4);
assertEquals(1, segment.relativeDiscontinuitySequence);
assertEquals(7975000, segment.durationUs);
assertTrue(segment.isEncrypted);
assertEquals("https://priv.example.com/key.php?r=2682", segment.encryptionKeyUri);
assertEquals("https://priv.example.com/key.php?r=2682", segment.fullSegmentEncryptionKeyUri);
// 0xA7B == 2683.
assertNotNull(segment.encryptionIV);
assertEquals("A7B", segment.encryptionIV.toUpperCase(Locale.getDefault()));
......
......@@ -276,9 +276,9 @@ import java.util.List;
// Handle encryption.
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex);
// Check if encryption is specified.
if (segment.isEncrypted) {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
// Check if the segment is completely encrypted using the identity key format.
if (segment.fullSegmentEncryptionKeyUri != null) {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.fullSegmentEncryptionKeyUri);
if (!keyUri.equals(encryptionKeyUri)) {
// Encryption is specified and the key has changed.
out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex,
......@@ -314,7 +314,7 @@ import java.util.List;
out.chunk = new HlsMediaChunk(mediaDataSource, dataSpec, initDataSpec, selectedUrl,
muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence,
isTimestampMaster, timestampAdjuster, previous, segment.keyFormat, encryptionKey,
isTimestampMaster, timestampAdjuster, previous, mediaPlaylist.drmInitData, encryptionKey,
encryptionIv);
}
......
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.hls;
import android.text.TextUtils;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
......@@ -32,7 +33,6 @@ import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
import com.google.android.exoplayer2.metadata.id3.PrivFrame;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.MimeTypes;
......@@ -88,6 +88,7 @@ import java.util.concurrent.atomic.AtomicInteger;
private final boolean shouldSpliceIn;
private final boolean needNewExtractor;
private final List<Format> muxedCaptionFormats;
private final DrmInitData drmInitData;
private final boolean isPackedAudio;
private final Id3Decoder id3Decoder;
......@@ -117,20 +118,21 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster.
* @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number.
* @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null.
* @param keyFormat A string describing the format for {@code keyData}, or null if the chunk is
* not encrypted.
* @param keyData Data specifying how to obtain the keys to decrypt the chunk, or null if the
* chunk is not encrypted.
* @param encryptionIv The AES initialization vector, or null if the chunk is not encrypted.
* @param drmInitData A {@link DrmInitData} to sideload to the extractor.
* @param fullSegmentEncryptionKey The key to decrypt the full segment, or null if the segment is
* not fully encrypted.
* @param encryptionIv The AES initialization vector, or null if the segment is not fully
* encrypted.
*/
public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec,
HlsUrl hlsUrl, List<Format> muxedCaptionFormats, int trackSelectionReason,
Object trackSelectionData, long startTimeUs, long endTimeUs, int chunkIndex,
int discontinuitySequenceNumber, boolean isMasterTimestampSource,
TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, String keyFormat,
byte[] keyData, byte[] encryptionIv) {
super(buildDataSource(dataSource, keyFormat, keyData, encryptionIv), dataSpec, hlsUrl.format,
trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs, chunkIndex);
TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, DrmInitData drmInitData,
byte[] fullSegmentEncryptionKey, byte[] encryptionIv) {
super(buildDataSource(dataSource, fullSegmentEncryptionKey, encryptionIv), dataSpec,
hlsUrl.format, trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs,
chunkIndex);
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.initDataSpec = initDataSpec;
this.hlsUrl = hlsUrl;
......@@ -139,6 +141,7 @@ import java.util.concurrent.atomic.AtomicInteger;
this.timestampAdjuster = timestampAdjuster;
// Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource;
this.drmInitData = drmInitData;
lastPathSegment = dataSpec.uri.getLastPathSegment();
isPackedAudio = lastPathSegment.endsWith(AAC_FILE_EXTENSION)
|| lastPathSegment.endsWith(AC3_FILE_EXTENSION)
......@@ -331,14 +334,13 @@ import java.util.concurrent.atomic.AtomicInteger;
// Internal factory methods.
/**
* If the content is encrypted using the "identity" key format, returns an
* {@link Aes128DataSource} that wraps the original in order to decrypt the loaded data. Else
* returns the original.
* If the segment is fully encrypted, returns an {@link Aes128DataSource} that wraps the original
* in order to decrypt the loaded data. Else returns the original.
*/
private static DataSource buildDataSource(DataSource dataSource, String keyFormat, byte[] keyData,
private static DataSource buildDataSource(DataSource dataSource, byte[] fullSegmentEncryptionKey,
byte[] encryptionIv) {
if (HlsMediaPlaylist.KEYFORMAT_IDENTITY.equals(keyFormat)) {
return new Aes128DataSource(dataSource, keyData, encryptionIv);
if (fullSegmentEncryptionKey != null) {
return new Aes128DataSource(dataSource, fullSegmentEncryptionKey, encryptionIv);
}
return dataSource;
}
......@@ -357,7 +359,7 @@ import java.util.concurrent.atomic.AtomicInteger;
extractor = previousExtractor;
} else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)
|| lastPathSegment.startsWith(M4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 4)) {
extractor = new FragmentedMp4Extractor(0, timestampAdjuster);
extractor = new FragmentedMp4Extractor(0, timestampAdjuster, null, drmInitData);
} else {
// MPEG-2 TS segments, but we need a new extractor.
// This flag ensures the change of pid between streams does not affect the sample queues.
......
......@@ -117,8 +117,9 @@ public final class HlsDownloader extends SegmentDownloader<HlsMasterPlaylist, St
HlsMediaPlaylist.Segment hlsSegment, HashSet<Uri> encryptionKeyUris)
throws IOException, InterruptedException {
long startTimeUs = mediaPlaylist.startTimeUs + hlsSegment.relativeStartTimeUs;
if (hlsSegment.isEncrypted) {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, hlsSegment.encryptionKeyUri);
if (hlsSegment.fullSegmentEncryptionKeyUri != null) {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri,
hlsSegment.fullSegmentEncryptionKeyUri);
if (encryptionKeyUris.add(keyUri)) {
segments.add(new Segment(startTimeUs, new DataSpec(keyUri)));
}
......
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.hls.playlist;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DrmInitData;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
......@@ -50,17 +51,10 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
*/
public final long relativeStartTimeUs;
/**
* Whether the segment is encrypted, as defined by #EXT-X-KEY.
* The encryption identity key uri as defined by #EXT-X-KEY, or null if the segment does not use
* full segment encryption with identity key.
*/
public final boolean isEncrypted;
/**
* The key format as defined by #EXT-X-KEY, or null if the segment is not encrypted.
*/
public final String keyFormat;
/**
* The encryption key uri as defined by #EXT-X-KEY, or null if the segment is not encrypted.
*/
public final String encryptionKeyUri;
public final String fullSegmentEncryptionKeyUri;
/**
* The encryption initialization vector as defined by #EXT-X-KEY, or null if the segment is not
* encrypted.
......@@ -77,7 +71,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
public final long byterangeLength;
public Segment(String uri, long byterangeOffset, long byterangeLength) {
this(uri, 0, -1, C.TIME_UNSET, false, null, null, null, byterangeOffset, byterangeLength);
this(uri, 0, -1, C.TIME_UNSET, null, null, byterangeOffset, byterangeLength);
}
/**
......@@ -85,23 +79,19 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* @param durationUs See {@link #durationUs}.
* @param relativeDiscontinuitySequence See {@link #relativeDiscontinuitySequence}.
* @param relativeStartTimeUs See {@link #relativeStartTimeUs}.
* @param isEncrypted See {@link #isEncrypted}.
* @param keyFormat See {@link #keyFormat}.
* @param encryptionKeyUri See {@link #encryptionKeyUri}.
* @param fullSegmentEncryptionKeyUri See {@link #fullSegmentEncryptionKeyUri}.
* @param encryptionIV See {@link #encryptionIV}.
* @param byterangeOffset See {@link #byterangeOffset}.
* @param byterangeLength See {@link #byterangeLength}.
*/
public Segment(String url, long durationUs, int relativeDiscontinuitySequence,
long relativeStartTimeUs, boolean isEncrypted, String keyFormat, String encryptionKeyUri,
long relativeStartTimeUs, String fullSegmentEncryptionKeyUri,
String encryptionIV, long byterangeOffset, long byterangeLength) {
this.url = url;
this.durationUs = durationUs;
this.relativeDiscontinuitySequence = relativeDiscontinuitySequence;
this.relativeStartTimeUs = relativeStartTimeUs;
this.isEncrypted = isEncrypted;
this.keyFormat = keyFormat;
this.encryptionKeyUri = encryptionKeyUri;
this.fullSegmentEncryptionKeyUri = fullSegmentEncryptionKeyUri;
this.encryptionIV = encryptionIV;
this.byterangeOffset = byterangeOffset;
this.byterangeLength = byterangeLength;
......@@ -115,11 +105,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
}
/**
* The identity key format, as defined by #EXT-X-KEY.
*/
public static final String KEYFORMAT_IDENTITY = "identity";
/**
* Type of the playlist, as defined by #EXT-X-PLAYLIST-TYPE.
*/
......@@ -176,6 +161,11 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* Whether the playlist contains a #EXT-X-PROGRAM-DATE-TIME tag.
*/
public final boolean hasProgramDateTime;
/**
* DRM initialization data for sample decryption, or null if none of the segment uses sample
* encryption.
*/
public final DrmInitData drmInitData;
/**
* The initialization segment, as defined by #EXT-X-MAP.
*/
......@@ -203,6 +193,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* @param hasIndependentSegmentsTag See {@link #hasIndependentSegmentsTag}.
* @param hasEndTag See {@link #hasEndTag}.
* @param hasProgramDateTime See {@link #hasProgramDateTime}.
* @param drmInitData See {@link #drmInitData}.
* @param initializationSegment See {@link #initializationSegment}.
* @param segments See {@link #segments}.
*/
......@@ -210,7 +201,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
long startOffsetUs, long startTimeUs, boolean hasDiscontinuitySequence,
int discontinuitySequence, int mediaSequence, int version, long targetDurationUs,
boolean hasIndependentSegmentsTag, boolean hasEndTag, boolean hasProgramDateTime,
Segment initializationSegment, List<Segment> segments) {
DrmInitData drmInitData, Segment initializationSegment, List<Segment> segments) {
super(baseUri, tags);
this.playlistType = playlistType;
this.startTimeUs = startTimeUs;
......@@ -222,6 +213,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
this.hasIndependentSegmentsTag = hasIndependentSegmentsTag;
this.hasEndTag = hasEndTag;
this.hasProgramDateTime = hasProgramDateTime;
this.drmInitData = drmInitData;
this.initializationSegment = initializationSegment;
this.segments = Collections.unmodifiableList(segments);
if (!segments.isEmpty()) {
......@@ -273,7 +265,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
public HlsMediaPlaylist copyWith(long startTimeUs, int discontinuitySequence) {
return new HlsMediaPlaylist(playlistType, baseUri, tags, startOffsetUs, startTimeUs, true,
discontinuitySequence, mediaSequence, version, targetDurationUs, hasIndependentSegmentsTag,
hasEndTag, hasProgramDateTime, initializationSegment, segments);
hasEndTag, hasProgramDateTime, drmInitData, initializationSegment, segments);
}
/**
......@@ -288,7 +280,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
}
return new HlsMediaPlaylist(playlistType, baseUri, tags, startOffsetUs, startTimeUs,
hasDiscontinuitySequence, discontinuitySequence, mediaSequence, version, targetDurationUs,
hasIndependentSegmentsTag, true, hasProgramDateTime, initializationSegment, segments);
hasIndependentSegmentsTag, true, hasProgramDateTime, drmInitData, initializationSegment,
segments);
}
}
......@@ -16,9 +16,12 @@
package com.google.android.exoplayer2.source.hls.playlist;
import android.net.Uri;
import android.util.Base64;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.source.UnrecognizedInputFormatException;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
......@@ -28,6 +31,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
......@@ -71,6 +75,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final String METHOD_NONE = "NONE";
private static final String METHOD_AES_128 = "AES-128";
private static final String METHOD_SAMPLE_AES = "SAMPLE-AES";
private static final String METHOD_SAMPLE_AES_CENC = "SAMPLE-AES-CENC";
private static final String KEYFORMAT_IDENTITY = "identity";
private static final String KEYFORMAT_WIDEVINE_PSSH_BINARY =
"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
private static final String KEYFORMAT_WIDEVINE_PSSH_JSON = "com.widevine";
private static final String BOOLEAN_TRUE = "YES";
private static final String BOOLEAN_FALSE = "NO";
......@@ -315,10 +324,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
long segmentByteRangeLength = C.LENGTH_UNSET;
int segmentMediaSequence = 0;
boolean isEncrypted = false;
String keyFormat = null;
String encryptionKeyUri = null;
String encryptionIV = null;
DrmInitData drmInitData = null;
String line;
while (iterator.hasNext()) {
......@@ -363,18 +371,26 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
(long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND);
} else if (line.startsWith(TAG_KEY)) {
String method = parseStringAttr(line, REGEX_METHOD);
isEncrypted = METHOD_AES_128.equals(method) || METHOD_SAMPLE_AES.equals(method);
if (isEncrypted) {
keyFormat = parseOptionalStringAttr(line, REGEX_KEYFORMAT);
if (keyFormat == null) {
keyFormat = HlsMediaPlaylist.KEYFORMAT_IDENTITY;
}
encryptionKeyUri = parseStringAttr(line, REGEX_URI);
String keyFormat = parseOptionalStringAttr(line, REGEX_KEYFORMAT);
encryptionKeyUri = null;
encryptionIV = null;
if (!METHOD_NONE.equals(method)) {
encryptionIV = parseOptionalStringAttr(line, REGEX_IV);
} else {
keyFormat = null;
encryptionKeyUri = null;
encryptionIV = null;
if (KEYFORMAT_IDENTITY.equals(keyFormat) || keyFormat == null) {
if (METHOD_AES_128.equals(method)) {
// The segment is fully encrypted using an identity key.
encryptionKeyUri = parseStringAttr(line, REGEX_URI);
} else {
// Do nothing. Samples are encrypted using an identity key, but this is not supported.
// Hopefully, a traditional DRM alternative is also provided.
}
} else {
SchemeData schemeData = parseWidevineSchemeData(line, keyFormat);
if (schemeData != null) {
drmInitData = new DrmInitData(METHOD_SAMPLE_AES_CENC.equals(method)
? C.CENC_TYPE_cenc : C.CENC_TYPE_cbcs, schemeData);
}
}
}
} else if (line.startsWith(TAG_BYTERANGE)) {
String byteRange = parseStringAttr(line, REGEX_BYTERANGE);
......@@ -396,7 +412,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
}
} else if (!line.startsWith("#")) {
String segmentEncryptionIV;
if (!isEncrypted) {
if (encryptionKeyUri == null) {
segmentEncryptionIV = null;
} else if (encryptionIV != null) {
segmentEncryptionIV = encryptionIV;
......@@ -408,7 +424,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
segmentByteRangeOffset = 0;
}
segments.add(new Segment(line, segmentDurationUs, relativeDiscontinuitySequence,
segmentStartTimeUs, isEncrypted, keyFormat, encryptionKeyUri, segmentEncryptionIV,
segmentStartTimeUs, encryptionKeyUri, segmentEncryptionIV,
segmentByteRangeOffset, segmentByteRangeLength));
segmentStartTimeUs += segmentDurationUs;
segmentDurationUs = 0;
......@@ -425,7 +441,24 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
return new HlsMediaPlaylist(playlistType, baseUri, tags, startOffsetUs, playlistStartTimeUs,
hasDiscontinuitySequence, playlistDiscontinuitySequence, mediaSequence, version,
targetDurationUs, hasIndependentSegmentsTag, hasEndTag, playlistStartTimeUs != 0,
initializationSegment, segments);
drmInitData, initializationSegment, segments);
}
private static SchemeData parseWidevineSchemeData(String line, String keyFormat)
throws ParserException {
if (KEYFORMAT_WIDEVINE_PSSH_BINARY.equals(keyFormat)) {
String uriString = parseStringAttr(line, REGEX_URI);
return new SchemeData(C.WIDEVINE_UUID, MimeTypes.VIDEO_MP4,
Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT));
}
if (KEYFORMAT_WIDEVINE_PSSH_JSON.equals(keyFormat)) {
try {
return new SchemeData(C.WIDEVINE_UUID, "hls", line.getBytes(C.UTF8_NAME));
} catch (UnsupportedEncodingException e) {
throw new ParserException(e);
}
}
return null;
}
private static int parseIntAttr(String line, Pattern pattern) throws ParserException {
......
......@@ -101,7 +101,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
trackEncryptionBoxes, nalUnitLengthFieldLength, null, null);
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
| FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, null, track);
| FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, null, track, null);
extractorWrappers[i] = new ChunkExtractorWrapper(extractor, streamElement.type, format);
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册