'Exoplayer - How to download a simple m3u8 file with ts chunks

Hi I'm trying to download a simple m3u8 video from a given URL using ExoPlayer and the DownloadService

the URL returns

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:16
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:13.733333,
q1080_0.ts
#EXTINF:8.333333,
q1080_1.ts
#EXTINF:8.333333,
q1080_2.ts
#EXTINF:15.533333,
q1080_3.ts
#EXT-X-ENDLIST

I tried using DownloadHelper like this

val downloadHelper: DownloadHelper = DownloadHelper.forMediaItem(
                        activity,
                        MediaItem.fromUri(Uri.parse(url)),
                        DefaultRenderersFactory(activity),
                        DefaultHttpDataSourceFactory())

 downloadHelper.prepare(object : DownloadHelper.Callback {
      override fun onPrepared(helper: DownloadHelper) { }
     override fun onPrepareError(helper: DownloadHelper, e: IOException) { }
 })

val buffer = ByteArray(1204)
val request = downloadHelper.getDownloadRequest(buffer) // <--- CRASH HERE
DownloadService.sendAddDownload(activity, HLSDownloaderService::class.java, request, false)

but it doesn't work. I'm getting the next error

2021-05-22 18:50:37.816 28929-28929/app.meedu.app E/MethodChannel#app.meedu.app/video-downloader: Failed to handle method call
    java.lang.IllegalStateException
        at com.google.android.exoplayer2.util.Assertions.checkState(Assertions.java:84)
        at com.google.android.exoplayer2.offline.DownloadHelper.assertPreparedWithMedia(DownloadHelper.java:827)
        at com.google.android.exoplayer2.offline.DownloadHelper.getDownloadRequest(DownloadHelper.java:753)
        at app.meedu.app.platform_channels.VideoDownloader.onMethodCall(VideoDownloader.kt:53)
        at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:233)
        at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:85)
        at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:818)
        at android.os.MessageQueue.nativePollOnce(Native Method)
        at android.os.MessageQueue.next(MessageQueue.java:335)
        at android.os.Looper.loop(Looper.java:183)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
2021-05-22 18:50:41.570 28929-29125/app.meedu.app E/VideoFrameReleaseHelper: Failed to call Surface.setFrameRate
      java.lang.IllegalStateException: Surface has already been released.
        at android.view.Surface.checkNotReleasedLocked(Surface.java:696)
        at android.view.Surface.setFrameRate(Surface.java:905)
        at com.google.android.exoplayer2.video.VideoFrameReleaseHelper.setSurfaceFrameRateV30(VideoFrameReleaseHelper.java:389)
        at com.google.android.exoplayer2.video.VideoFrameReleaseHelper.clearSurfaceFrameRate(VideoFrameReleaseHelper.java:379)
        at com.google.android.exoplayer2.video.VideoFrameReleaseHelper.onStopped(VideoFrameReleaseHelper.java:232)
        at com.google.android.exoplayer2.video.MediaCodecVideoRenderer.onStopped(MediaCodecVideoRenderer.java:471)
        at com.google.android.exoplayer2.BaseRenderer.stop(BaseRenderer.java:165)
        at com.google.android.exoplayer2.ExoPlayerImplInternal.ensureStopped(ExoPlayerImplInternal.java:1599)
        at com.google.android.exoplayer2.ExoPlayerImplInternal.disableRenderer(ExoPlayerImplInternal.java:1608)
        at com.google.android.exoplayer2.ExoPlayerImplInternal.resetInternal(ExoPlayerImplInternal.java:1351)
        at com.google.android.exoplayer2.ExoPlayerImplInternal.stopInternal(ExoPlayerImplInternal.java:1314)
        at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:494)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:223)
        at android.os.HandlerThread.run(HandlerThread.java:67)


Solution 1:[1]

you need to perform helper.getDownloadRequest inside onPrepared(helper: DownloadHelper) { }. And make sure you are using the helper that is passed into the callback, not the one you defined yourself!

here is my code from a working app

public static void downloadAsset(HLSAsset asset) {
    MediaItem mediaItem = asset.getMediaItem();

    DownloadHelper downloadHelper = DownloadHelper.forMediaItem(
            context,
            mediaItem,
            null,
            dataSourceFactory
    );

    downloadHelper.prepare(new DownloadHelper.Callback() {
        @Override
        public void onPrepared(DownloadHelper helper) {
            DownloadRequest request = helper
                    .getDownloadRequest(
                            asset.id,
                            null
                    );

            DownloadService.sendAddDownload(
                    context,
                    AssetDownloadService.class, 
                    request,
                    false
            );
        }

        @Override
        public void onPrepareError(DownloadHelper helper, IOException e) {
            Log.w(TAG, "Something happened");
        }
    });
}

and relevant HLSAsset bit

public class HLSAsset {
    String id;
    String hlsUrl;

    ... 

    MediaItem getMediaItem() {
        return new MediaItem.Builder()
                .setUri(hlsUrl)
                .setStreamKeys(
                    Collections.singletonList(
                     new StreamKey(HlsMultivariantPlaylist.GROUP_INDEX_VARIANT, 0))
                    )
                .setMimeType(MimeTypes.APPLICATION_M3U8)
                .build();
    }

}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jo Gro