Commit 8982da4b by aquilescanta Committed by Andrew Lewis

Support encrypted initialization segment

Defined in RFC 8216 Section 4.3.2.5.

Issue:#5441
PiperOrigin-RevId: 234114119
parent d61171a1
...@@ -4,6 +4,8 @@ ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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,
trackSelection.getSelectionReason(), trackSelection.getSelectionData());
return; return;
} }
if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) { out.chunk = maybeCreateEncryptionChunkFor(segment, mediaPlaylist, selectedVariantIndex);
setEncryptionData(keyUri, segment.encryptionIV, encryptionKey); if (out.chunk != null) {
} return;
} else {
clearEncryptionData();
} }
out.chunk = out.chunk =
...@@ -338,8 +339,7 @@ import java.util.List; ...@@ -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; ...@@ -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; ...@@ -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;
encryptionIvString = iv;
encryptionIv = ivDataWithPadding;
} }
DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNSET, null, DataSpec.FLAG_ALLOW_GZIP);
private void clearEncryptionData() { return new EncryptionKeyChunk(
encryptionKeyUri = null; encryptionDataSource,
encryptionKey = null; dataSpec,
encryptionIvString = null; variants[selectedVariantIndex].format,
encryptionIv = null; trackSelection.getSelectionReason(),
trackSelection.getSelectionData(),
scratchSpace);
} }
// Private classes. // Private classes.
...@@ -575,19 +563,21 @@ import java.util.List; ...@@ -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; ...@@ -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 @@ ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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
......
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