1

I am trying to pre-cache/pre-buffer HLS videos to my app. I used CacheWriter to cache the (.mp4) file, But it was not able to cache segments of HLS video. Basically, I have only URL of the master playlist file which has media playlists of different qualities, and that each media playlist has segments (.ts).

So, I have to cache the master playlist and any one media playlist and then some segments and play the cached media to Exoplayer. how can I cache these? I also visited https://github.com/google/ExoPlayer/issues/9337 But this does not have any example to do so.

This is how I cached .mp4 by CacheWriter

    CacheWriter cacheWriter = new CacheWriter( mCacheDataSource,
                        dataSpec,
                        null,
                        progressListener);

    cacheWriter.cache();

1 Answers1

0

I am answering my own question for further users struggling on it. We can pre-cache pre-cache HLS adaptive stream in ExoPlayer By using HlsDownloader provided by Exoplayer.

Add this Kotlin class to your project ExoPlayerModule.kt.

//SitaRam
package com.example.youtpackagename

import android.content.Context
import android.util.Log
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.source.hls.offline.HlsDownloader
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
import com.google.android.exoplayer2.upstream.FileDataSource
import com.google.android.exoplayer2.upstream.cache.CacheDataSource
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
import com.google.android.exoplayer2.upstream.cache.SimpleCache
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.util.concurrent.CancellationException

//bytes to be downloaded
private const val PRE_CACHE_AMOUNT = 2 * 1048576L

class ExoPlayerModule(context: Context) {
    
    private var cronetDataSourceFactory =  DefaultHttpDataSource.Factory()

    //StaticMember is class which contains cookie in my case, you can skip cookies and use DefaultHttpDataSource.Factory().
  /*val Cookie = mapOf("Cookie" to StaticMember.getCookie())
   private var cronetDataSourceFactory = if (StaticMember.getCookie() != null) {
       DefaultHttpDataSource.Factory().setDefaultRequestProperties(Cookie)
   }else {
       DefaultHttpDataSource.Factory()
   }*/

    private val cacheReadDataSourceFactory = FileDataSource.Factory()
    private var cache = simpleCache.SimpleCache(context)
    private var cacheDataSourceFactory = CacheDataSource.Factory()
        .setCache(cache)
//        .setCacheWriteDataSinkFactory(cacheSink)
        .setCacheReadDataSourceFactory(cacheReadDataSourceFactory)
        .setUpstreamDataSourceFactory(cronetDataSourceFactory)
        .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)

    fun isUriCached(uri: String, position: Long = 0): Boolean {
        return cache.isCached(uri, position, PRE_CACHE_AMOUNT)
    }
    
    //updating cookies (if you are using cookies).
   /* fun updateDataSourceFactory(){
        val Cookie = mapOf("Cookie" to StaticMember.getCookie())
        cronetDataSourceFactory = if (StaticMember.getCookie() != null) {
            DefaultHttpDataSource.Factory().setDefaultRequestProperties(Cookie)
        }else {
            DefaultHttpDataSource.Factory()
        }
        cacheDataSourceFactory = CacheDataSource.Factory()
            .setCache(cache)
//        .setCacheWriteDataSinkFactory(cacheSink)
            .setCacheReadDataSourceFactory(cacheReadDataSourceFactory)
            .setUpstreamDataSourceFactory(cronetDataSourceFactory)
            .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
    }*/

    // TODO add the same for mp4. Also they might be a much better option, since they only have
    // single track, so no matter what connection you have - loading can't happen twice
    fun getHlsMediaSource(mediaItem: MediaItem): HlsMediaSource {
        return HlsMediaSource.Factory(cacheDataSourceFactory)
            .setAllowChunklessPreparation(true)
            .createMediaSource(mediaItem)
    }

    fun releaseCache() = cache.release()

    suspend fun preCacheUri(mediaItem: MediaItem) {
        val downloader = HlsDownloader(mediaItem, cacheDataSourceFactory)
        withContext(Dispatchers.IO) {
            try {
                downloader.download { _, bytesDownloaded, _ ->
                        if (MainActivity.nextUrl==mediaItem){
                          //  Log.e("bytesCaching", "while: same $mediaItem same")
                        }else {
                          //  Log.e("bytesCaching", "while: $mediaItem")
                            downloader.cancel()
                        }
                    if (bytesDownloaded >= PRE_CACHE_AMOUNT) {
//                        log("video precached at $percent%")
                        downloader.cancel()
                    }
                }
            } catch (e: Exception) {
                if (e !is CancellationException) log("precache exception $e")
            }
        }
    }

    private fun log(s: String) {
        TODO("Not yet implemented")
    }
}

Initializing ExoPlayerModule

ExoPlayerModule PlayerModuleO = new ExoPlayerModule(MainActivity.this);

For Pre-Loading.

String previousUrl = "";
    public void preLoad(String url) {
        if (previousUrl.equals(url)) {
            return;
        }

        previousUrl = url;
        MediaItem mediaItem =MediaItem.fromUri(Uri.parse(url));
        PlayerModuleO.preCacheUri(mediaItem, new Continuation<>() {
            @NonNull
            @Override
            public CoroutineContext getContext() {
                return EmptyCoroutineContext.INSTANCE;
            }

            @Override
            public void resumeWith(@NonNull Object o) {

            }
        });

    }

Playing cached or non-cached media.

 MediaItem mediaItem = MediaItem.fromUri(Uri.parse(url));

        exoPlayer.setMediaSource(PlayerModuleO.getHlsMediaSource(mediaItem));
        exoPlayer.prepare();
        exoPlayer.play();

Releasing cache

PlayerModuleO.releaseCache();

If you are having any problems then feel free to ask.