Android Caching using ExoPlayer in Kotlin

This post is about how to implement caching using ExoPlayer in Android. To learn about how to implement Exoplayer Library to play media files on Android visit here.

 

Let’s create a simple video player app in which we will be fetching a video from a URL and cache (inside device memory) and play that video inside our ExoPlayer. 

Creating new Project

1 . Create a new project by going to File  New Android Project, select Empty Activity, provide app name, select language to kotlin and then finally click on finish.

2 . Open app-level build.gradle file, add the dependency of ExoPlayer in the dependencies section, and sync the project.

build.gradle

dependencies {
//exoplayer dependency
implementation 'com.google.android.exoplayer:exoplayer:2.15.0'
}

3. Go to AndroidManifest.xml and add the internet permission above <application> tag as shown below:

AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

4. Open the layout file activity_main.xml and add the PlayerView as shown below:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

5. Create a new kotlin class inside package folder, named it as MyApp.kt and add the below code:

MyApp.kt

package com.c1ctech.exoplayercachingexp

import android.app.Application
import com.google.android.exoplayer2.database.ExoDatabaseProvider
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
import com.google.android.exoplayer2.upstream.cache.SimpleCache

class MyApp : Application() {

companion object {
lateinit var simpleCache: SimpleCache
const val exoPlayerCacheSize: Long = 90 * 1024 * 1024
lateinit var leastRecentlyUsedCacheEvictor: LeastRecentlyUsedCacheEvictor
lateinit var exoDatabaseProvider: ExoDatabaseProvider
}

override fun onCreate() {
super.onCreate()
leastRecentlyUsedCacheEvictor = LeastRecentlyUsedCacheEvictor(exoPlayerCacheSize)
exoDatabaseProvider = ExoDatabaseProvider(this)
simpleCache = SimpleCache(cacheDir, leastRecentlyUsedCacheEvictor, exoDatabaseProvider)
}
}

In the above code,

SimpleCache: Constructs the cache. It takes three parameters :

  • cacheDir – A dedicated cache directory where we want to cache video.
  • LeastRecentlyUsedCacheEvictor– When the cache size is reached out, it will automatically find and remove files which least recently used.
  • databaseProvider – Provides the database in which the cache index is stored.

6. Open AndroidManifest.xml file and define the Application class using the name property inside <application> tag.

<application
android:name=".MyApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ExoplayerCachingExp">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

7. Open MainActivity.kt and add the below code:

MainActivity.kt

package com.c1ctech.exoplayercachingexp

import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
import com.google.android.exoplayer2.upstream.HttpDataSource
import com.google.android.exoplayer2.upstream.cache.CacheDataSource
import com.google.android.exoplayer2.upstream.cache.SimpleCache
import com.google.android.exoplayer2.util.Util

class MainActivity : AppCompatActivity() {

private lateinit var httpDataSourceFactory: HttpDataSource.Factory
private lateinit var defaultDataSourceFactory: DefaultDataSourceFactory
private lateinit var cacheDataSourceFactory: DataSource.Factory
private lateinit var simpleExoPlayer: SimpleExoPlayer
private val simpleCache: SimpleCache = MyApp.simpleCache

private var mPlayer: SimpleExoPlayer? = null
private lateinit var playerView: PlayerView
private val videoURL = "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4"


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

//get PlayerView by its id
playerView = findViewById(R.id.playerView)
}

private fun initPlayer() {

httpDataSourceFactory = DefaultHttpDataSource.Factory()
.setAllowCrossProtocolRedirects(true)

defaultDataSourceFactory = DefaultDataSourceFactory(
applicationContext, httpDataSourceFactory
)

//A DataSource that reads and writes a Cache.
cacheDataSourceFactory = CacheDataSource.Factory()
.setCache(simpleCache)
.setUpstreamDataSourceFactory(httpDataSourceFactory)
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)

// Create a player instance and set mediaSourceFactory.
simpleExoPlayer = SimpleExoPlayer.Builder(this)
.setMediaSourceFactory(DefaultMediaSourceFactory(cacheDataSourceFactory)).build()

val videoUri = Uri.parse(videoURL)
val mediaItem = MediaItem.fromUri(videoUri)
val mediaSource =
ProgressiveMediaSource.Factory(cacheDataSourceFactory).createMediaSource(mediaItem)

// Bind the player to the view.
playerView.player = simpleExoPlayer

//setting exoplayer when it is ready.
simpleExoPlayer.playWhenReady = true

//Seeks to a position specified in milliseconds in the specified window.
simpleExoPlayer.seekTo(0, 0)

//set repeat mode.
simpleExoPlayer.repeatMode = Player.REPEAT_MODE_OFF

// Set the media source to be played.
simpleExoPlayer.setMediaSource(mediaSource, true)

// Prepare the player.
simpleExoPlayer.prepare()
}


override fun onStart() {
super.onStart()
if (Util.SDK_INT >= 24) {
initPlayer()
}
}

override fun onResume() {
super.onResume()
if (Util.SDK_INT < 24 || mPlayer == null) {
initPlayer()
}
}

override fun onPause() {
super.onPause()
if (Util.SDK_INT < 24) {
releasePlayer()
}
}

override fun onStop() {
super.onStop()
if (Util.SDK_INT >= 24) {
releasePlayer()
}
}

private fun releasePlayer() {
if (mPlayer == null) {
return
}
//release player when done
mPlayer!!.release()
mPlayer = null
}
}

CacheDataSourceFactory : Constructs a factory which creates CacheDataSource instance for reading and writing the cache.

httpDataSourceFactory = DefaultHttpDataSource.Factory()
.setAllowCrossProtocolRedirects(true)

cacheDataSourceFactory = CacheDataSource.Factory()
.setCache(simpleCache)
.setUpstreamDataSourceFactory(httpDataSourceFactory)
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)

When you run the app it will look like this as shown below:

Leave a Reply