Commit 03e859d7 by Oliver Woodman

Fix handling of encrypted media if IV changes.

1. Correctly replace the AES data source if IV changes.
2. Check the largest timestamp for being equal to MIN_VALUE, and
   handle this case properly.
3. Clean up AES data source a little.

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