mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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:
parent
c1d7e5bf9d
commit
52ec70dd80
4 changed files with 45 additions and 20 deletions
|
|
@ -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;
|
||||||
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;
|
||||||
* @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;
|
||||||
// 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() {
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,10 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||||
* Whether the segment is encrypted, as defined by #EXT-X-KEY.
|
* Whether the segment is encrypted, as defined by #EXT-X-KEY.
|
||||||
*/
|
*/
|
||||||
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.
|
||||||
*/
|
*/
|
||||||
|
|
@ -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 {
|
||||||
* @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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
(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
|
||||||
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;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue