Media3 (ExoPlayer) + Compose: Video & Audio Player Implementation
Media3 (formerly ExoPlayer) is Google's recommended library for audio and video playback on Android. Integrating it with Jetpack Compose requires combining AndroidView, lifecycle management, and proper state handling.
ExoPlayer.Builder Setup
Create and configure an ExoPlayer instance:
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
val exoPlayer = ExoPlayer.Builder(context)
.setTrackSelector(DefaultTrackSelector(context))
.build()
Loading MediaItems
Add one or multiple media items to play:
import androidx.media3.common.MediaItem
import androidx.media3.common.C
// Single video
val mediaItem = MediaItem.Builder()
.setUri("https://example.com/video.mp4")
.setMimeType(MimeTypes.APPLICATION_MP4)
.build()
exoPlayer.setMediaItem(mediaItem)
// Playlist with multiple items
val playlist = listOf(
MediaItem.fromUri("https://example.com/video1.mp4"),
MediaItem.fromUri("https://example.com/video2.mp4"),
MediaItem.fromUri("https://example.com/audio.mp3")
)
exoPlayer.setMediaItems(playlist)
exoPlayer.prepare()
PlayerView in AndroidView
Embed PlayerView in Compose using AndroidView:
import androidx.media3.ui.PlayerView
import androidx.compose.ui.viewinterop.AndroidView
AndroidView(
factory = { context ->
PlayerView(context).apply {
player = exoPlayer
useController = true
controllerShowTimeoutMs = 5000
controllerHideTimeoutMs = 3000
}
},
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
)
Custom Audio Player UI in Compose
Build a custom player UI for audio without video:
val currentPosition by exoPlayer.currentPosition.collectAsState()
val duration by exoPlayer.duration.collectAsState()
val isPlaying by exoPlayer.isPlaying.collectAsState()
Column(modifier = Modifier.padding(16.dp)) {
// Play/Pause Button
Button(onClick = {
if (isPlaying) exoPlayer.pause() else exoPlayer.play()
}) {
Text(if (isPlaying) "⏸ Pause" else "▶ Play")
}
// Progress Slider
Slider(
value = currentPosition.toFloat(),
onValueChange = { exoPlayer.seekTo(it.toLong()) },
valueRange = 0f..duration.toFloat(),
modifier = Modifier.fillMaxWidth()
)
// Time Display
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Text(formatTime(currentPosition))
Text(formatTime(duration))
}
}
fun formatTime(ms: Long): String {
val seconds = (ms / 1000) % 60
val minutes = (ms / 60000) % 60
return "%02d:%02d".format(minutes, seconds)
}
Lifecycle-Aware Playback
Pause playback when app goes to background:
DisposableEffect(Unit) {
val lifecycleObserver = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> exoPlayer.play()
Lifecycle.Event.ON_PAUSE -> exoPlayer.pause()
else -> {}
}
}
val lifecycle = LocalLifecycleOwner.current.lifecycle
lifecycle.addObserver(lifecycleObserver)
onDispose {
lifecycle.removeObserver(lifecycleObserver)
}
}
Proper Resource Release with DisposableEffect
Always release the player when the Compose component is disposed:
DisposableEffect(Unit) {
onDispose {
exoPlayer.release()
}
}
Playlist Management
Control playback flow within a playlist:
// Move to next item
Button(onClick = { exoPlayer.seekToNextMediaItem() }) {
Text("Next Track")
}
// Move to previous item
Button(onClick = { exoPlayer.seekToPreviousMediaItem() }) {
Text("Previous Track")
}
// Repeat modes
IconButton(onClick = {
exoPlayer.repeatMode = when (exoPlayer.repeatMode) {
Player.REPEAT_MODE_OFF -> Player.REPEAT_MODE_ALL
Player.REPEAT_MODE_ALL -> Player.REPEAT_MODE_ONE
else -> Player.REPEAT_MODE_OFF
}
}) {
Text("Repeat: ${exoPlayer.repeatMode}")
}
Best Practices
- Always use
DisposableEffectto release player resources - Pause playback when app goes to background to save battery
- Use lifecycle observers for proper state management
- Handle network errors and fallback to local playback
- Test with various media formats (MP4, WebM, DASH, HLS)
- Provide users with playback controls and progress indication
Media3 provides a robust foundation for audio and video playback. Combined with Compose's declarative UI, you can build modern media experiences with minimal boilerplate.
Explore more Android development patterns: 8 Android App Templates → https://myougatheax.gumroad.com
Top comments (0)