androidx / media

Jetpack Media3 support libraries for media use cases, including ExoPlayer, an extensible media player for Android
https://developer.android.com/media/media3
Apache License 2.0
1.74k stars 416 forks source link

androidx.media3.datasource.FileDataSource$FileDataSourceException While Playing Downloaded Offline Video from File #1801

Open ajayhack opened 1 month ago

ajayhack commented 1 month ago

Version

Media3 1.3.1

More version details

Media3 1.3.1

Devices that reproduce the issue

Device:- Samasung Galaxy F62 Android OS 13.

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Yes

Reproduction steps

Download The Stream shared on This email:- android-media-github@google.com and Save it in In-App Data Scope File Storage Folder , Then retrieve the downlaoded file URI path and play in Media3 and then we are facing the error.

The Downloaded file uri I am passing in Media 3 MediaItem.Builder :- "file:///data/user/0/core2.maz.com.core1/files/.Videos/293711.mp4" through code: builder = offlineFile.getMediaItem().buildUpon(); Code used to generate "offlineFile" in case of "AppConstants.isTvodApp()":

private MediaSource getOfflineFile(Context context, String simulatedUrl, Menu menu) {
        // Default parameters, except allowCrossProtocolRedirects is true
        DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory()
                .setUserAgent(Util.getUserAgent(this, getString(R.string.kAppName)))
                .setConnectTimeoutMs(DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS)
                .setReadTimeoutMs(DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS)
                .setAllowCrossProtocolRedirects(true);
        DefaultDataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(
                context, httpDataSourceFactory);
        DataSource.Factory cacheDataSourceFactory = new CacheDataSource.Factory().setCache(getCache(context)).setCacheReadDataSourceFactory(dataSourceFactory);

        String extension = simulatedUrl.substring(simulatedUrl.lastIndexOf(".") + 1);
        if (!AppUtils.isEmpty(extension) && extension.contains("?")) {
            String[] arr = extension.split("\\?");
            extension = arr[0];
        }
        String fileNameID = GenericUtilsKt.getDownloadedID(menu);
        extension = AppConstants.isTvodApp() ? AppConstants.GEN2_DOWNLOADED_VIDEO_EXTENSION : extension;
        File file = FileManager.getFileOnExternalDirectory(AppConstants.DIRECTORY_NAME_CACHE_VIDEOS + File.separator + fileNameID + "." + extension);
        if(null != file) {
            if (AppConstants.isTvodApp()){
                if (AppFeedManager.downloadStateMap != null &&
                        AppFeedManager.downloadStateMap.containsKey(fileNameID) &&
                        AppFeedManager.downloadStateMap.get(fileNameID) == AppConstants.DOWNLOAD_VIDEO_STATE_TVOD.COMPLETE_STATE){
                    return new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
                            .createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
                }else {
                    return null;
                }
            }else {
                return new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
                        .createMediaSource(MediaItem.fromUri(Uri.fromFile(file)));
            }
        }
        return null;
    }

Expected result

Downloaded Video in File should be loaded and play fine in Media3 Player in Offline Mode.

Actual result

While Playing Offline Downloaded Video File in Media3 Getting error:- 2024-10-11 14:50:36.640 14992-25037 ExoPlayerImplInternal core2.maz.com.core1 E Playback error androidx.media3.exoplayer.ExoPlaybackException: Source error at androidx.media3.exoplayer.ExoPlayerImplInternal.handleIoException(ExoPlayerImplInternal.java:717) at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:689) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loopOnce(Looper.java:226) at android.os.Looper.loop(Looper.java:313) at android.os.HandlerThread.run(HandlerThread.java:67) Caused by: androidx.media3.datasource.FileDataSource$FileDataSourceException at androidx.media3.datasource.FileDataSource.open(FileDataSource.java:125) at androidx.media3.datasource.DefaultDataSource.open(DefaultDataSource.java:275) at androidx.media3.datasource.cache.CacheDataSource.openNextSource(CacheDataSource.java:799) at androidx.media3.datasource.cache.CacheDataSource.open(CacheDataSource.java:612) at androidx.media3.datasource.StatsDataSource.open(StatsDataSource.java:86) at androidx.media3.exoplayer.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1029) at androidx.media3.exoplayer.upstream.Loader$LoadTask.run(Loader.java:421) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644) at java.lang.Thread.run(Thread.java:1012)

