DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Media3 (ExoPlayer) + Compose: Video & Audio Player Implementation

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()
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)
)
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

Proper Resource Release with DisposableEffect

Always release the player when the Compose component is disposed:

DisposableEffect(Unit) {
    onDispose {
        exoPlayer.release()
    }
}
Enter fullscreen mode Exit fullscreen mode

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}")
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Always use DisposableEffect to 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)