Here you go. I don't know if it works, but it should:
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.BaseDataSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import static java.lang.Math.min;
public class EncryptedFileDataSource extends BaseDataSource {
public static class EncryptedFileDataSourceException extends IOException {
public EncryptedFileDataSourceException(Throwable cause) {
super(cause);
}
public EncryptedFileDataSourceException(String message, IOException cause) {
super(message, cause);
}
}
/**
* {@link DataSource.Factory} for {@link EncryptedFileDataSource} instances.
*/
public static final class Factory implements DataSource.Factory {
@Nullable
private TransferListener listener;
private byte[] key;
private String transformation;
public Factory() {
this(null, null);
}
public Factory(byte[] key) {
this(key, null);
}
public Factory(byte[] key, String transformation) {
setKey(key);
setTransformation(transformation);
}
public void setKey(byte[] key) {
if (key == null) {
return;
}
this.key = key;
}
public void setTransformation(String transformation) {
if (transformation == null) {
transformation = "AES/ECB/PKCS5Padding";
}
this.transformation = transformation;
}
/**
* Sets a {@link TransferListener} for {@link EncryptedFileDataSource} instances created by this factory.
*
* @param listener The {@link TransferListener}.
* @return This factory.
*/
public Factory setListener(@Nullable TransferListener listener) {
this.listener = listener;
return this;
}
@Override
public EncryptedFileDataSource createDataSource() {
EncryptedFileDataSource dataSource = new EncryptedFileDataSource(key, transformation);
if (listener != null) {
dataSource.addTransferListener(listener);
}
return dataSource;
}
}
@Nullable
private RandomAccessFile file;
@Nullable
private Uri uri;
private long bytesRemaining;
private boolean opened;
private byte[] key;
private String transformation;
private Cipher cipher;
private byte[] tmpBuffer;
private EncryptedFileDataSource(byte[] key, String transformation) {
super(false);
setKey(key);
setTransformation(transformation);
}
public void setKey(byte[] key) {
this.key = key;
}
public void setTransformation(String transformation) {
this.transformation = transformation;
}
@Override
public long open(@NonNull DataSpec dataSpec) throws EncryptedFileDataSourceException {
try {
byte[] key = this.key;
String transformation = this.transformation;
Uri uri = dataSpec.uri;
this.uri = uri;
transferInitializing(dataSpec);
if (key != null && transformation != null) {
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
cipher = Cipher.getInstance(transformation);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
}
this.file = openLocalFile(uri);
file.seek(dataSpec.position);
bytesRemaining = dataSpec.length == C.LENGTH_UNSET ? file.length() - dataSpec.position
: dataSpec.length;
if (bytesRemaining < 0) {
throw new EOFException();
}
} catch (IOException
| NoSuchPaddingException
| NoSuchAlgorithmException
| InvalidKeyException e) {
throw new EncryptedFileDataSourceException(e);
}
opened = true;
transferStarted(dataSpec);
return bytesRemaining;
}
@Override
public int read(@NonNull byte[] buffer, int offset, int readLength) throws EncryptedFileDataSourceException {
if (readLength == 0) {
return 0;
} else if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT;
} else {
int bytesRead, bytesStored;
try {
int length = (int) min(bytesRemaining, readLength);
Cipher cipher = this.cipher;
if (cipher != null) {
tmpBuffer = resize(tmpBuffer, length);
bytesRead = castNonNull(file).read(tmpBuffer, 0, length);
bytesStored = cipher.doFinal(tmpBuffer, 0, length, buffer, offset);
} else {
bytesStored = bytesRead = castNonNull(file).read(buffer, offset, length);
}
} catch (IOException
| BadPaddingException
| IllegalBlockSizeException
| ShortBufferException e) {
throw new EncryptedFileDataSourceException(e);
}
if (bytesRead > 0) {
bytesRemaining -= bytesRead;
bytesTransferred(bytesRead);
}
return bytesStored;
}
}
@Nullable
@Override
public Uri getUri() {
return uri;
}
@Override
public void close() throws EncryptedFileDataSourceException {
uri = null;
try {
if (file != null) {
file.close();
}
} catch (IOException e) {
throw new EncryptedFileDataSourceException(e);
} finally {
file = null;
key = null;
transformation = null;
cipher = null;
if (opened) {
opened = false;
transferEnded();
}
byte[] tmpBuffer = this.tmpBuffer;
this.tmpBuffer = null;
if (tmpBuffer != null) {
for (int i = tmpBuffer.length - 1; i >= 0; i -= 2) {
tmpBuffer[i] = (byte) 0;
}
}
}
}
static byte[] resize(byte[] b, int newLen) {
if (newLen < 0) return b;
if (b == null || b.length < newLen) {
return new byte[newLen];
} else return b;
}
private static RandomAccessFile openLocalFile(Uri uri) throws FileDataSource.FileDataSourceException {
try {
return new RandomAccessFile(Assertions.checkNotNull(uri.getPath()), "r");
} catch (FileNotFoundException e) {
if (!TextUtils.isEmpty(uri.getQuery()) || !TextUtils.isEmpty(uri.getFragment())) {
throw new FileDataSource.FileDataSourceException(
String.format(
"uri has query and/or fragment, which are not supported. Did you call Uri.parse()"
+ " on a string containing '?' or '#'? Use Uri.fromFile(new File(path)) to"
+ " avoid this. path=%s,query=%s,fragment=%s",
uri.getPath(), uri.getQuery(), uri.getFragment()),
e);
}
throw new FileDataSource.FileDataSourceException(e);
}
}
}
Use it like this:
public void playVideo(Uri uri, byte[] key, @Nullable String transformation) {
playVideo(MediaItem.fromUri(uri), key, transformation);
}
public void playVideo(MediaItem media, byte[] key, @Nullable String transformation) {
ExoPlayer player = new SimpleExoPlayer.Builder(this).build();
playerView.setPlayer(player);
try {
DataSource.Factory dataSourceFactory = new EncryptedFileDataSource.Factory(key, transformation);
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
ProgressiveMediaSource.Factory progressiveFactory = new ProgressiveMediaSource.Factory(
dataSourceFactory,
extractorsFactory
);
ProgressiveMediaSource mediaSource = progressiveFactory.createMediaSource(media);
player.setMediaSource(mediaSource);
//player.setMediaSource(mediaSource, startPositionMs: long);
//player.setMediaSource(mediaSource, resetPosition: boolean);
player.setPlayWhenReady(true);
} catch (Exception e) {
e.printStackTrace();
}
}
I used the following sources to make this: