Open ladeng opened 3 weeks ago
Add the SDK version: implementation 'androidx.media3:media3-exoplayer:1.4.0' implementation 'androidx.media3:media3-exoplayer-dash:1.4.0' implementation 'androidx.media3:media3-ui:1.4.0'
Since you came here from https://github.com/google/ExoPlayer/issues/11229, i just want to check: are you using a custom DataSource
implementation similar to that issue? If so, my suggestion is the same as that issue: ensure your DataSource
is passing all of the DataSourceContractTest
tests.
If not, please can you describe in more detail how you are configuring the decryption within your player?
The test has passed using DataSourceContractTest, please see the screenshot:
Below is all my code: EncryptedDataSourceTest.java: `@RunWith(AndroidJUnit4.class) public class EncryptedDataSourceTest extends DataSourceContractTest {
private static final String TEST_FILE_PATH = "/storage/emulated/0/a/v/abc.mp4";
@Override
protected DataSource createDataSource() throws Exception {
Cipher cipher = AesPassUtils.getCipher(Cipher.DECRYPT_MODE,AesPassUtils.AES_KEY,AesPassUtils.AES_IV);
return new AESDecryptionDataSource(new AESDecryptionDataSourceFactory(cipher).createDataSource(),cipher);
}
@Override
protected ImmutableList<TestResource> getTestResources() throws Exception {
return null;
}
@Override
protected Uri getNotFoundUri() {
return null;
}
@Test
public void testEncryptedFileCanBeRead() throws Exception {
DataSource dataSource = createDataSource();
dataSource.open(new DataSpec(Uri.parse(TEST_FILE_PATH)));
byte[] buffer = new byte[1024];
int bytesRead = dataSource.read(buffer, 0, buffer.length);
assertTrue("The encrypted file should be readable after decryption", bytesRead > 0);
dataSource.close();
}
@Test
public void testPlayDecryptedVideo() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
ExoPlayer[] player = new ExoPlayer[1];
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
player[0] = new ExoPlayer.Builder(context).build();
});
DataSource.Factory dataSourceFactory = new DataSource.Factory() {
@Override
public DataSource createDataSource() {
try {
return EncryptedDataSourceTest.this.createDataSource();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
};
MediaSource mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(Uri.parse(TEST_FILE_PATH)));
// main thread
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
player[0].setMediaSource(mediaSource);
player[0].prepare();
player[0].play();
});
Thread.sleep(5000);
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
System.out.println(String.format("ExoPlayer status:%d",player[0].getPlaybackState()));
player[0].release();
});
}
}`
AESDecryptionDataSource.java: `public class AESDecryptionDataSource implements DataSource {
private final DataSource upstream;
private final Cipher cipher;
private InputStream inputStream;
private long bytesRemaining;
public AESDecryptionDataSource(DataSource upstream, Cipher cipher) {
this.upstream = upstream;
this.cipher = cipher;
}
@Override
public void addTransferListener(TransferListener transferListener) {
upstream.addTransferListener(transferListener);
}
@Override
public long open(DataSpec dataSpec) throws IOException {
long dataSpecLength = upstream.open(dataSpec);
bytesRemaining = dataSpecLength == C.LENGTH_UNSET ? C.LENGTH_UNSET : dataSpecLength;
inputStream = new CipherInputStream(new DataSourceInputStream(upstream, dataSpec), cipher);
return dataSpecLength;
}
@Override
public int read(byte[] buffer, int offset, int readLength) throws IOException {
if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT;
}
int bytesRead = inputStream.read(buffer, offset, readLength);
if (bytesRead == -1) {
return C.RESULT_END_OF_INPUT;
}
if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead;
}
return bytesRead;
}
@Override
public Uri getUri() {
return upstream.getUri();
}
@Override
public Map<String, List<String>> getResponseHeaders() {
return DataSource.super.getResponseHeaders();
}
@Override
public void close() throws IOException {
try {
if (inputStream != null) {
inputStream.close();
}
} finally {
upstream.close();
}
}
private static class DataSourceInputStream extends InputStream {
private final DataSource dataSource;
private final DataSpec dataSpec;
private boolean opened = false;
DataSourceInputStream(DataSource dataSource, DataSpec dataSpec) {
this.dataSource = dataSource;
this.dataSpec = dataSpec;
}
@Override
public int read() throws IOException {
byte[] singleByte = new byte[1];
int result = read(singleByte);
return result == -1 ? -1 : (singleByte[0] & 0xFF);
}
@Override
public int read(byte[] buffer, int offset, int readLength) throws IOException {
if (!opened) {
dataSource.open(dataSpec);
opened = true;
}
return dataSource.read(buffer, offset, readLength);
}
@Override
public void close() throws IOException {
dataSource.close();
}
}
}`
AESDecryptionDataSourceFactory.java: `public class AESDecryptionDataSourceFactory implements DataSource.Factory { private final DataSource.Factory fileDataSourceFactory; private final Cipher cipher;
public AESDecryptionDataSourceFactory(Cipher cipher) { this.cipher = cipher; this.fileDataSourceFactory = new FileDataSource.Factory(); }
@Override public DataSource createDataSource() { return new AESDecryptionDataSource(fileDataSourceFactory.createDataSource(), cipher); } }`
hopefully this information can help resolve the issue
Something isn't right in your screenshot. I would expect to see all the tests defined in DataSourceContractTest
being run, like when I run FileDataSourceContractTest
:
In your screenshot it seems that none of these inherited tests are being run.
I would also expect to most of these tests failing with a NullPointerException
because you can't return null
from getTestResources()
or getNotFoundUri()
.
I would take a look at FileDataSourceContractTest
and work on fixing your test set-up so that the contract tests are running.
You may also want to take a look at https://github.com/androidx/media/issues/856.
From your original comment you mention:
I use encrypt with “AES/CBC/PKCS5Padding”
Since you are using CBC then you likely need similar code to that in https://github.com/androidx/media/issues/856#issuecomment-2308585356 which adjusts the read position of the upstream encrypted datasource to ensure it always starts from the beginning of a block. Otherwise you will often start decrypting from the middle of a block, which I think will produce garbage decrypted data, and likely explains the exception you're seeing when seeking (when we open the data source at a byte offset based on the seek position).
@ladeng Reading can't be that hard, right? I quote myself from https://github.com/androidx/media/issues/856
If you want the tests to work then you need to skip that part of the diff. The compile will fail for various reasons which you will all have to fix appropriately.
+1, same error here
I use encrypt with “AES/CBC/PKCS5Padding”
I am playing a local encrypted video. after setting the video resource, sliding the progress bar to set the playback progress(player.seekTo) will cause an exception: ExoPlayerImplInternal: Playback error androidx.media3.exoplayer.ExoPlaybackException: Source error at androidx.media3.exoplayer.ExoPlayerImplInternal.handleIoException(ExoPlayerImplInternal.java:736) at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:706) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:219) at android.os.HandlerThread.run(HandlerThread.java:67) Caused by: androidx.media3.common.ParserException: Invalid NAL length{contentIsMalformed=true, dataType=1} at androidx.media3.extractor.mp4.Mp4Extractor.readSample(Mp4Extractor.java:725) at androidx.media3.extractor.mp4.Mp4Extractor.read(Mp4Extractor.java:332) at androidx.media3.exoplayer.source.BundledExtractorsAdapter.read(BundledExtractorsAdapter.java:147) at androidx.media3.exoplayer.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1082) at androidx.media3.exoplayer.upstream.Loader$LoadTask.run(Loader.java:421) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:929)
when the encrypted video is played to the last frame, the output error: E/ExoPlayerImplInternal: Playback error androidx.media3.exoplayer.ExoPlaybackException: Source error at androidx.media3.exoplayer.ExoPlayerImplInternal.handleIoException(ExoPlayerImplInternal.java:736) at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:712) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:219) at android.os.HandlerThread.run(HandlerThread.java:67) Caused by: java.io.EOFException at androidx.media3.exoplayer.source.SampleDataQueue.sampleData(SampleDataQueue.java:186) at androidx.media3.exoplayer.source.SampleQueue.sampleData(SampleQueue.java:602) at androidx.media3.extractor.TrackOutput.sampleData(TrackOutput.java:161) at androidx.media3.extractor.mp4.Mp4Extractor.readSample(Mp4Extractor.java:755) at androidx.media3.extractor.mp4.Mp4Extractor.read(Mp4Extractor.java:332) at androidx.media3.exoplayer.source.BundledExtractorsAdapter.read(BundledExtractorsAdapter.java:147) at androidx.media3.exoplayer.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1082) at androidx.media3.exoplayer.upstream.Loader$LoadTask.run(Loader.java:421) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:929)
but after the video is decrypted and output as an mp4 file, it is normal to play it again, so my encrypted video file seems to be fine? after several days of trying, I have not found a solution yet. please help me!