TheWidlarzGroup / react-native-video

A <Video /> component for react-native
MIT License
7.08k stars 2.85k forks source link

[FEATURE]: HLS Video on local cache directory is not playing in Android #3712

Open webhibegit opened 2 months ago

webhibegit commented 2 months ago

Version

v6 (Beta)

What platforms are you having the problem on?

Android

Architecture

Old architecture

What happened?

Hi, I am trying to build a video caching system like Instagram or YouTube reels. Now I facing a problem. When I play my HLS video that is on the cloud is playing fine but when I try to play them from my cache directory then it gives me an error {"error": {"extra": -2147483648, "what": 1}}.

Reproduction

repository link

Reproduction

Here I download my HLS video and its corresponding files to the cache directory

function* cacheAllReel() {
    let videos: reels = yield select((state: RootState) => state.Reel.reels.find((it) => !it.isDownloaded));

    const filePathArr = videos.src.split('/');
    const folderUrl = videos.src.split('/').slice(0, -1).join('/');
    const cachedFilePath = `${RNFS.ExternalCachesDirectoryPath}/videos/${filePathArr[filePathArr.length - 2]}`;

    try {
        console.log("`${cachedFilePath}/${filePathArr[filePathArr.length - 1]}`", `${cachedFilePath}/${filePathArr[filePathArr.length - 1]}`)
        yield call(RNFS.mkdir, cachedFilePath);

        yield call(RNFS.downloadFile, {
            fromUrl: videos.src,
            toFile: `${cachedFilePath}/${filePathArr[filePathArr.length - 1]}`,
            background: true
        });

        yield call(RNFS.downloadFile, {
            fromUrl: `${folderUrl}/high.m3u8`,
            toFile: `${cachedFilePath}/high.m3u8`,
            background: true
        });

        yield call(RNFS.downloadFile, {
            fromUrl: `${folderUrl}/mid.m3u8`,
            toFile: `${cachedFilePath}/mid.m3u8`,
            background: true
        });

        yield call(RNFS.downloadFile, {
            fromUrl: `${folderUrl}/low.m3u8`,
            toFile: `${cachedFilePath}/low.m3u8`,
            background: true
        });

        const regex = /^.*\.ts$/gm;

        let lowFileContent: string = yield call(RNFS.readFile, `${cachedFilePath}/low.m3u8`, 'utf8');
        let lowMatch;
        while ((lowMatch = regex.exec(lowFileContent)) !== null) {
            yield call(RNFS.downloadFile, {
                fromUrl: `${folderUrl}/${lowMatch[0]}`,
                toFile: `${cachedFilePath}/${lowMatch[0]}`,
                background: true
            });
        }

        let midFileContent: string = yield call(RNFS.readFile, `${cachedFilePath}/mid.m3u8`, 'utf8');
        let midMatch;
        while ((midMatch = regex.exec(midFileContent)) !== null) {
            yield call(RNFS.downloadFile, {
                fromUrl: `${folderUrl}/${midMatch[0]}`,
                toFile: `${cachedFilePath}/${midMatch[0]}`,
                background: true
            });
        }

        let highFileContent: string = yield call(RNFS.readFile, `${cachedFilePath}/high.m3u8`, 'utf8');
        let highMatch;
        while ((highMatch = regex.exec(highFileContent)) !== null) {
            yield call(RNFS.downloadFile, {
                fromUrl: `${folderUrl}/${highMatch[0]}`,
                toFile: `${cachedFilePath}/${highMatch[0]}`,
                background: true
            });
        }

    } catch (err) {
        console.log('RNFS Error', err);
    }
}

It downloads all files properly. And I check that playlist.m3u8 exists or not

let data = await RNFS.exists(
            '/storage/emulated/0/Android/data/com.charub/cache/videos/video_1712660382226/playlist.m3u8'
        );
console.log('data', data);

and it gives true. So my m3u8 file exists.

Now in the <Video/> section

<Video
    source={{
        uri: '/storage/emulated/0/Android/data/com.charub/cache/videos/video_1712660382226/playlist.m3u8'
    }}
    muted={muted}
    repeat={true}
    paused={!isCurrent}
    style={{
        flex: 1
    }}
    resizeMode={'cover'}
    onLoad={(data) => console.log('loda', data)}
    onError={(e) => console.log('e', e)}
/>

here it gives error {"error": {"extra": -2147483648, "what": 1}}. But When I change the URI to cloud URL or to its corresponding .ts file like /storage/emulated/0/Android/data/com.charub/cache/videos/video_1712660382226/high0.ts then it works fine. I won't understand what is happening.

Please help me out.

freeboub commented 2 months ago

See doc: https://react-native-video.github.io/react-native-video/other/caching Currently, caching is only supported for URLs that end in a .mp4, .m4v, or .mov extension Let's move this request to feature.

webhibegit commented 2 months ago

Can I play the local cache directory .m3u8 file? Because I am trying to build my own caching system for that.

AlirezaHadjar commented 2 months ago

@webhibegit I am having the same issue with react-native-track-player. Apparently, local m3u8 files are broken. I asked our backend dev to provide both mp3 and m3u8 versions of the media and use mp3 for saving locally.

freeboub commented 2 months ago

The issue here is that we currently have to easy way (or no implementation) to download chunks from the server. According to your trace, only the m3u is downloaded, but m3u is a master playlist which is also referencing sub files on the server (the chunks). So there is a real development to do in order to implement this behavior. That said we can have 2 ways to implement the feature.

Michota commented 1 month ago

Can we expect this feature (caching m3u8) to be introduced in near feature? I think the community calls for it :D

Michota commented 1 month ago

Bump. We need it!

freeboub commented 1 month ago

I think this is something like this should be implemented: https://medium.com/google-exoplayer/downloading-adaptive-streams-37191f9776e but I think it will break ABR.

Michota commented 2 weeks ago

3 weeks have passed. Is there any chance this will be added in June?