Media

Stream will be shared on :- android-media-github@google.com , Download the stream and Save it to In-App Data Scope File Storage and load it in Media3 via a File URI.

Bug Report

marcbaechinger commented 1 month ago

Thanks for your report.

When I use the link you sent I get an HTTP 403 with browser and curl I'm afraid.

However, before going into the details how to get that file and given downloading from that URL can involve some difficulties like the ones I just experienced manually and probably similarly when accessing it programmatically. Can we double check the file exists there at this location?

I'm asking because I did the following in PlayerActivity of the main demo app of ExoPlayer:

File filesDir = getFilesDir();
File videoDir = new File(filesDir.getAbsolutePath() + "/.Videos");
if (!videoDir.exists()) {
   videoDir.mkdir();
}
File video = new File(videoDir.getAbsolutePath(), "dizzy.mp4");

Log.d("files", "filesDir: " + filesDir.getAbsolutePath() + " can write " + filesDir.canWrite());
Log.d("files", "videoDir: " + videoDir.getAbsolutePath() + " can write " + videoDir.canWrite());
Log.d("files", "video: " + video.getAbsolutePath() + " can read " + video.canRead());

Log.d("files", "playing " + Uri.fromFile(video));
player.setMediaItem(MediaItem.fromUri(Uri.fromFile(video)), /* resetPosition= */ !haveStartPosition);
player.prepare();

This failed as expected because there is no .Videos/dizzy.mp4 in the files dir of that app. But it gave me a clear error message of the root cause about the file not found: "Caused by: java.io.FileNotFoundException: /data/user/0/androidx.media3.demo.main/files/.Videos/dizzy.mp4: open failed: ENOENT (No such file or directory)"

I then downloaded dizzy.mp4 with curl:

curl -o dizzy.mp4 https://html5demos.com/assets/dizzy.mp4

I then uploaded the vile in the video directory created by the code above. When I tried again, it didn't work! it gave me a clear error message of the root cause about the extractors not recognizing the container: Caused by: androidx.media3.exoplayer.source.UnrecognizedInputFormatException: None of the available extractors (FragmentedMp4Extractor, Mp4Extractor, FlvExtractor, FlacExtractor, WavExtractor, AmrExtractor, PsExtractor, OggExtractor, TsExtractor, MatroskaExtractor, AdtsExtractor, Ac3Extractor, Ac4Extractor, Mp3Extractor, AviExtractor, JpegExtractor, PngExtractor, WebpExtractor, BmpExtractor, HeifExtractor, AvifExtractor) could read the stream. {contentIsMalformed=false, dataType=1}

So I figured that I missed the -L option in curl hence my file wasn't an mp4 but an HTML page telling me about the redirect.

Uploading the actual video file then made it play as expected from that location.

What I'm trying to say is that there are many reasons how I made it fail, but it was always regarding the file not being there or being not the file I expected it to be. And each time I got a clear error description.

The path file:///data/user/0/core2.maz.com.core1/files/.Videos/293711.mp4 you are using is pretty much what I get file///: /data/user/0/androidx.media3.demo.main/files/.Videos/dizzy.mp4 so this seems fine.

So if the expected valid video file is indeed in that location, may I ask you to attempt t play the file as you did above and then right after the stack trace landed in logcat do a bug report and upload it here? I'm wondering whether we find some more log output that tells us what happened.

If that all does not help, then we can get back to downloading that link you sent, but I would be surprised the problem is about the file being played from that location except the file is not the one we expect it to be.

Please let m,e know what you think or if I misunderstood something.

