Skip to content

Instantly share code, notes, and snippets.

@rameshvoltella
Forked from Parsoa/AESDataSource.java
Created December 10, 2019 06:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rameshvoltella/2966a132916311153b9a5ddd4c799885 to your computer and use it in GitHub Desktop.
Save rameshvoltella/2966a132916311153b9a5ddd4c799885 to your computer and use it in GitHub Desktop.
ExoPlayer playing AES/GCM/NoPadding encrypted video
package com.sharifin.lang.storage;
import android.net.Uri ;
import com.google.android.exoplayer2.C ;
import com.google.android.exoplayer2.upstream.DataSource ;
import com.google.android.exoplayer2.upstream.DataSpec ;
import com.google.android.exoplayer2.upstream.TransferListener ;
import java.io.EOFException ;
import java.io.File ;
import java.io.FileInputStream ;
import java.io.FileNotFoundException ;
import java.io.IOException ;
import java.io.InputStream ;
import android.util.Log;
import javax.crypto.Cipher ;
import javax.crypto.CipherInputStream ;
/**
* Created by Parsoa on 5/2/17.
*/
public class AESDataSource implements DataSource {
private static final String DEBUG_TAG = "AESDataSource" ;
private StreamingCipherInputStream cipherInputStream ;
private Uri uri ;
private long bytesRemaining ;
private boolean opened ;
public Cipher cipher ;
public AESDataSource(Cipher cipher) {
this.cipher = cipher ;
}
@Override
public long open(DataSpec dataSpec) throws IOException {
// if we're open, we shouldn't need to open again, fast-fail
if (opened) {
return bytesRemaining ;
}
Log.e(DEBUG_TAG, "open") ;
uri = dataSpec.uri;
try {
setupInputStream() ;
Log.e(DEBUG_TAG, "Dataspec: " + dataSpec.position + " , " + dataSpec.length + " , " + cipherInputStream.available()) ;
cipherInputStream.forceSkip(dataSpec.position) ;
computeBytesRemaining(dataSpec) ;
} catch (IOException e) {
throw new EncryptedFileDataSourceException(e) ;
}
opened = true ;
return bytesRemaining ;
}
private void setupInputStream() throws FileNotFoundException {
File encryptedFile = new File(uri.getPath());
FileInputStream fileInputStream = new FileInputStream(encryptedFile);
cipherInputStream = new StreamingCipherInputStream(fileInputStream, cipher);
}
private void computeBytesRemaining(DataSpec dataSpec) throws IOException {
if (dataSpec.length != C.LENGTH_UNSET) {
bytesRemaining = dataSpec.length;
} else {
bytesRemaining = cipherInputStream.available();
if (bytesRemaining == Integer.MAX_VALUE) {
bytesRemaining = 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
Log.e(DEBUG_TAG, "read") ;
if (readLength == 0) {
return 0 ;
} else if (bytesRemaining == 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 = cipherInputStream.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 (bytesRemaining != 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 (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead ;
}
return bytesRead ;
}
private int getBytesToRead(int bytesToRead) {
if (bytesRemaining == C.LENGTH_UNSET) {
return bytesToRead ;
}
return (int) Math.min(bytesRemaining, bytesToRead) ;
}
@Override
public void close() throws EncryptedFileDataSourceException {
uri = null;
try {
if (cipherInputStream != null) {
cipherInputStream.close();
}
} catch (IOException e) {
throw new EncryptedFileDataSourceException(e);
} finally {
cipherInputStream = null;
if (opened) {
opened = false;
}
}
}
@Override
public Uri getUri() {
return uri ;
}
// ================================================================================================================ \\
// ================================================================================================================ \\
// ================================================================================================================ \\
public static class StreamingCipherInputStream extends CipherInputStream {
private int bytesAvailable ;
public StreamingCipherInputStream(InputStream is, Cipher c) {
super(is, c);
try {
bytesAvailable = is.available();
} catch (IOException e) {
// let it be 0
}
}
// if the CipherInputStream has returns 0 from #skip, #read out enough bytes to get where we need to be
public long forceSkip(long bytesToSkip) throws IOException {
Log.e(DEBUG_TAG, "skipping " + bytesToSkip) ;
long processedBytes = 0;
while (processedBytes < bytesToSkip) {
Log.e(DEBUG_TAG, "processed " + processedBytes + ", skipping " + (8) + " bytes") ;
long bytesSkipped = skip(8) ;
if (bytesSkipped == 0) {
Log.e(DEBUG_TAG, "Couldn't skip, reading") ;
if (read() == -1) {
throw new EOFException() ;
}
bytesSkipped = 1 ;
}
processedBytes += bytesSkipped ;
}
Log.e(DEBUG_TAG, "done skipping " + bytesToSkip) ;
return processedBytes ;
}
@Override
public int available() throws IOException {
return bytesAvailable;
}
}
// ================================================================================================================ \\
// ================================================================================================================ \\
// ================================================================================================================ \\
public static final class EncryptedFileDataSourceException extends IOException {
public EncryptedFileDataSourceException(IOException cause) {
super(cause);
}
}
}
private void initializePlayer() {
if (!shouldInitialize || videoUrl == null || videoUrl.isEmpty()) {
// fail-fast
return ;
}
if (player == null) {
Log.e(DEBUG_TAG, "initializePlayer") ;
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveVideoTrackSelection.Factory(BANDWIDTH_METER);
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
player = ExoPlayerFactory.newSimpleInstance(mContext, trackSelector, new DefaultLoadControl());
player.addListener(this);
eventLogger = new EventLogger(trackSelector);
player.addListener(eventLogger);
player.setAudioDebugListener(eventLogger);
player.setVideoDebugListener(eventLogger);
player.setMetadataOutput(eventLogger);
simpleExoPlayerView.setPlayer(player);
player.setPlayWhenReady(true);
myPlayBackControlView.setPlayer(player);
simpleExoPlayerView.setControllerVisibilityListener(new PlaybackControlView.VisibilityListener() {
@Override
public void onVisibilityChange(int visibility) {
if (player == null)
return;
if (player.getPlayWhenReady()) {
btnPause.setVisibility(visibility) ;
}
}
});
playerInitialized = true ;
Log.e(DEBUG_TAG, "playerInitialized!") ;
}
initializeMediaSource() ;
}
private void initializeMediaSource() {
mediaSource = buildLocalMediaSource(Uri.parse(videoUrl)) ;
continuePlaying() ;
}
private void continuePlaying() {
final boolean haveResumePosition = resumeWindow != C.INDEX_UNSET ;
if (haveResumePosition) {
player.seekTo(resumeWindow, resumePosition) ;
}
player.prepare(mediaSource, !haveResumePosition, false) ;
updateButtonVisibilities() ;
}
private MediaSource buildLocalDecryptedMediaSource() {
try {
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), getIV()) ;
DataSpec dataSpec = new DataSpec(Uri.parse(file.getCanonicalPath())) ;
final FileDataSource fileDataSource = new FileDataSource();
try {
fileDataSource.open(dataSpec);
} catch (FileDataSource.FileDataSourceException e) {
e.printStackTrace();
}
DataSource.Factory factory = new DataSource.Factory() {
@Override
public DataSource createDataSource() {
return fileDataSource;
}
};
return new ExtractorMediaSource(fileDataSource.getUri(), factory, new DefaultExtractorsFactory(), null, null);
} catch (IOException e) {
e.printStackTrace() ;
}
Log.e(DEBUG_TAG, "Decryption failed") ;
return null ;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment