What You'll Learn
Media3 + Compose (ExoPlayer, video player UI, music player, PiP support, media notification) explained.
Setup
dependencies {
implementation("androidx.media3:media3-exoplayer:1.5.1")
implementation("androidx.media3:media3-ui-compose:1.5.1")
implementation("androidx.media3:media3-session:1.5.1")
}
Video Player
@Composable
fun VideoPlayer(uri: String) {
val context = LocalContext.current
val player = remember {
ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(uri))
prepare()
}
}
DisposableEffect(Unit) {
onDispose { player.release() }
}
AndroidView(
factory = { ctx ->
PlayerView(ctx).apply {
this.player = player
useController = true
}
},
modifier = Modifier.fillMaxWidth().aspectRatio(16f / 9f)
)
}
Custom Controller
@Composable
fun CustomVideoPlayer(uri: String) {
val context = LocalContext.current
val player = remember {
ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(uri))
prepare()
}
}
var isPlaying by remember { mutableStateOf(false) }
var progress by remember { mutableFloatStateOf(0f) }
var duration by remember { mutableLongStateOf(0L) }
LaunchedEffect(player) {
while (true) {
isPlaying = player.isPlaying
progress = if (player.duration > 0) player.currentPosition.toFloat() / player.duration else 0f
duration = player.duration
delay(200)
}
}
DisposableEffect(Unit) { onDispose { player.release() } }
Column {
AndroidView(
factory = { PlayerView(it).apply { this.player = player; useController = false } },
modifier = Modifier.fillMaxWidth().aspectRatio(16f / 9f)
)
Row(Modifier.fillMaxWidth().padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
IconButton(onClick = { if (isPlaying) player.pause() else player.play() }) {
Icon(if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow, "Play/Pause")
}
Slider(
value = progress,
onValueChange = { player.seekTo((it * duration).toLong()) },
modifier = Modifier.weight(1f)
)
Text(formatTime(player.currentPosition), style = MaterialTheme.typography.labelSmall)
}
}
}
fun formatTime(ms: Long): String {
val seconds = (ms / 1000) % 60
val minutes = (ms / 1000 / 60) % 60
return "%d:%02d".format(minutes, seconds)
}
Music Player (MediaSession)
class MusicService : MediaSessionService() {
private var mediaSession: MediaSession? = null
override fun onCreate() {
super.onCreate()
val player = ExoPlayer.Builder(this).build()
mediaSession = MediaSession.Builder(this, player).build()
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession
override fun onDestroy() {
mediaSession?.run {
player.release()
release()
}
super.onDestroy()
}
}
PiP (Picture-in-Picture)
@Composable
fun PipVideoPlayer(uri: String) {
val context = LocalContext.current
val activity = context as Activity
Button(onClick = {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.enterPictureInPictureMode(
PictureInPictureParams.Builder()
.setAspectRatio(Rational(16, 9))
.build()
)
}
}) { Text("PiP Mode") }
VideoPlayer(uri)
}
Summary
| Feature | Implementation |
|---|---|
| Video player |
ExoPlayer + PlayerView
|
| Custom UI |
AndroidView + Compose |
| Music service | MediaSessionService |
| PiP | enterPictureInPictureMode |
| Notification | MediaSession |
-
ExoPlayerfor video/audio playback -
AndroidViewintegrates PlayerView into Compose -
MediaSessionauto-generates media notification - PiP for background viewing
Ready-Made Android App Templates
8 production-ready Android app templates with Jetpack Compose, MVVM, Hilt, and Material 3.
Browse templates → Gumroad
Top comments (0)