ajayhack commented 1 month ago

@marcbaechinger , I have tried below 2 download urls of our 2 different apps with same Repo(Which has same Codebase):-

Download URL 1st:- http://dp8hsntg6do36.cloudfront.net/5bb256198c1abc3aec000019/91dbefd7-5649-41bc-8c89-5f0446b2f2a1high.mp4?requester=bbc Downloaded Fine inside App Scope Data File Path:- file:///data/user/0/core2.maz.com.core1/files/.Videos/520384.mp4 and plays fine from file path in Media 3 without any issue.

Download URL 2nd:- https://ovf.cdn.zype.com/5527e30469702d5e08000000/uploads/562e9fd769702d04e3815601/022891466598_DEADBOYS_1986_1.mp4?edge-cache-token=FullPath~Starts%3D1728993581~Expires%3D1729079981~Signature%3DQerj7grvcM-oREKRwA-F4LsEcLKQ65RUvSazhuatBsl51zcp_Cb9m0YhEi439CB6h8dr-2GPQbW1tOiDsHLeBw

Downloaded Fine inside App Scope Data File Path:- file:///data/user/0/core2.maz.com.core1/files/.Videos/293711.mp4 and try to plays from file path url but it doesn't play at all and instantly gives Media 3 Internal Source error + androidx.media3.datasource.FileDataSource$FileDataSourceException

Note:- 2nd Download URL has expiry of 1 day please use it asap. Also both download url's are above 800MB File. I have also checked in Logs for both downloaded file and they are successfully downloaded , also I am getting the file and there size correctly as well.

All Logic is same for Download + Play from App Scope Data File Path for both the URL's. I have tried to download 2nd url video and placed it under android->packageName->res-raw directory and read/play from there in Media3 and it plays fine , So why this not playing fine from App Scope File?

Full Source ADB Bug Report is attached. dumpstate-2024-10-15-11-11-51.zip

marcbaechinger commented 1 month ago

Thanks!

That works for me and I still can't repro I'm afraid. I see a band on a stage starting a concert (see logs below) or BBC news. So I can't repro with either of these files.

The error in the logs and the one above seems to be originated here in FileDataSource.open() here.

androidx.media3.datasource.FileDataSource.open(FileDataSource.java:125) at 

When I set a break point there, it gives me bytesRemaining=931351140. While when the exception is thrown there the bytes remaining is below 0 as per the expression.

I tried an empty file with 0 Bytes, but this gives a different error So I was wondering if there is an issue when the length of the file is calculated with file.length() - dataSpec.position, but from how you create the MediaItem in the code above this is all default, so it shouldn't make a difference:

bytesRemaining =
          dataSpec.length == C.LENGTH_UNSET ? file.length() - dataSpec.position : dataSpec.length;

Can you repro this on another device as well or is it specific to the device that you mention above?

14:45:44.881  D  filesDir: /data/user/0/androidx.media3.demo.main/files can write true
14:45:44.881  D  videoDir: /data/user/0/androidx.media3.demo.main/files/.Videos can write true
14:45:44.881  D  video: /data/user/0/androidx.media3.demo.main/files/.Videos/test.mp4 can read true
14:45:44.881  D  playing file:///data/user/0/androidx.media3.demo.main/files/.Videos/test.mp4
14:45:44.884  D  timeline [eventTime=0.01, mediaPos=0.00, window=0, periodCount=1, windowCount=1, reason=PLAYLIST_CHANGED
14:45:44.884  D    period [?]
14:45:44.884  D    window [?, seekable=false, dynamic=true]
14:45:44.884  D  ]
14:45:44.884  D  mediaItem [eventTime=0.01, mediaPos=0.00, window=0, reason=PLAYLIST_CHANGED]
14:45:44.886  D  state [eventTime=0.01, mediaPos=0.00, window=0, BUFFERING]
14:45:44.909  D  surfaceSize [eventTime=0.03, mediaPos=0.00, window=0, 1080, 2201]
14:45:44.916  D  loading [eventTime=0.04, mediaPos=0.00, window=0, period=0, true]
14:45:44.917  D  timeline [eventTime=0.04, mediaPos=0.00, window=0, period=0, periodCount=1, windowCount=1, reason=SOURCE_UPDATE
14:45:44.917  D    period [?]
14:45:44.917  D    window [?, seekable=false, dynamic=false]
14:45:44.917  D  ]
14:45:45.268  D  timeline [eventTime=0.39, mediaPos=0.00, window=0, period=0, periodCount=1, windowCount=1, reason=SOURCE_UPDATE
14:45:45.268  D    period [3797.36]
14:45:45.268  D    window [3797.36, seekable=true, dynamic=false]
14:45:45.268  D  ]
14:45:45.328  D  videoEnabled [eventTime=0.45, mediaPos=0.00, window=0, period=0]
14:45:45.328  D  audioEnabled [eventTime=0.45, mediaPos=0.00, window=0, period=0]
14:45:45.328  D  tracks [eventTime=0.45, mediaPos=0.00, window=0, period=0
14:45:45.328  D    group [
14:45:45.329  D      [X] Track:0, id=1, mimeType=video/avc, codecs=avc1.4D401E, res=720x480, color=NA/NA/NA/8/8, fps=29.970032, supported=YES
14:45:45.329  D    ]
14:45:45.329  D    group [
14:45:45.329  D      [X] Track:0, id=2, mimeType=audio/mp4a-latm, bitrate=160000, codecs=mp4a.40.2, channels=2, sample_rate=48000, language=und, supported=YES
14:45:45.329  D    ]
14:45:45.329  D    Metadata [
14:45:45.329  D      TSSE: description=null: values=[Lavf52.32.0]
14:45:45.329  D      Mp4Timestamp: creation time=2082844800, modification time=2082844800, timescale=1000
14:45:45.329  D    ]
14:45:45.329  D  ]
14:45:45.332  D  downstreamFormat [eventTime=0.46, mediaPos=0.00, window=0, period=0, id=1, mimeType=video/avc, codecs=avc1.4D401E, res=720x480, color=NA/NA/NA/8/8, fps=29.970032]
14:45:45.368  D  videoDecoderInitialized [eventTime=0.49, mediaPos=0.00, window=0, period=0, c2.exynos.h264.decoder]
14:45:45.368  D  videoInputFormat [eventTime=0.49, mediaPos=0.00, window=0, period=0, id=1, mimeType=video/avc, codecs=avc1.4D401E, res=720x480, color=NA/NA/NA/8/8, fps=29.970032]
14:45:45.368  D  downstreamFormat [eventTime=0.49, mediaPos=0.00, window=0, period=0, id=2, mimeType=audio/mp4a-latm, bitrate=160000, codecs=mp4a.40.2, channels=2, sample_rate=48000, language=und]
14:45:45.405  D  audioDecoderInitialized [eventTime=0.53, mediaPos=0.00, window=0, period=0, c2.android.aac.decoder]
14:45:45.405  D  audioInputFormat [eventTime=0.53, mediaPos=0.00, window=0, period=0, id=2, mimeType=audio/mp4a-latm, bitrate=160000, codecs=mp4a.40.2, channels=2, sample_rate=48000, language=und]
14:45:45.409  D  rendererReady [eventTime=0.53, mediaPos=0.00, window=0, period=0, rendererIndex=1, audio, true]
14:45:45.425  D  audioTrackInit [eventTime=0.55, mediaPos=0.00, window=0, period=0, 2,12,48000,false,false,61568]
14:45:45.436  D  videoSize [eventTime=0.56, mediaPos=0.00, window=0, period=0, 720, 480]
14:45:45.437  D  renderedFirstFrame [eventTime=0.56, mediaPos=0.00, window=0, period=0, Surface(name=null mNativeObject=-5476376649636185664)/@0xc8979fa]
14:45:45.447  D  surfaceSize [eventTime=0.57, mediaPos=0.00, window=0, period=0, 1080, 720]
14:45:45.449  D  rendererReady [eventTime=0.57, mediaPos=0.00, window=0, period=0, rendererIndex=0, video, true]
14:45:45.449  D  state [eventTime=0.57, mediaPos=0.00, window=0, period=0, READY]
ajayhack commented 1 month ago

@marcbaechinger This is reproduced on other Devices Too like OnePlus 11 Mobile Device.

Also the content which is not playing after download and giving "FileDataSourceException" :- I have checked after downloading it's giving proper size of the file and location of the file (that means correct file is getting downloaded) , I don't know why as you mention 0 bytes(file.length() - dataSpec.position) , As File is already downloaded correctly and we also got the size as well.

The latest url for the same content:- https://ovf.cdn.zype.com/5527e30469702d5e08000000/uploads/562e9fd769702d04e3815601/022891466598_DEADBOYS_1986_1.mp4?edge-cache-token=FullPath~Starts%3D1728993581~Expires%3D1729079981~Signature%3DQerj7grvcM-oREKRwA-F4LsEcLKQ65RUvSazhuatBsl51zcp_Cb9m0YhEi439CB6h8dr-2GPQbW1tOiDsHLeBw

So, As the content is not available for India , we checked it through our QA's in USA and for them it's working fine after download and play from the same codebase.

So I wanted to know is there any content permission restriction issue causing this for non-allowed content region?

Means content is not available for India , but we have downloaded it and then later on while playing from App Scope File Storage it's giving "FileDataSourceException" .

The 1st download url(which I have shared in previous comment):- http://dp8hsntg6do36.cloudfront.net/5bb256198c1abc3aec000019/91dbefd7-5649-41bc-8c89-5f0446b2f2a1high.mp4?requester=bbc

This 👆 is available for Globally all region and we have downloaded and play from App Scope File Storage and it's playing fine.

Can you look my issue from the above points point of view.

marcbaechinger commented 1 month ago

ExoPlayer doesn't apply any restrictions.

we checked it through our QA's in USA and for them it's working fine after download and play from the same codebase.

This again points to a difference in the files produced by the download, not? Can you ask the QA team to upload the file that they downloaded and try with this file manually?

ajayhack commented 1 month ago

@marcbaechinger what about this point : Also the content which is not playing after download and giving "FileDataSourceException" :- I have checked after downloading it's giving proper size of the file and location of the file (that means correct file is getting downloaded) , I don't know why as you mention 0 bytes(file.length() - dataSpec.position) , As File is already downloaded correctly and we also got the size as well.

As I'm seeing in debugging into Media3 internal class and they do get the proper file size but then it becomes 0bytes in here:- (file.length() - dataSpec.position) doesn't it means there is some issue in Media3 Internal class for playing this content.

marcbaechinger commented 1 month ago

Sorry if I wasn't clear with (file.length() - dataSpec.position). What I meant is that the error is produced because remaining bytes is below 0 and the only reason I see why this could happen is through this calculation. Please accept my apologies, I shouldn't have mentioned this.

doesn't it means there is some issue in Media3 Internal class for playing this content.

I don't know. It works for your QA. It worked for me with your files and with another random file I tried. FileDataSource exists since 2017 and we haven't heard of fundamental issues that are caused by it. So I conclude there is something specific to your case, and to investigate I need a repro.

Please understand I'm not going to discuss this some further, unless this issue becomes actionable by someone providing a way to repro.

ajayhack commented 1 month ago

@marcbaechinger I have checked with 3 URL's for same app:- https://ovf.cdn.zype.com/5527e30469702d5e08000000/uploads/6703ec22ec4d8e00011e46c0/760137282884_BrainDamage_feature.mp4?edge-cache-token=FullPath~Starts%3D1729160987~Expires%3D1729247387~Signature%3DxaXakcHRO3dxLdqvtbHayGN6DW0okaPXlvKTOaIBaVnw-qoUNWLxJwgAVbN0QJ_Pqiyntw2C0KM97nXYXPagDg

https://ovf.cdn.zype.com/5527e30469702d5e08000000/uploads/66e8c370b1e5d60001760519/NF_SECOND_ROUND___REGGAE___Final.mp4?edge-cache-token=FullPath~Starts%3D1729160941~Expires%3D1729247341~Signature%3DeocCM0Oga6hMWnZNV6azupeoSYwYzdCobJauXuIcu7wlcRP7iwA9s4u0GJBitCM53pCYQ4xQNBGNfEk7F93oCg

https://ovf.cdn.zype.com/5527e30469702d5e08000000/uploads/562e9fd769702d04e3815601/022891466598_DEADBOYS_1986_1.mp4?edge-cache-token=FullPath~Starts%3D1729161129~Expires%3D1729247529~Signature%3DiF6bS0fGX3U50REm95kcNazXmC7q3CUQzfMPXDQ-H2Q6vX-u0Cb3hSLHS9xxjt9Jk_jGuHB9uT1dhT2tbcaQBw

What I observed that above first 2 url's are playing on browser but the 3rd url is not playing on browser and this is the url which is causing me issue after downloading and playing.

So, Can you check all 3 url's downlaoding it to App Package Scope Storage and read from there and play on your end.

marcbaechinger commented 1 month ago

To me it looks like that download service is flaky.

I have downloaded the second and the third URI from above.

The third downloaded successfully and then I played it from the files dir without issue. The band is on stage and plays as before (888MB).

The second file is 1.5GB. Download started quite quickly, then slowed down and finally aborted from the server side. curl tells me after downloading 884 of 1455M:

 60 1455M   60  883M    0     0  1552k      0  0:15:59  0:09:42  0:06:17 1601k* Recv failure: Connection reset by peer
* OpenSSL SSL_read: Connection reset by peer, errno 104
* Failed receiving HTTP2 data: 56(Failure when receiving data from the peer)
 60 1455M   60  884M    0     0  1552k      0  0:15:59  0:09:43  0:06:16 1469k
* Connection #0 to host ovf.cdn.zype.com left intact
curl: (56) Recv failure: Connection reset by peer

I then play the file from the files dir and it starts playing. However, when I seek to the end of the file, I get the very same stack trace as you get:

14:26:01.562  E  playerFailed [eventTime=3.50, mediaPos=931.90, window=0, period=0, errorCode=ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE
                   androidx.media3.exoplayer.ExoPlaybackException: Source error
                       at androidx.media3.exoplayer.ExoPlayerImplInternal.handleIoException(ExoPlayerImplInternal.java:737)
                       at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:709)
                       at android.os.Handler.dispatchMessage(Handler.java:103)
                       at android.os.Looper.loopOnce(Looper.java:232)
                       at android.os.Looper.loop(Looper.java:317)
                       at android.os.HandlerThread.run(HandlerThread.java:85)
                   Caused by: androidx.media3.datasource.FileDataSource$FileDataSourceException
                       at androidx.media3.datasource.FileDataSource.open(FileDataSource.java:118)
                       at androidx.media3.datasource.DefaultDataSource.open(DefaultDataSource.java:275)
                       at androidx.media3.datasource.cache.CacheDataSource.openNextSource(CacheDataSource.java:801)
                       at androidx.media3.datasource.cache.CacheDataSource.open(CacheDataSource.java:614)
                       at androidx.media3.datasource.StatsDataSource.open(StatsDataSource.java:87)
                       at androidx.media3.exoplayer.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1062)
                       at androidx.media3.exoplayer.upstream.Loader$LoadTask.run(Loader.java:450)
                       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
                       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
                       at java.lang.Thread.run(Thread.java:1117)
                 ]

I conclude this isn't a Media3 issue. The file is corrupted during download. If the file downloads correctly, like it did for me in most cases and apparently for your QA as well, then playing from the files dir works fine.

I added the bad media label accordingly.