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();
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.