problem of ExoPlayer with more than locally 2 GB encrypted files
See original GitHub issueHi, At first my deepest thank goes to your team. There is a problem while using ExoPlayer for playing encrypted files. we can play less than 2GB encrypted files but there is a problem for more than 2GB encrypted files and we couldn’t play these files. Is there any solution to solve this problem?
note:
- we using custom data source! code is at the bottom.
- we encrypt files ate users’ devices (locally)
- we do not change format file in an encryption process
- we secretKey and IvParameterSpec size is 16 byte
- we have no change in size of files before or after of encryption (for example 2.54GB file not change size in before or after of encryption)
error :
Source error.
com.google.android.exoplayer2.source.UnrecognizedInputFormatException: None of the available extractors (MatroskaExtractor, FragmentedMp4Extractor, Mp4Extractor, Mp3Extractor, AdtsExtractor, Ac3Extractor, TsExtractor, FlvExtractor, OggExtractor, PsExtractor, WavExtractor) could read the stream.
encrypt info :
public static final String AES_ALGORITHM = "AES";
public static final String AES_TRANSFORMATION = "AES/CTR/NoPadding";
encrypt code :
SecretKeySpec secretKey = new SecretKeySpec(keys, AES_ALGORITHM);
Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
cipher.init(cipherMode, secretKey, iv);
FileInputStream inputStream = new FileInputStream(inputFile);
byte[] inputBytes = new byte[1024*1024];
System.out.println("Start");
int bytesRead;
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream,
cipher);
while ((bytesRead = inputStream.read(inputBytes, 0, bufferSize)) != -1) {
cipherOutputStream.write(inputBytes , 0 , bytesRead);
}
inputStream.close();
cipherOutputStream.close();
if (inputFile.exists()) {
inputFile.delete();
}
System.out.println("END");
player :
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
dataSourceFactory = new EncryptedFileDataSourceFactory(cipher, key,
iv, bandwidthMeter);
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
videoSource = new ProgressiveMediaSource.Factory(dataSourceFactory ,
extractorsFactory).createMediaSource(uri);
EncryptedFileDataSourceFactory :
public class EncryptedFileDataSourceFactory implements DataSource.Factory {
private Cipher mCipher;
private SecretKeySpec mSecretKeySpec;
private IvParameterSpec mIvParameterSpec;
private TransferListener mTransferListener;
public EncryptedFileDataSourceFactory(Cipher cipher, SecretKeySpec secretKeySpec, IvParameterSpec ivParameterSpec , TransferListener listener) {
this.mCipher = cipher;
this.mSecretKeySpec = secretKeySpec;
this.mIvParameterSpec = ivParameterSpec;
this.mTransferListener = listener;
}
@Override
public EncryptedFileDataSource createDataSource() {
return new EncryptedFileDataSource(mCipher , mSecretKeySpec , mIvParameterSpec , mTransferListener);
}
}
EncryptedFileDataSource :
public final class EncryptedFileDataSource implements DataSource {
private final TransferListener mTransferListener;
private StreamingCipherInputStream mInputStream;
private Uri mUri;
private long mBytesRemaining;
private boolean mOpened;
private Cipher mCipher;
private SecretKeySpec mSecretKeySpec;
private IvParameterSpec mIvParameterSpec;
DataSpec dataSpec;
public EncryptedFileDataSource(Cipher cipher, SecretKeySpec secretKeySpec, IvParameterSpec ivParameterSpec, TransferListener listener) {
mCipher = cipher;
mSecretKeySpec = secretKeySpec;
mIvParameterSpec = ivParameterSpec;
mTransferListener = listener;
}
@Override
public void addTransferListener(TransferListener transferListener) {
}
@Override
public long open(DataSpec dataSpec) throws EncryptedFileDataSourceException {
// if we're open, we shouldn't need to open again, fast-fail
if (mOpened) {
return mBytesRemaining;
}
// #getUri is part of the contract...
this.dataSpec = dataSpec;
mUri = dataSpec.uri;
// put all our throwable work in a single block, wrap the error in a custom Exception
try {
setupInputStream();
skipToPosition(dataSpec);
computeBytesRemaining(dataSpec);
} catch (IOException e) {
throw new EncryptedFileDataSourceException(e);
}
// if we made it this far, we're open
mOpened = true;
// notify
if (mTransferListener != null) {
mTransferListener.onTransferStart(this, dataSpec, false);
}
// report
return mBytesRemaining;
}
private void setupInputStream() throws FileNotFoundException {
File encryptedFile = new File(mUri.getPath());
FileInputStream fileInputStream = new FileInputStream(encryptedFile);
mInputStream = new StreamingCipherInputStream(fileInputStream, mCipher, mSecretKeySpec, mIvParameterSpec);
}
private void skipToPosition(DataSpec dataSpec) throws IOException {
mInputStream.forceSkip(dataSpec.position);
}
private void computeBytesRemaining(DataSpec dataSpec) throws IOException {
if (dataSpec.length != C.LENGTH_UNSET) {
mBytesRemaining = dataSpec.length;
} else {
mBytesRemaining = mInputStream.available();
if (mBytesRemaining == Integer.MAX_VALUE) {
mBytesRemaining = C.LENGTH_UNSET;
}
}
}
@Override
public int read(byte[] buffer, int offset, int readLength) throws EncryptedFileDataSourceException {
// fast-fail if there's 0 quantity requested or we think we've already processed everything
if (readLength == 0) {
return 0;
} else if (mBytesRemaining == 0) {
return C.RESULT_END_OF_INPUT;
}
// constrain the read length and try to read from the cipher input stream
int bytesToRead = getBytesToRead(readLength);
int bytesRead;
try {
bytesRead = mInputStream.read(buffer, offset, bytesToRead);
} catch (IOException e) {
throw new EncryptedFileDataSourceException(e);
}
// if we get a -1 that means we failed to read - we're either going to EOF error or broadcast EOF
if (bytesRead == -1) {
if (mBytesRemaining != C.LENGTH_UNSET) {
throw new EncryptedFileDataSourceException(new EOFException());
}
return C.RESULT_END_OF_INPUT;
}
// we can't decrement bytes remaining if it's just a flag representation (as opposed to a mutable numeric quantity)
if (mBytesRemaining != C.LENGTH_UNSET) {
mBytesRemaining -= bytesRead;
}
// notify
if (mTransferListener != null) {
mTransferListener.onBytesTransferred(this, dataSpec, false, bytesRead);
}
// report
return bytesRead;
}
private int getBytesToRead(int bytesToRead) {
if (mBytesRemaining == C.LENGTH_UNSET) {
return bytesToRead;
}
return (int) Math.min(mBytesRemaining, bytesToRead);
}
@Override
public Uri getUri() {
return mUri;
}
@Override
public void close() throws EncryptedFileDataSourceException {
mUri = null;
try {
if (mInputStream != null) {
mInputStream.close();
}
} catch (IOException e) {
throw new EncryptedFileDataSourceException(e);
} finally {
mInputStream = null;
if (mOpened) {
mOpened = false;
if (mTransferListener != null) {
mTransferListener.onTransferEnd(this, dataSpec, false);
}
}
}
}
}
StreamingCipherInputStream :
class StreamingCipherInputStream extends CipherInputStream {
private static final int AES_BLOCK_SIZE = 16;
private InputStream mUpstream;
private Cipher mCipher;
private SecretKeySpec mSecretKeySpec;
private IvParameterSpec mIvParameterSpec;
public StreamingCipherInputStream(InputStream inputStream, Cipher cipher, SecretKeySpec secretKeySpec, IvParameterSpec ivParameterSpec) {
super(inputStream, cipher);
mUpstream = inputStream;
mCipher = cipher;
mSecretKeySpec = secretKeySpec;
mIvParameterSpec = ivParameterSpec;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return super.read(b, off, len);
}
public long forceSkip(long bytesToSkip) throws IOException {
long skipped = mUpstream.skip(bytesToSkip);
try {
int skip = (int) (bytesToSkip % AES_BLOCK_SIZE);
long blockOffset = bytesToSkip - skip;
long numberOfBlocks = blockOffset / AES_BLOCK_SIZE;
// from here to the next inline comment, i don't understand
BigInteger ivForOffsetAsBigInteger = new BigInteger(1, mIvParameterSpec.getIV()).add(BigInteger.valueOf(numberOfBlocks));
byte[] ivForOffsetByteArray = ivForOffsetAsBigInteger.toByteArray();
IvParameterSpec computedIvParameterSpecForOffset;
if (ivForOffsetByteArray.length < AES_BLOCK_SIZE) {
byte[] resizedIvForOffsetByteArray = new byte[AES_BLOCK_SIZE];
System.arraycopy(ivForOffsetByteArray, 0, resizedIvForOffsetByteArray, AES_BLOCK_SIZE - ivForOffsetByteArray.length, ivForOffsetByteArray.length);
computedIvParameterSpecForOffset = new IvParameterSpec(resizedIvForOffsetByteArray);
} else {
computedIvParameterSpecForOffset = new IvParameterSpec(ivForOffsetByteArray, ivForOffsetByteArray.length - AES_BLOCK_SIZE, AES_BLOCK_SIZE);
}
mCipher.init(Cipher.ENCRYPT_MODE, mSecretKeySpec, computedIvParameterSpecForOffset);
byte[] skipBuffer = new byte[skip];
// i get that we need to update, but i don't get how we're able to take the shortcut from here to the previous comment
mCipher.update(skipBuffer, 0, skip, skipBuffer);
Arrays.fill(skipBuffer, (byte) 0);
} catch (Exception e) {
return 0;
}
return skipped;
}
// We need to return the available bytes from the upstream.
// In this implementation we're front loading it, but it's possible the value might change during the lifetime
// of this instance, and reference to the stream should be retained and queried for available bytes instead
@Override
public int available() throws IOException {
return mUpstream.available();
}
}
Issue Analytics
- State:
- Created 4 years ago
- Comments:7 (4 by maintainers)
Top Results From Across the Web
Having trouble playing encrypted video files in ExoPlayer ...
I am attempting to playback video files, via ExoPlayer, that I have encrypted within my app, but anytime I try to playback a...
Read more >RELEASENOTES.md - google/ExoPlayer - Sourcegraph
MP3: Fix issue parsing the XING headers belonging to files larger than 2GB. ([#7337](https://github.com/google/ExoPlayer/issues/7337)).
Read more >RELEASENOTES.md · master · Lahlouh, Ishak / RFC_Player
MP3: Fix issue parsing the XING headers belonging to files larger than 2GB (#7337). MPEG-TS: Fix issue where SEI NAL units were incorrectly ......
Read more >App resources overview - Android Developers
Table 1. Resource directories supported inside project res/ directory. Directory, Resource Type. animator/, XML files that ...
Read more >Amazon Kinesis Video Streams FAQs
The video track of each fragment must contain codec private data in the Advanced Video Coding (AVC) for H.264 format and HEVC for...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Glad to hear you solved the issue!
@ojw28 Hi, At first, I appreciate your advice and it was perfectly useful for me to solve the problem. It’s worth mentioning that maximum value of files with more than 2GB was more than maximum value of integer. so it was the reason of the problem in our component.