Commit 52ec70dd by aquilescanta Committed by Oliver Woodman

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
parent c1d7e5bf
...@@ -306,7 +306,8 @@ import java.util.List; ...@@ -306,7 +306,8 @@ import java.util.List;
out.chunk = new HlsMediaChunk(mediaDataSource, dataSpec, initDataSpec, selectedUrl, out.chunk = new HlsMediaChunk(mediaDataSource, dataSpec, initDataSpec, selectedUrl,
muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(), muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence, startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence,
isTimestampMaster, timestampAdjuster, previous, encryptionKey, encryptionIv); isTimestampMaster, timestampAdjuster, previous, segment.keyFormat, encryptionKey,
encryptionIv);
} }
/** /**
......
...@@ -32,6 +32,7 @@ import com.google.android.exoplayer2.metadata.id3.Id3Decoder; ...@@ -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.metadata.id3.PrivFrame;
import com.google.android.exoplayer2.source.chunk.MediaChunk; 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.HlsMasterPlaylist.HlsUrl;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
...@@ -116,16 +117,19 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -116,16 +117,19 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster. * @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster.
* @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number. * @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number.
* @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null. * @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null.
* @param encryptionKey For AES encryption chunks, the encryption key. * @param keyFormat A string describing the format for {@code keyData}, or null if the chunk is
* @param encryptionIv For AES encryption chunks, the encryption initialization vector. * 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, public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec,
HlsUrl hlsUrl, List<Format> muxedCaptionFormats, int trackSelectionReason, HlsUrl hlsUrl, List<Format> muxedCaptionFormats, int trackSelectionReason,
Object trackSelectionData, long startTimeUs, long endTimeUs, int chunkIndex, Object trackSelectionData, long startTimeUs, long endTimeUs, int chunkIndex,
int discontinuitySequenceNumber, boolean isMasterTimestampSource, int discontinuitySequenceNumber, boolean isMasterTimestampSource,
TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, byte[] encryptionKey, TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, String keyFormat,
byte[] encryptionIv) { byte[] keyData, byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format, super(buildDataSource(dataSource, keyFormat, keyData, encryptionIv), dataSpec, hlsUrl.format,
trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs, chunkIndex); trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs, chunkIndex);
this.discontinuitySequenceNumber = discontinuitySequenceNumber; this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.initDataSpec = initDataSpec; this.initDataSpec = initDataSpec;
...@@ -327,15 +331,16 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -327,15 +331,16 @@ import java.util.concurrent.atomic.AtomicInteger;
// Internal factory methods. // Internal factory methods.
/** /**
* If the content is encrypted, returns an {@link Aes128DataSource} that wraps the original in * If the content is encrypted using the "identity" key format, returns an
* order to decrypt the loaded data. Else returns the original. * {@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) { byte[] encryptionIv) {
if (encryptionKey == null || encryptionIv == null) { if (HlsMediaPlaylist.KEYFORMAT_IDENTITY.equals(keyFormat)) {
return dataSource; return new Aes128DataSource(dataSource, keyData, encryptionIv);
} }
return new Aes128DataSource(dataSource, encryptionKey, encryptionIv); return dataSource;
} }
private Extractor createExtractor() { private Extractor createExtractor() {
......
...@@ -54,6 +54,10 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -54,6 +54,10 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
*/ */
public final boolean isEncrypted; 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. * 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 encryptionKeyUri;
...@@ -73,7 +77,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -73,7 +77,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
public final long byterangeLength; public final long byterangeLength;
public Segment(String uri, long byterangeOffset, 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 { ...@@ -82,19 +86,21 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* @param relativeDiscontinuitySequence See {@link #relativeDiscontinuitySequence}. * @param relativeDiscontinuitySequence See {@link #relativeDiscontinuitySequence}.
* @param relativeStartTimeUs See {@link #relativeStartTimeUs}. * @param relativeStartTimeUs See {@link #relativeStartTimeUs}.
* @param isEncrypted See {@link #isEncrypted}. * @param isEncrypted See {@link #isEncrypted}.
* @param keyFormat See {@link #keyFormat}.
* @param encryptionKeyUri See {@link #encryptionKeyUri}. * @param encryptionKeyUri See {@link #encryptionKeyUri}.
* @param encryptionIV See {@link #encryptionIV}. * @param encryptionIV See {@link #encryptionIV}.
* @param byterangeOffset See {@link #byterangeOffset}. * @param byterangeOffset See {@link #byterangeOffset}.
* @param byterangeLength See {@link #byterangeLength}. * @param byterangeLength See {@link #byterangeLength}.
*/ */
public Segment(String url, long durationUs, int relativeDiscontinuitySequence, public Segment(String url, long durationUs, int relativeDiscontinuitySequence,
long relativeStartTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV, long relativeStartTimeUs, boolean isEncrypted, String keyFormat, String encryptionKeyUri,
long byterangeOffset, long byterangeLength) { String encryptionIV, long byterangeOffset, long byterangeLength) {
this.url = url; this.url = url;
this.durationUs = durationUs; this.durationUs = durationUs;
this.relativeDiscontinuitySequence = relativeDiscontinuitySequence; this.relativeDiscontinuitySequence = relativeDiscontinuitySequence;
this.relativeStartTimeUs = relativeStartTimeUs; this.relativeStartTimeUs = relativeStartTimeUs;
this.isEncrypted = isEncrypted; this.isEncrypted = isEncrypted;
this.keyFormat = keyFormat;
this.encryptionKeyUri = encryptionKeyUri; this.encryptionKeyUri = encryptionKeyUri;
this.encryptionIV = encryptionIV; this.encryptionIV = encryptionIV;
this.byterangeOffset = byterangeOffset; this.byterangeOffset = byterangeOffset;
...@@ -110,7 +116,12 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -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) @Retention(RetentionPolicy.SOURCE)
@IntDef({PLAYLIST_TYPE_UNKNOWN, PLAYLIST_TYPE_VOD, PLAYLIST_TYPE_EVENT}) @IntDef({PLAYLIST_TYPE_UNKNOWN, PLAYLIST_TYPE_VOD, PLAYLIST_TYPE_EVENT})
......
...@@ -69,7 +69,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -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 TYPE_CLOSED_CAPTIONS = "CLOSED-CAPTIONS";
private static final String METHOD_NONE = "NONE"; 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_TRUE = "YES";
private static final String BOOLEAN_FALSE = "NO"; private static final String BOOLEAN_FALSE = "NO";
...@@ -97,7 +98,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -97,7 +98,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final Pattern REGEX_ATTR_BYTERANGE = private static final Pattern REGEX_ATTR_BYTERANGE =
Pattern.compile("BYTERANGE=\"(\\d+(?:@\\d+)?)\\b\""); Pattern.compile("BYTERANGE=\"(\\d+(?:@\\d+)?)\\b\"");
private static final Pattern REGEX_METHOD = Pattern.compile("METHOD=(" + METHOD_NONE + "|" 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_URI = Pattern.compile("URI=\"(.+?)\"");
private static final Pattern REGEX_IV = Pattern.compile("IV=([^,.*]+)"); private static final Pattern REGEX_IV = Pattern.compile("IV=([^,.*]+)");
private static final Pattern REGEX_TYPE = Pattern.compile("TYPE=(" + TYPE_AUDIO + "|" + TYPE_VIDEO 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 ...@@ -314,6 +316,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
int segmentMediaSequence = 0; int segmentMediaSequence = 0;
boolean isEncrypted = false; boolean isEncrypted = false;
String keyFormat = null;
String encryptionKeyUri = null; String encryptionKeyUri = null;
String encryptionIV = null; String encryptionIV = null;
...@@ -360,11 +363,16 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -360,11 +363,16 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
(long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND); (long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND);
} else if (line.startsWith(TAG_KEY)) { } else if (line.startsWith(TAG_KEY)) {
String method = parseStringAttr(line, REGEX_METHOD); String method = parseStringAttr(line, REGEX_METHOD);
isEncrypted = METHOD_AES128.equals(method); isEncrypted = METHOD_AES_128.equals(method) || METHOD_SAMPLE_AES.equals(method);
if (isEncrypted) { if (isEncrypted) {
keyFormat = parseOptionalStringAttr(line, REGEX_KEYFORMAT);
if (keyFormat == null) {
keyFormat = HlsMediaPlaylist.KEYFORMAT_IDENTITY;
}
encryptionKeyUri = parseStringAttr(line, REGEX_URI); encryptionKeyUri = parseStringAttr(line, REGEX_URI);
encryptionIV = parseOptionalStringAttr(line, REGEX_IV); encryptionIV = parseOptionalStringAttr(line, REGEX_IV);
} else { } else {
keyFormat = null;
encryptionKeyUri = null; encryptionKeyUri = null;
encryptionIV = null; encryptionIV = null;
} }
...@@ -400,7 +408,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -400,7 +408,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
segmentByteRangeOffset = 0; segmentByteRangeOffset = 0;
} }
segments.add(new Segment(line, segmentDurationUs, relativeDiscontinuitySequence, segments.add(new Segment(line, segmentDurationUs, relativeDiscontinuitySequence,
segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV, segmentStartTimeUs, isEncrypted, keyFormat, encryptionKeyUri, segmentEncryptionIV,
segmentByteRangeOffset, segmentByteRangeLength)); segmentByteRangeOffset, segmentByteRangeLength));
segmentStartTimeUs += segmentDurationUs; segmentStartTimeUs += segmentDurationUs;
segmentDurationUs = 0; segmentDurationUs = 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment