Add support for KEYFORMAT and SAMPLE-AES (only parsing, not decryption)

Also add sample streams that use METHOD=SAMPLE-AES. Note that some of
the streams provide alternative EXT-X-KEY's. Support for alternative
decryption methods will be added in a later CL.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=166048858
This commit is contained in:
aquilescanta 2017-08-22 05:46:00 -07:00 committed by Oliver Woodman
parent c1d7e5bf9d
commit 52ec70dd80
4 changed files with 45 additions and 20 deletions

View file

@ -306,7 +306,8 @@ 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, encryptionKey, encryptionIv);
isTimestampMaster, timestampAdjuster, previous, segment.keyFormat, encryptionKey,
encryptionIv);
}
/**

View file

@ -32,6 +32,7 @@ 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;
@ -116,16 +117,19 @@ 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 encryptionKey For AES encryption chunks, the encryption key.
* @param encryptionIv For AES encryption chunks, the encryption initialization vector.
* @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.
*/
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, byte[] encryptionKey,
byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format,
TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, String keyFormat,
byte[] keyData, byte[] encryptionIv) {
super(buildDataSource(dataSource, keyFormat, keyData, encryptionIv), dataSpec, hlsUrl.format,
trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs, chunkIndex);
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.initDataSpec = initDataSpec;
@ -327,15 +331,16 @@ import java.util.concurrent.atomic.AtomicInteger;
// Internal factory methods.
/**
* If the content is encrypted, returns an {@link Aes128DataSource} that wraps the original in
* order to decrypt the loaded data. Else returns the original.
* 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.
*/
private static DataSource buildDataSource(DataSource dataSource, byte[] encryptionKey,
private static DataSource buildDataSource(DataSource dataSource, String keyFormat, byte[] keyData,
byte[] encryptionIv) {
if (encryptionKey == null || encryptionIv == null) {
return dataSource;
if (HlsMediaPlaylist.KEYFORMAT_IDENTITY.equals(keyFormat)) {
return new Aes128DataSource(dataSource, keyData, encryptionIv);
}
return new Aes128DataSource(dataSource, encryptionKey, encryptionIv);
return dataSource;
}
private Extractor createExtractor() {

View file

@ -53,6 +53,10 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* Whether the segment is encrypted, as defined by #EXT-X-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.
*/
@ -73,7 +77,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, byterangeOffset, byterangeLength);
this(uri, 0, -1, C.TIME_UNSET, false, null, null, null, byterangeOffset, byterangeLength);
}
/**
@ -82,19 +86,21 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* @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 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 encryptionKeyUri, String encryptionIV,
long byterangeOffset, long byterangeLength) {
long relativeStartTimeUs, boolean isEncrypted, String keyFormat, String encryptionKeyUri,
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.encryptionIV = encryptionIV;
this.byterangeOffset = byterangeOffset;
@ -110,7 +116,12 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
}
/**
* Type of the playlist as defined by #EXT-X-PLAYLIST-TYPE.
* 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.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({PLAYLIST_TYPE_UNKNOWN, PLAYLIST_TYPE_VOD, PLAYLIST_TYPE_EVENT})

View file

@ -69,7 +69,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final String TYPE_CLOSED_CAPTIONS = "CLOSED-CAPTIONS";
private static final String METHOD_NONE = "NONE";
private static final String METHOD_AES128 = "AES-128";
private static final String METHOD_AES_128 = "AES-128";
private static final String METHOD_SAMPLE_AES = "SAMPLE-AES";
private static final String BOOLEAN_TRUE = "YES";
private static final String BOOLEAN_FALSE = "NO";
@ -97,7 +98,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final Pattern REGEX_ATTR_BYTERANGE =
Pattern.compile("BYTERANGE=\"(\\d+(?:@\\d+)?)\\b\"");
private static final Pattern REGEX_METHOD = Pattern.compile("METHOD=(" + METHOD_NONE + "|"
+ METHOD_AES128 + ")");
+ METHOD_AES_128 + "|" + METHOD_SAMPLE_AES + ")");
private static final Pattern REGEX_KEYFORMAT = Pattern.compile("KEYFORMAT=\"(.+?)\"");
private static final Pattern REGEX_URI = Pattern.compile("URI=\"(.+?)\"");
private static final Pattern REGEX_IV = Pattern.compile("IV=([^,.*]+)");
private static final Pattern REGEX_TYPE = Pattern.compile("TYPE=(" + TYPE_AUDIO + "|" + TYPE_VIDEO
@ -314,6 +316,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
int segmentMediaSequence = 0;
boolean isEncrypted = false;
String keyFormat = null;
String encryptionKeyUri = null;
String encryptionIV = null;
@ -360,11 +363,16 @@ 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_AES128.equals(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);
encryptionIV = parseOptionalStringAttr(line, REGEX_IV);
} else {
keyFormat = null;
encryptionKeyUri = null;
encryptionIV = null;
}
@ -400,7 +408,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
segmentByteRangeOffset = 0;
}
segments.add(new Segment(line, segmentDurationUs, relativeDiscontinuitySequence,
segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV,
segmentStartTimeUs, isEncrypted, keyFormat, encryptionKeyUri, segmentEncryptionIV,
segmentByteRangeOffset, segmentByteRangeLength));
segmentStartTimeUs += segmentDurationUs;
segmentDurationUs = 0;