mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
81e2c9f0d3
commit
03e859d774
@ -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 {
|
|||||||
|
|
||||||
// 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)) {
|
Uri keyUri = Util.getMergedUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
|
||||||
|
if (!keyUri.equals(encryptionKeyUri)) {
|
||||||
// Encryption is specified and the key has changed.
|
// Encryption is specified and the key has changed.
|
||||||
Uri keyUri = Util.getMergedUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
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;
|
||||||
this.iv = iv.substring(2);
|
|
||||||
} else {
|
|
||||||
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 {
|
|||||||
} 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
@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 {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
|
cipherInputStream = null;
|
||||||
upstream.close();
|
upstream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user