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
This commit is contained in:
Oliver Woodman 2014-11-20 17:11:02 +00:00
parent 81e2c9f0d3
commit 03e859d774
3 changed files with 50 additions and 23 deletions

View File

@ -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<Variant> masterVariants = masterPlaylist.variants;
ArrayList<Variant> enabledVariants = new ArrayList<Variant>();
@ -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);
}
}

View File

@ -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;
}

View File

@ -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();
}