diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index c7eede8c7a..b422db2d5e 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -67,7 +67,9 @@ public class HlsChunkSource { private int variantIndex; private DataSource encryptedDataSource; - private String encryptionKeyUri; + private Uri encryptionKeyUri; + private String encryptedDataSourceIv; + private byte[] encryptedDataSourceSecretKey; /** * @param dataSource A {@link DataSource} suitable for loading the media data. @@ -179,16 +181,17 @@ public class HlsChunkSource { // Check if encryption is specified. if (HlsMediaPlaylist.ENCRYPTION_METHOD_AES_128.equals(segment.encryptionMethod)) { - if (!segment.encryptionKeyUri.equals(encryptionKeyUri)) { + Uri keyUri = Util.getMergedUri(mediaPlaylist.baseUri, segment.encryptionKeyUri); + if (!keyUri.equals(encryptionKeyUri)) { // Encryption is specified and the key has changed. - Uri keyUri = Util.getMergedUri(mediaPlaylist.baseUri, segment.encryptionKeyUri); HlsChunk toReturn = newEncryptionKeyChunk(keyUri, segment.encryptionIV); - encryptionKeyUri = segment.encryptionKeyUri; return toReturn; } + if (!Util.areEqual(segment.encryptionIV, encryptedDataSourceIv)) { + initEncryptedDataSource(keyUri, segment.encryptionIV, encryptedDataSourceSecretKey); + } } else { - encryptedDataSource = null; - encryptionKeyUri = null; + clearEncryptedDataSource(); } long startTimeUs; @@ -290,6 +293,33 @@ public class HlsChunkSource { return new EncryptionKeyChunk(upstreamDataSource, dataSpec, iv); } + /* package */ void initEncryptedDataSource(Uri keyUri, String iv, byte[] secretKey) { + String trimmedIv; + if (iv.toLowerCase(Locale.getDefault()).startsWith("0x")) { + trimmedIv = iv.substring(2); + } else { + trimmedIv = iv; + } + + byte[] ivData = new BigInteger(trimmedIv, 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); + + encryptedDataSource = new Aes128DataSource(secretKey, ivDataWithPadding, upstreamDataSource); + encryptionKeyUri = keyUri; + encryptedDataSourceIv = iv; + encryptedDataSourceSecretKey = secretKey; + } + + private void clearEncryptedDataSource() { + encryptionKeyUri = null; + encryptedDataSource = null; + encryptedDataSourceIv = null; + encryptedDataSourceSecretKey = null; + } + private static Variant[] filterVariants(HlsMasterPlaylist masterPlaylist, int[] variantIndices) { List masterVariants = masterPlaylist.variants; ArrayList enabledVariants = new ArrayList(); @@ -378,25 +408,14 @@ public class HlsChunkSource { public EncryptionKeyChunk(DataSource dataSource, DataSpec dataSpec, String iv) { super(dataSource, dataSpec, bitArray); - if (iv.toLowerCase(Locale.getDefault()).startsWith("0x")) { - this.iv = iv.substring(2); - } else { - this.iv = iv; - } + this.iv = iv; } @Override protected void consume(BitArray data) throws IOException { byte[] secretKey = new byte[data.bytesLeft()]; data.readBytes(secretKey, 0, secretKey.length); - - byte[] ivData = new BigInteger(iv, 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); - - encryptedDataSource = new Aes128DataSource(secretKey, ivDataWithPadding, upstreamDataSource); + initEncryptedDataSource(dataSpec.uri, iv, secretKey); } } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index 693af382f1..4982ca894c 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -256,7 +256,9 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { } else if (loadingFinished) { return TrackRenderer.END_OF_TRACK_US; } else { - return extractors.getLast().getLargestSampleTimestamp(); + long largestSampleTimestamp = extractors.getLast().getLargestSampleTimestamp(); + return largestSampleTimestamp == Long.MIN_VALUE ? downstreamPositionUs + : largestSampleTimestamp; } } @@ -349,8 +351,13 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { return; } - boolean bufferFull = !extractors.isEmpty() && (extractors.getLast().getLargestSampleTimestamp() - - downstreamPositionUs) >= BUFFER_DURATION_US; + boolean bufferFull = false; + if (!extractors.isEmpty()) { + long largestSampleTimestamp = extractors.getLast().getLargestSampleTimestamp(); + bufferFull = largestSampleTimestamp != Long.MIN_VALUE + && (largestSampleTimestamp - downstreamPositionUs) >= BUFFER_DURATION_US; + } + if (loader.isLoading() || bufferFull) { return; } diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/Aes128DataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/Aes128DataSource.java index 1e76d898b8..938dd70ef1 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/Aes128DataSource.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/Aes128DataSource.java @@ -42,7 +42,6 @@ public class Aes128DataSource implements DataSource { private final byte[] secretKey; private final byte[] iv; - private Cipher cipher; private CipherInputStream cipherInputStream; public Aes128DataSource(byte[] secretKey, byte[] iv, DataSource upstream) { @@ -53,6 +52,7 @@ public class Aes128DataSource implements DataSource { @Override public long open(DataSpec dataSpec) throws IOException { + Cipher cipher; try { cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); } catch (NoSuchAlgorithmException e) { @@ -80,6 +80,7 @@ public class Aes128DataSource implements DataSource { @Override public void close() throws IOException { + cipherInputStream = null; upstream.close(); }