mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Support encrypted initialization segment
Defined in RFC 8216 Section 4.3.2.5. Issue:#5441 PiperOrigin-RevId: 234114119
This commit is contained in:
parent
d61171a100
commit
8982da4b75
3 changed files with 173 additions and 105 deletions
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
* HLS:
|
* HLS:
|
||||||
* Form an adaptive track group out of audio renditions with matching name.
|
* Form an adaptive track group out of audio renditions with matching name.
|
||||||
|
* Support encrypted initialization segments
|
||||||
|
([#5441](https://github.com/google/ExoPlayer/issues/5441)).
|
||||||
* DASH:
|
* DASH:
|
||||||
* Fix issue handling large `EventStream` presentation timestamps
|
* Fix issue handling large `EventStream` presentation timestamps
|
||||||
([#5490](https://github.com/google/ExoPlayer/issues/5490)).
|
([#5490](https://github.com/google/ExoPlayer/issues/5490)).
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,11 @@ import com.google.android.exoplayer2.util.TimestampAdjuster;
|
||||||
import com.google.android.exoplayer2.util.UriUtil;
|
import com.google.android.exoplayer2.util.UriUtil;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source of Hls (possibly adaptive) chunks.
|
* Source of Hls (possibly adaptive) chunks.
|
||||||
|
|
@ -84,6 +86,12 @@ import java.util.List;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of keys that the key cache can hold. This value must be 2 or greater in
|
||||||
|
* order to hold initialization segment and media segment keys simultaneously.
|
||||||
|
*/
|
||||||
|
private static final int KEY_CACHE_SIZE = 4;
|
||||||
|
|
||||||
private final HlsExtractorFactory extractorFactory;
|
private final HlsExtractorFactory extractorFactory;
|
||||||
private final DataSource mediaDataSource;
|
private final DataSource mediaDataSource;
|
||||||
private final DataSource encryptionDataSource;
|
private final DataSource encryptionDataSource;
|
||||||
|
|
@ -92,6 +100,7 @@ import java.util.List;
|
||||||
private final HlsPlaylistTracker playlistTracker;
|
private final HlsPlaylistTracker playlistTracker;
|
||||||
private final TrackGroup trackGroup;
|
private final TrackGroup trackGroup;
|
||||||
private final List<Format> muxedCaptionFormats;
|
private final List<Format> muxedCaptionFormats;
|
||||||
|
private final FullSegmentEncryptionKeyCache keyCache;
|
||||||
|
|
||||||
private boolean isTimestampMaster;
|
private boolean isTimestampMaster;
|
||||||
private byte[] scratchSpace;
|
private byte[] scratchSpace;
|
||||||
|
|
@ -99,11 +108,6 @@ import java.util.List;
|
||||||
private HlsUrl expectedPlaylistUrl;
|
private HlsUrl expectedPlaylistUrl;
|
||||||
private boolean independentSegments;
|
private boolean independentSegments;
|
||||||
|
|
||||||
private Uri encryptionKeyUri;
|
|
||||||
private byte[] encryptionKey;
|
|
||||||
private String encryptionIvString;
|
|
||||||
private byte[] encryptionIv;
|
|
||||||
|
|
||||||
// Note: The track group in the selection is typically *not* equal to trackGroup. This is due to
|
// Note: The track group in the selection is typically *not* equal to trackGroup. This is due to
|
||||||
// the way in which HlsSampleStreamWrapper generates track groups. Use only index based methods
|
// the way in which HlsSampleStreamWrapper generates track groups. Use only index based methods
|
||||||
// in TrackSelection to avoid unexpected behavior.
|
// in TrackSelection to avoid unexpected behavior.
|
||||||
|
|
@ -139,6 +143,7 @@ import java.util.List;
|
||||||
this.variants = variants;
|
this.variants = variants;
|
||||||
this.timestampAdjusterProvider = timestampAdjusterProvider;
|
this.timestampAdjusterProvider = timestampAdjusterProvider;
|
||||||
this.muxedCaptionFormats = muxedCaptionFormats;
|
this.muxedCaptionFormats = muxedCaptionFormats;
|
||||||
|
keyCache = new FullSegmentEncryptionKeyCache();
|
||||||
liveEdgeInPeriodTimeUs = C.TIME_UNSET;
|
liveEdgeInPeriodTimeUs = C.TIME_UNSET;
|
||||||
Format[] variantFormats = new Format[variants.length];
|
Format[] variantFormats = new Format[variants.length];
|
||||||
int[] initialTrackSelection = new int[variants.length];
|
int[] initialTrackSelection = new int[variants.length];
|
||||||
|
|
@ -308,20 +313,16 @@ import java.util.List;
|
||||||
// Handle encryption.
|
// Handle encryption.
|
||||||
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(segmentIndexInPlaylist);
|
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(segmentIndexInPlaylist);
|
||||||
|
|
||||||
// Check if the segment is completely encrypted using the identity key format.
|
// Check if the segment or its initialization segment are fully encrypted.
|
||||||
if (segment.fullSegmentEncryptionKeyUri != null) {
|
out.chunk =
|
||||||
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.fullSegmentEncryptionKeyUri);
|
maybeCreateEncryptionChunkFor(
|
||||||
if (!keyUri.equals(encryptionKeyUri)) {
|
segment.initializationSegment, mediaPlaylist, selectedVariantIndex);
|
||||||
// Encryption is specified and the key has changed.
|
if (out.chunk != null) {
|
||||||
out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex,
|
return;
|
||||||
trackSelection.getSelectionReason(), trackSelection.getSelectionData());
|
}
|
||||||
return;
|
out.chunk = maybeCreateEncryptionChunkFor(segment, mediaPlaylist, selectedVariantIndex);
|
||||||
}
|
if (out.chunk != null) {
|
||||||
if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) {
|
return;
|
||||||
setEncryptionData(keyUri, segment.encryptionIV, encryptionKey);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
clearEncryptionData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out.chunk =
|
out.chunk =
|
||||||
|
|
@ -338,8 +339,7 @@ import java.util.List;
|
||||||
isTimestampMaster,
|
isTimestampMaster,
|
||||||
timestampAdjusterProvider,
|
timestampAdjusterProvider,
|
||||||
previous,
|
previous,
|
||||||
encryptionKey,
|
keyCache.asUnmodifiable());
|
||||||
encryptionIv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -352,8 +352,7 @@ import java.util.List;
|
||||||
if (chunk instanceof EncryptionKeyChunk) {
|
if (chunk instanceof EncryptionKeyChunk) {
|
||||||
EncryptionKeyChunk encryptionKeyChunk = (EncryptionKeyChunk) chunk;
|
EncryptionKeyChunk encryptionKeyChunk = (EncryptionKeyChunk) chunk;
|
||||||
scratchSpace = encryptionKeyChunk.getDataHolder();
|
scratchSpace = encryptionKeyChunk.getDataHolder();
|
||||||
setEncryptionData(encryptionKeyChunk.dataSpec.uri, encryptionKeyChunk.iv,
|
keyCache.put(encryptionKeyChunk.dataSpec.uri, encryptionKeyChunk.getResult());
|
||||||
encryptionKeyChunk.getResult());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -486,38 +485,27 @@ import java.util.List;
|
||||||
: (mediaPlaylist.getEndTimeUs() - playlistTracker.getInitialStartTimeUs());
|
: (mediaPlaylist.getEndTimeUs() - playlistTracker.getInitialStartTimeUs());
|
||||||
}
|
}
|
||||||
|
|
||||||
private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv, int variantIndex,
|
private Chunk maybeCreateEncryptionChunkFor(
|
||||||
int trackSelectionReason, Object trackSelectionData) {
|
@Nullable Segment segment, HlsMediaPlaylist mediaPlaylist, int selectedVariantIndex) {
|
||||||
DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNSET, null, DataSpec.FLAG_ALLOW_GZIP);
|
if (segment == null || segment.fullSegmentEncryptionKeyUri == null) {
|
||||||
return new EncryptionKeyChunk(encryptionDataSource, dataSpec, variants[variantIndex].format,
|
return null;
|
||||||
trackSelectionReason, trackSelectionData, scratchSpace, iv);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setEncryptionData(Uri keyUri, String iv, byte[] secretKey) {
|
|
||||||
String trimmedIv;
|
|
||||||
if (Util.toLowerInvariant(iv).startsWith("0x")) {
|
|
||||||
trimmedIv = iv.substring(2);
|
|
||||||
} else {
|
|
||||||
trimmedIv = iv;
|
|
||||||
}
|
}
|
||||||
|
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.fullSegmentEncryptionKeyUri);
|
||||||
byte[] ivData = new BigInteger(trimmedIv, 16).toByteArray();
|
if (keyCache.containsKey(keyUri)) {
|
||||||
byte[] ivDataWithPadding = new byte[16];
|
// The key is present in the key cache. We re-insert it to prevent it from being evicted by
|
||||||
int offset = ivData.length > 16 ? ivData.length - 16 : 0;
|
// the following key addition. Note that removal of the key is necessary to affect the
|
||||||
System.arraycopy(ivData, offset, ivDataWithPadding, ivDataWithPadding.length - ivData.length
|
// eviction order.
|
||||||
+ offset, ivData.length - offset);
|
keyCache.put(keyUri, keyCache.remove(keyUri));
|
||||||
|
return null;
|
||||||
encryptionKeyUri = keyUri;
|
}
|
||||||
encryptionKey = secretKey;
|
DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNSET, null, DataSpec.FLAG_ALLOW_GZIP);
|
||||||
encryptionIvString = iv;
|
return new EncryptionKeyChunk(
|
||||||
encryptionIv = ivDataWithPadding;
|
encryptionDataSource,
|
||||||
}
|
dataSpec,
|
||||||
|
variants[selectedVariantIndex].format,
|
||||||
private void clearEncryptionData() {
|
trackSelection.getSelectionReason(),
|
||||||
encryptionKeyUri = null;
|
trackSelection.getSelectionData(),
|
||||||
encryptionKey = null;
|
scratchSpace);
|
||||||
encryptionIvString = null;
|
|
||||||
encryptionIv = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private classes.
|
// Private classes.
|
||||||
|
|
@ -575,19 +563,21 @@ import java.util.List;
|
||||||
|
|
||||||
private static final class EncryptionKeyChunk extends DataChunk {
|
private static final class EncryptionKeyChunk extends DataChunk {
|
||||||
|
|
||||||
public final String iv;
|
|
||||||
|
|
||||||
private byte[] result;
|
private byte[] result;
|
||||||
|
|
||||||
public EncryptionKeyChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat,
|
public EncryptionKeyChunk(
|
||||||
int trackSelectionReason, Object trackSelectionData, byte[] scratchSpace, String iv) {
|
DataSource dataSource,
|
||||||
|
DataSpec dataSpec,
|
||||||
|
Format trackFormat,
|
||||||
|
int trackSelectionReason,
|
||||||
|
Object trackSelectionData,
|
||||||
|
byte[] scratchSpace) {
|
||||||
super(dataSource, dataSpec, C.DATA_TYPE_DRM, trackFormat, trackSelectionReason,
|
super(dataSource, dataSpec, C.DATA_TYPE_DRM, trackFormat, trackSelectionReason,
|
||||||
trackSelectionData, scratchSpace);
|
trackSelectionData, scratchSpace);
|
||||||
this.iv = iv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void consume(byte[] data, int limit) throws IOException {
|
protected void consume(byte[] data, int limit) {
|
||||||
result = Arrays.copyOf(data, limit);
|
result = Arrays.copyOf(data, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -642,4 +632,29 @@ import java.util.List;
|
||||||
return segmentStartTimeInPeriodUs + segment.durationUs;
|
return segmentStartTimeInPeriodUs + segment.durationUs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LRU cache that holds up to {@link #KEY_CACHE_SIZE} full-segment-encryption keys. Which each
|
||||||
|
* addition, once the cache's size exceeds {@link #KEY_CACHE_SIZE}, the oldest item (according to
|
||||||
|
* insertion order) is removed.
|
||||||
|
*/
|
||||||
|
private static final class FullSegmentEncryptionKeyCache extends LinkedHashMap<Uri, byte[]> {
|
||||||
|
|
||||||
|
private final Map<Uri, byte[]> unmodifiableView;
|
||||||
|
|
||||||
|
public FullSegmentEncryptionKeyCache() {
|
||||||
|
super(
|
||||||
|
/* initialCapacity= */ KEY_CACHE_SIZE * 2, /* loadFactor= */ 1, /* accessOrder= */ false);
|
||||||
|
unmodifiableView = Collections.unmodifiableMap(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<Uri, byte[]> entry) {
|
||||||
|
return size() > KEY_CACHE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Uri, byte[]> asUnmodifiable() {
|
||||||
|
return unmodifiableView;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
package com.google.android.exoplayer2.source.hls;
|
package com.google.android.exoplayer2.source.hls;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
|
@ -37,7 +38,9 @@ import com.google.android.exoplayer2.util.UriUtil;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -62,10 +65,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
* @param timestampAdjusterProvider The provider from which to obtain the {@link
|
* @param timestampAdjusterProvider The provider from which to obtain the {@link
|
||||||
* TimestampAdjuster}.
|
* TimestampAdjuster}.
|
||||||
* @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 fullSegmentEncryptionKey The key to decrypt the full segment, or null if the segment is
|
* @param keyCache A map from encryption key URI to the corresponding encryption key.
|
||||||
* not fully encrypted.
|
|
||||||
* @param encryptionIv The AES initialization vector, or null if the segment is not fully
|
|
||||||
* encrypted.
|
|
||||||
*/
|
*/
|
||||||
public static HlsMediaChunk createInstance(
|
public static HlsMediaChunk createInstance(
|
||||||
HlsExtractorFactory extractorFactory,
|
HlsExtractorFactory extractorFactory,
|
||||||
|
|
@ -74,26 +74,41 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
HlsMediaPlaylist mediaPlaylist,
|
HlsMediaPlaylist mediaPlaylist,
|
||||||
int segmentIndexInPlaylist,
|
int segmentIndexInPlaylist,
|
||||||
HlsUrl hlsUrl,
|
HlsUrl hlsUrl,
|
||||||
List<Format> muxedCaptionFormats,
|
@Nullable List<Format> muxedCaptionFormats,
|
||||||
int trackSelectionReason,
|
int trackSelectionReason,
|
||||||
Object trackSelectionData,
|
@Nullable Object trackSelectionData,
|
||||||
boolean isMasterTimestampSource,
|
boolean isMasterTimestampSource,
|
||||||
TimestampAdjusterProvider timestampAdjusterProvider,
|
TimestampAdjusterProvider timestampAdjusterProvider,
|
||||||
HlsMediaChunk previousChunk,
|
@Nullable HlsMediaChunk previousChunk,
|
||||||
byte[] fullSegmentEncryptionKey,
|
Map<Uri, byte[]> keyCache) {
|
||||||
byte[] encryptionIv) {
|
// Media segment.
|
||||||
|
HlsMediaPlaylist.Segment mediaSegment = mediaPlaylist.segments.get(segmentIndexInPlaylist);
|
||||||
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(segmentIndexInPlaylist);
|
|
||||||
DataSpec dataSpec =
|
DataSpec dataSpec =
|
||||||
new DataSpec(
|
new DataSpec(
|
||||||
UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url),
|
UriUtil.resolveToUri(mediaPlaylist.baseUri, mediaSegment.url),
|
||||||
segment.byterangeOffset,
|
mediaSegment.byterangeOffset,
|
||||||
segment.byterangeLength,
|
mediaSegment.byterangeLength,
|
||||||
/* key= */ null);
|
/* key= */ null);
|
||||||
|
byte[] mediaSegmentKey =
|
||||||
|
keyCache.get(
|
||||||
|
UriUtil.resolveToUri(mediaPlaylist.baseUri, mediaSegment.fullSegmentEncryptionKeyUri));
|
||||||
|
boolean mediaSegmentEncrypted = mediaSegmentKey != null;
|
||||||
|
byte[] mediaSegmentIv =
|
||||||
|
mediaSegmentEncrypted ? getEncryptionIvArray(mediaSegment.encryptionIV) : null;
|
||||||
|
DataSource mediaDataSource = buildDataSource(dataSource, mediaSegmentKey, mediaSegmentIv);
|
||||||
|
|
||||||
|
// Init segment.
|
||||||
|
HlsMediaPlaylist.Segment initSegment = mediaSegment.initializationSegment;
|
||||||
DataSpec initDataSpec = null;
|
DataSpec initDataSpec = null;
|
||||||
HlsMediaPlaylist.Segment initSegment = segment.initializationSegment;
|
boolean initSegmentEncrypted = false;
|
||||||
|
DataSource initDataSource = null;
|
||||||
if (initSegment != null) {
|
if (initSegment != null) {
|
||||||
|
byte[] initSegmentKey =
|
||||||
|
keyCache.get(
|
||||||
|
UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.fullSegmentEncryptionKeyUri));
|
||||||
|
initSegmentEncrypted = initSegmentKey != null;
|
||||||
|
byte[] initSegmentIv =
|
||||||
|
initSegmentEncrypted ? getEncryptionIvArray(initSegment.encryptionIV) : null;
|
||||||
Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url);
|
Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url);
|
||||||
initDataSpec =
|
initDataSpec =
|
||||||
new DataSpec(
|
new DataSpec(
|
||||||
|
|
@ -101,12 +116,13 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
initSegment.byterangeOffset,
|
initSegment.byterangeOffset,
|
||||||
initSegment.byterangeLength,
|
initSegment.byterangeLength,
|
||||||
/* key= */ null);
|
/* key= */ null);
|
||||||
|
initDataSource = buildDataSource(dataSource, initSegmentKey, initSegmentIv);
|
||||||
}
|
}
|
||||||
|
|
||||||
long segmentStartTimeInPeriodUs = startOfPlaylistInPeriodUs + segment.relativeStartTimeUs;
|
long segmentStartTimeInPeriodUs = startOfPlaylistInPeriodUs + mediaSegment.relativeStartTimeUs;
|
||||||
long segmentEndTimeInPeriodUs = segmentStartTimeInPeriodUs + segment.durationUs;
|
long segmentEndTimeInPeriodUs = segmentStartTimeInPeriodUs + mediaSegment.durationUs;
|
||||||
int discontinuitySequenceNumber =
|
int discontinuitySequenceNumber =
|
||||||
mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence;
|
mediaPlaylist.discontinuitySequence + mediaSegment.relativeDiscontinuitySequence;
|
||||||
|
|
||||||
Extractor previousExtractor = null;
|
Extractor previousExtractor = null;
|
||||||
Id3Decoder id3Decoder;
|
Id3Decoder id3Decoder;
|
||||||
|
|
@ -128,9 +144,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
return new HlsMediaChunk(
|
return new HlsMediaChunk(
|
||||||
extractorFactory,
|
extractorFactory,
|
||||||
dataSource,
|
mediaDataSource,
|
||||||
dataSpec,
|
dataSpec,
|
||||||
|
mediaSegmentEncrypted,
|
||||||
|
initDataSource,
|
||||||
initDataSpec,
|
initDataSpec,
|
||||||
|
initSegmentEncrypted,
|
||||||
hlsUrl,
|
hlsUrl,
|
||||||
muxedCaptionFormats,
|
muxedCaptionFormats,
|
||||||
trackSelectionReason,
|
trackSelectionReason,
|
||||||
|
|
@ -139,16 +158,14 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
segmentEndTimeInPeriodUs,
|
segmentEndTimeInPeriodUs,
|
||||||
/* chunkMediaSequence= */ mediaPlaylist.mediaSequence + segmentIndexInPlaylist,
|
/* chunkMediaSequence= */ mediaPlaylist.mediaSequence + segmentIndexInPlaylist,
|
||||||
discontinuitySequenceNumber,
|
discontinuitySequenceNumber,
|
||||||
segment.hasGapTag,
|
mediaSegment.hasGapTag,
|
||||||
isMasterTimestampSource,
|
isMasterTimestampSource,
|
||||||
/* timestampAdjuster= */ timestampAdjusterProvider.getAdjuster(discontinuitySequenceNumber),
|
/* timestampAdjuster= */ timestampAdjusterProvider.getAdjuster(discontinuitySequenceNumber),
|
||||||
segment.drmInitData,
|
mediaSegment.drmInitData,
|
||||||
previousExtractor,
|
previousExtractor,
|
||||||
id3Decoder,
|
id3Decoder,
|
||||||
scratchId3Data,
|
scratchId3Data,
|
||||||
shouldSpliceIn,
|
shouldSpliceIn);
|
||||||
fullSegmentEncryptionKey,
|
|
||||||
encryptionIv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String PRIV_TIMESTAMP_FRAME_OWNER =
|
public static final String PRIV_TIMESTAMP_FRAME_OWNER =
|
||||||
|
|
@ -171,19 +188,20 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
*/
|
*/
|
||||||
public final HlsUrl hlsUrl;
|
public final HlsUrl hlsUrl;
|
||||||
|
|
||||||
private final DataSource initDataSource;
|
@Nullable private final DataSource initDataSource;
|
||||||
private final DataSpec initDataSpec;
|
@Nullable private final DataSpec initDataSpec;
|
||||||
private final boolean isEncrypted;
|
|
||||||
private final boolean isMasterTimestampSource;
|
private final boolean isMasterTimestampSource;
|
||||||
private final boolean hasGapTag;
|
private final boolean hasGapTag;
|
||||||
private final TimestampAdjuster timestampAdjuster;
|
private final TimestampAdjuster timestampAdjuster;
|
||||||
private final boolean shouldSpliceIn;
|
private final boolean shouldSpliceIn;
|
||||||
private final HlsExtractorFactory extractorFactory;
|
private final HlsExtractorFactory extractorFactory;
|
||||||
private final List<Format> muxedCaptionFormats;
|
@Nullable private final List<Format> muxedCaptionFormats;
|
||||||
private final DrmInitData drmInitData;
|
@Nullable private final DrmInitData drmInitData;
|
||||||
private final Extractor previousExtractor;
|
@Nullable private final Extractor previousExtractor;
|
||||||
private final Id3Decoder id3Decoder;
|
private final Id3Decoder id3Decoder;
|
||||||
private final ParsableByteArray scratchId3Data;
|
private final ParsableByteArray scratchId3Data;
|
||||||
|
private final boolean mediaSegmentEncrypted;
|
||||||
|
private final boolean initSegmentEncrypted;
|
||||||
|
|
||||||
private Extractor extractor;
|
private Extractor extractor;
|
||||||
private HlsSampleStreamWrapper output;
|
private HlsSampleStreamWrapper output;
|
||||||
|
|
@ -195,11 +213,14 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
private HlsMediaChunk(
|
private HlsMediaChunk(
|
||||||
HlsExtractorFactory extractorFactory,
|
HlsExtractorFactory extractorFactory,
|
||||||
DataSource dataSource,
|
DataSource mediaDataSource,
|
||||||
DataSpec dataSpec,
|
DataSpec dataSpec,
|
||||||
DataSpec initDataSpec,
|
boolean mediaSegmentEncrypted,
|
||||||
|
DataSource initDataSource,
|
||||||
|
@Nullable DataSpec initDataSpec,
|
||||||
|
boolean initSegmentEncrypted,
|
||||||
HlsUrl hlsUrl,
|
HlsUrl hlsUrl,
|
||||||
List<Format> muxedCaptionFormats,
|
@Nullable List<Format> muxedCaptionFormats,
|
||||||
int trackSelectionReason,
|
int trackSelectionReason,
|
||||||
Object trackSelectionData,
|
Object trackSelectionData,
|
||||||
long startTimeUs,
|
long startTimeUs,
|
||||||
|
|
@ -209,15 +230,13 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
boolean hasGapTag,
|
boolean hasGapTag,
|
||||||
boolean isMasterTimestampSource,
|
boolean isMasterTimestampSource,
|
||||||
TimestampAdjuster timestampAdjuster,
|
TimestampAdjuster timestampAdjuster,
|
||||||
DrmInitData drmInitData,
|
@Nullable DrmInitData drmInitData,
|
||||||
Extractor previousExtractor,
|
@Nullable Extractor previousExtractor,
|
||||||
Id3Decoder id3Decoder,
|
Id3Decoder id3Decoder,
|
||||||
ParsableByteArray scratchId3Data,
|
ParsableByteArray scratchId3Data,
|
||||||
boolean shouldSpliceIn,
|
boolean shouldSpliceIn) {
|
||||||
byte[] fullSegmentEncryptionKey,
|
|
||||||
byte[] encryptionIv) {
|
|
||||||
super(
|
super(
|
||||||
buildDataSource(dataSource, fullSegmentEncryptionKey, encryptionIv),
|
mediaDataSource,
|
||||||
dataSpec,
|
dataSpec,
|
||||||
hlsUrl.format,
|
hlsUrl.format,
|
||||||
trackSelectionReason,
|
trackSelectionReason,
|
||||||
|
|
@ -225,12 +244,14 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
startTimeUs,
|
startTimeUs,
|
||||||
endTimeUs,
|
endTimeUs,
|
||||||
chunkMediaSequence);
|
chunkMediaSequence);
|
||||||
|
this.mediaSegmentEncrypted = mediaSegmentEncrypted;
|
||||||
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
|
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
|
||||||
|
this.initDataSource = initDataSource;
|
||||||
this.initDataSpec = initDataSpec;
|
this.initDataSpec = initDataSpec;
|
||||||
|
this.initSegmentEncrypted = initSegmentEncrypted;
|
||||||
this.hlsUrl = hlsUrl;
|
this.hlsUrl = hlsUrl;
|
||||||
this.isMasterTimestampSource = isMasterTimestampSource;
|
this.isMasterTimestampSource = isMasterTimestampSource;
|
||||||
this.timestampAdjuster = timestampAdjuster;
|
this.timestampAdjuster = timestampAdjuster;
|
||||||
this.isEncrypted = fullSegmentEncryptionKey != null;
|
|
||||||
this.hasGapTag = hasGapTag;
|
this.hasGapTag = hasGapTag;
|
||||||
this.extractorFactory = extractorFactory;
|
this.extractorFactory = extractorFactory;
|
||||||
this.muxedCaptionFormats = muxedCaptionFormats;
|
this.muxedCaptionFormats = muxedCaptionFormats;
|
||||||
|
|
@ -239,7 +260,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
this.id3Decoder = id3Decoder;
|
this.id3Decoder = id3Decoder;
|
||||||
this.scratchId3Data = scratchId3Data;
|
this.scratchId3Data = scratchId3Data;
|
||||||
this.shouldSpliceIn = shouldSpliceIn;
|
this.shouldSpliceIn = shouldSpliceIn;
|
||||||
initDataSource = dataSource;
|
|
||||||
uid = uidSource.getAndIncrement();
|
uid = uidSource.getAndIncrement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,9 +303,20 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
// Note: The HLS spec forbids initialization segments for packed audio.
|
// Note: The HLS spec forbids initialization segments for packed audio.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DataSpec initSegmentDataSpec = initDataSpec.subrange(initSegmentBytesLoaded);
|
DataSpec initSegmentDataSpec;
|
||||||
|
boolean skipLoadedBytes;
|
||||||
|
if (initSegmentEncrypted) {
|
||||||
|
initSegmentDataSpec = initDataSpec;
|
||||||
|
skipLoadedBytes = initSegmentBytesLoaded != 0;
|
||||||
|
} else {
|
||||||
|
initSegmentDataSpec = initDataSpec.subrange(initSegmentBytesLoaded);
|
||||||
|
skipLoadedBytes = false;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
DefaultExtractorInput input = prepareExtraction(initDataSource, initSegmentDataSpec);
|
DefaultExtractorInput input = prepareExtraction(initDataSource, initSegmentDataSpec);
|
||||||
|
if (skipLoadedBytes) {
|
||||||
|
input.skipFully(initSegmentBytesLoaded);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
int result = Extractor.RESULT_CONTINUE;
|
int result = Extractor.RESULT_CONTINUE;
|
||||||
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
|
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
|
||||||
|
|
@ -307,7 +338,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
// remainder of the chunk directly.
|
// remainder of the chunk directly.
|
||||||
DataSpec loadDataSpec;
|
DataSpec loadDataSpec;
|
||||||
boolean skipLoadedBytes;
|
boolean skipLoadedBytes;
|
||||||
if (isEncrypted) {
|
if (mediaSegmentEncrypted) {
|
||||||
loadDataSpec = dataSpec;
|
loadDataSpec = dataSpec;
|
||||||
skipLoadedBytes = nextLoadPosition != 0;
|
skipLoadedBytes = nextLoadPosition != 0;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -433,7 +464,27 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
return C.TIME_UNSET;
|
return C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal factory methods.
|
// Internal methods.
|
||||||
|
|
||||||
|
private static byte[] getEncryptionIvArray(String ivString) {
|
||||||
|
String trimmedIv;
|
||||||
|
if (Util.toLowerInvariant(ivString).startsWith("0x")) {
|
||||||
|
trimmedIv = ivString.substring(2);
|
||||||
|
} else {
|
||||||
|
trimmedIv = ivString;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] ivData = new BigInteger(trimmedIv, /* radix= */ 16).toByteArray();
|
||||||
|
byte[] ivDataWithPadding = new byte[16];
|
||||||
|
int offset = ivData.length > 16 ? ivData.length - 16 : 0;
|
||||||
|
System.arraycopy(
|
||||||
|
ivData,
|
||||||
|
offset,
|
||||||
|
ivDataWithPadding,
|
||||||
|
ivDataWithPadding.length - ivData.length + offset,
|
||||||
|
ivData.length - offset);
|
||||||
|
return ivDataWithPadding;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the segment is fully encrypted, returns an {@link Aes128DataSource} that wraps the original
|
* If the segment is fully encrypted, returns an {@link Aes128DataSource} that wraps the original
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue