ExoPlayer buffering and cache – code sample

After, the third time I’ve googled for ExoPlayer buffering and caching solution, and it took me a while to find it, decided to put this code sample here. Hopefully to find it on next time 🙂

    <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/playerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:resize_mode="fit"
        android:background="@android:color/black"/>
class ExoPlayerController(private var playWhenReady: Boolean = true
      ) : LifecycleObserver, Player.EventListener {
...
private val url = "put video url here"
private var currentWindow = 0
private var playbackPosition: Long = 0

private fun initializePlayer() {
    val player = ExoPlayerFactory.newSimpleInstance(
            context,
            DefaultRenderersFactory(context),
            DefaultTrackSelector(),
            getLoadControl()
    )

    playerView.player = player
    val mediaSource = buildMediaSource(Uri.parse(url))

    player?.apply {
            playWhenReady = this@ExoPlayerController.playWhenReady
            seekTo(currentWindow, playbackPosition)
            addListener(this@ExoPlayerController)
            prepare(mediaSource, false, false)
     }
}

private fun releasePlayer() {
        player?.let {
            playWhenReady = it.playWhenReady
            playbackPosition = it.currentPosition
            currentWindow = it.currentWindowIndex
            it.release()
            player = null
        }
}

Buffering control:

// Before changing the values consider reading the following article
// https://blogs.akamai.com/2019/10/enhancing-a-streaming-quality-for-exoplayer---part-2-exoplayers-buffering-strategy-how-to-lower-.html
    
private fun getLoadControl() : LoadControl {
        val builder: DefaultLoadControl.Builder = 
                           DefaultLoadControl.Builder()
        builder.setBufferDurationsMs(
            50000, // DefaultLoadControl.DEFAULT_MIN_BUFFER_MS,
            50000, / /*.DEFAULT_MAX_BUFFER_MS,
            1000, // *.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
            1000 // *.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
        )
        return builder.createDefaultLoadControl()
}

Build a media source:

private fun buildMediaSource(uri: Uri): MediaSource? {
        val cacheDataSourceFactory = CacheDataSourceFactory(
            VideoCache.get(context),
            DefaultHttpDataSourceFactory("exoplayer-demo")
        )
        return ExtractorMediaSource.Factory(cacheDataSourceFactory).createMediaSource(uri)
    }

Video Cache:

object VideoCache {
    private var downloadCache: SimpleCache? = null
    private const val maxCacheSize: Long = 20 * 1024 * 1024
    private const val dirName: String = "media"

    fun get(context : Context) : SimpleCache{
        if (downloadCache == null) {
            val cacheFolder = File(context.cacheDir, dirName)
            val cacheEvictor = LeastRecentlyUsedCacheEvictor(maxCacheSize)
            downloadCache = SimpleCache(cacheFolder, cacheEvictor)
        }
        return downloadCache as SimpleCache
    }
}

Life Cycle Events:

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onStart() {
        if (Util.SDK_INT >= 24) {
            initializePlayer()
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() {
        if ((Util.SDK_INT < 24 || player == null)) {
            initializePlayer()
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause() {
        if (Util.SDK_INT < 24) {
            releasePlayer()
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onStop() {
        if (Util.SDK_INT >= 24) {
            releasePlayer()
        }
    }

Player.EventListener

override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
        when (playbackState) {
            Player.STATE_READY -> {
                //Log.d("ZAQ", "STATE_READY")
                if (player?.playWhenReady == true) onPlayerPlaying()
            }
            //Player.STATE_BUFFERING -> Log.d("ZAQ", "STATE_BUFFERING")
            //Player.STATE_ENDED -> Log.d("ZAQ", "STATE_ENDED")
            //Player.STATE_IDLE -> Log.d("ZAQ", "STATE_IDLE")
        }
}

private fun onPlayerPlaying() {
        // Do something
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.