Android Video Playback: A Comprehensive Guide with ExoPlayer2
The mobile landscape has experienced incredible growth in media consumption, especially in video content. The Android ecosystem is rich with options for video playback in mobile applications. Understanding how to leverage these options can give your application a competitive edge.

Popular Libraries in the Industry
- Android’s Native MediaPlayer: The built-in Android media player is easy to implement but offers fewer customization options and may not be ideal for more advanced needs.
- VLC for Android SDK: This is based on the popular desktop VLC player and provides a lot of features and customization options.
- Video.js: Primarily used for web applications but can be adapted for Android through WebView.
- ijkPlayer: Developed by Bilibili, it’s known for high customization and efficiency, albeit at the cost of increased APK size.
- ExoPlayer: Developed and maintained by Google, ExoPlayer has gained immense popularity for its ease of use, customization options, and performance efficiency.
Focusing on ExoPlayer 2
ExoPlayer 2 is our library of choice for this guide. It offers advanced functionalities like DASH, SmoothStreaming, and advanced HLS, which are absent in Android’s native MediaPlayer.

implementation("com.google.android.exoplayer:exoplayer:2.19.1")
val player = ExoPlayer.Builder(context).build()
binding.playerView.player = player
val mediaItem = MediaItem.fromUri("yourURL")
player.setMediaItem(mediaItem)
player.prepare()
player.play()
Actually, everything you need to do to play a video is this many lines of code, but I want to explain a few different points.
Code detail
MediaItem.fromUri(url): Here we create a media item from the provided URL, which can be any supported media format.
player.prepare() and player.play(): These commands prepare and start the player. The media starts playing as soon as
player.play()
is called.
Looping Media in Android with ExoPlayer
Looping playback experience to the user. Looping a media file essentially means that when the media reaches its end, it automatically starts over from the beginning, providing a seamless experience for the user.
private var job: Job? = null
val player = ExoPlayer.Builder(root.context).build()
binding.playerView.player = player
val mediaItem = MediaItem.fromUri(url)
player.setMediaItem(mediaItem)
player.prepare()
player.play()
job?.cancel()
job = CoroutineScope(Dispatchers.Main).launch { // for ANR you can use another thread
while (isActive) {
delay(15000)
player.seekTo(0) // loop
player.play()
}
} // Set 15 seconds looping
job
: This holds the coroutine job that controls the loop. It cancels the existing loop before starting a new one.
CoroutineScope(Dispatchers.Main).launch
: This starts a coroutine on the UI thread.
isActive
: This check is to see if the coroutine is still active.
For thread issues second option :
private val handler = Handler()
private var replayRunnable: Runnable? = null
private fun startReplay(player: ExoPlayer) {
replayRunnable = object : Runnable {
override fun run() {
player.seekTo(0)
player.play()
handler.postDelayed(this, 15000)
}
}
handler.post(replayRunnable!!)
}
private fun stopReplay() {
replayRunnable?.let {
handler.removeCallbacks(it)
}
}
ExoPlayer in Instagram-like Stories
First, visualize a horizontal RecyclerView with user profiles, similar to Instagram. Then, you can add ExoPlayer to power the video playback.( first device screen on 2.1 )

val homeAdapter = HomeAdapter(storyClickListener = { url ->
routeToStoryPage(it)
})
val videoUrl = arguments?.getString("videoUrl")
val player = ExoPlayer.Builder(requireContext()).build()
binding.playerView.player = player
val mediaItem = videoUrl?.let { MediaItem.fromUri(it) }
if (mediaItem != null) {
player.setMediaItem(mediaItem)
}
player.prepare()
player.play()
Thats it. Finally a few tips
- Playback States: A player has different playback states (STATE_IDLE, STATE_BUFFERING, STATE_READY, STATE_ENDED), and you can track these states using a Player.Listener.
player.addListener(object : Player.Listener {
override fun onPlaybackStateChanged(playbackState: Int) {
when (playbackState) {
Player.STATE_IDLE -> {}
Player.STATE_BUFFERING -> {}
Player.STATE_READY -> {}
Player.STATE_ENDED -> {}
}
}
})
- AnalyticsListener and EventLogger: These are additional listeners for analysis and logging.
- Dynamic Playlist Management: You can add new media items to the current playlist, remove them, or rearrange them using various Player class methods (addMediaItem, removeMediaItem, moveMediaItem, etc.).
- Repeat and Shuffle Modes: You can control how the playlist is played using methods like Player.setRepeatMode and Player.setShuffleModeEnabled.
player.repeatMode = Player.REPEAT_MODE_ALL
player.shuffleModeEnabled = true
- Adding Ads: .setAdsConfiguration() is used for configuring ads.
- Live Streams: Live streams are continuously updated, and ExoPlayer helps manage this dynamic nature with methods like isCurrentWindowLive and isCurrentWindowDynamic.
- Live Offset: The difference between the playback position and real-time in live streams is known as live offset, which can be obtained using getCurrentLiveOffset.
- Adjustable Playback Speed: The playback speed can be adjusted dynamically based on network conditions, controlled using minPlaybackSpeed and maxPlaybackSpeed parameters.
val currentPlaybackSpeed = 1.0f
player.setPlaybackSpeed(currentPlaybackSpeed);
Considerations for Your App
- Memory Usage: Be cautious when playing multiple videos simultaneously.
- CPU Usage: Video decoding can be CPU-intensive.
- Coroutines: Use appropriate dispatchers for CPU or IO intensive tasks.
- Network: Streaming videos require a stable internet connection, whereas local videos don’t.
For sample details check my github repo https://github.com/basaransuleyman/ExoPlayerVideoAndroid

For Jetpack Compose Fully Projects
Jetpack Compose with Hexagonal and Clean Architecture
Multi Module( Layer Based)with Clean Architecture & MVVM+MVIÂ
Boost Your Android App with CPU Profiling
Boost Your Android App with and Mastering with Memory Profiling