0Pricing
Android Academy · Lesson

Playing Audio and Video

Simple media playback in Android.

Playing Audio and Video is a free Android Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Android Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Beyond Images: Media Playback

Coil shows images beautifully, but apps also play sound and video: a notification chime, a podcast, a background clip, a video feed. Android has dedicated APIs for media.

The modern recommended library is Media3 (ExoPlayer). It handles streaming, formats, buffering, and playback controls far better than the old MediaPlayer. In this lesson you'll play simple audio and video in a Compose app.

Adding Media3 Dependencies

Media3 is split into modules. For most apps you want media3-exoplayer (the player engine) and media3-ui (ready-made player views). For Compose video you can also use the PlayerView via AndroidView.

// build.gradle.kts (module level)
dependencies {
    implementation("androidx.media3:media3-exoplayer:1.5.0")
    implementation("androidx.media3:media3-ui:1.5.0")
}

Creating an ExoPlayer

An ExoPlayer is built from a Context. You give it a MediaItem (a URL or local file), call prepare(), and start playback. It runs its own threads, so you don't block the UI.

Crucially, a player holds system resources and must be released, more on that soon.

import android.content.Context
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer

fun createPlayer(context: Context, url: String): ExoPlayer {
    val player = ExoPlayer.Builder(context).build()
    player.setMediaItem(MediaItem.fromUri(url))
    player.prepare()
    player.playWhenReady = true
    return player
}

Playing Audio

For audio-only playback the setup is identical, you just point the MediaItem at an audio URL (mp3, aac, etc.). There is no view to show, so you control it through the player object: play(), pause(), seekTo().

This is enough to build a podcast or music player.

import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer

fun playPodcast(player: ExoPlayer, audioUrl: String) {
    player.setMediaItem(MediaItem.fromUri(audioUrl))
    player.prepare()
    player.play()
}

fun togglePause(player: ExoPlayer) {
    if (player.isPlaying) player.pause() else player.play()
}

Remembering the Player in Compose

In Compose you should create the player once and keep it across recompositions. Use remember so a new player isn't built on every recomposition.

Pair this with proper cleanup (next scene) so you don't leak resources.

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer

@Composable
fun rememberPlayer(url: String): ExoPlayer {
    val context = LocalContext.current
    return remember {
        ExoPlayer.Builder(context).build().apply {
            setMediaItem(MediaItem.fromUri(url))
            prepare()
        }
    }
}

Releasing the Player

A player keeps codecs, buffers, and audio focus alive. If you forget to release it you leak memory and may keep playing after the screen is gone. In Compose, DisposableEffect runs cleanup when the composable leaves the composition.

Always call player.release() there.

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.media3.exoplayer.ExoPlayer

@Composable
fun ManagedPlayer(player: ExoPlayer) {
    DisposableEffect(player) {
        onDispose {
            player.release()
        }
    }
    // ... use player here
}

Showing Video with PlayerView

Media3 provides a ready-made PlayerView with controls. Compose doesn't have a native video composable, so you embed the View using AndroidView and attach the player.

This gives you play/pause, a seek bar, and fullscreen for free.

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView

@Composable
fun VideoPlayer(player: ExoPlayer, modifier: Modifier = Modifier) {
    AndroidView(
        factory = { context ->
            PlayerView(context).apply {
                this.player = player
            }
        },
        modifier = modifier
    )
}

A Complete Video Screen

Let's tie it together: create the player, remember it, attach it to a PlayerView, and release it on dispose. This is a self-contained video screen you can reuse.

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView

@Composable
fun VideoScreen(videoUrl: String) {
    val context = LocalContext.current
    val player = remember {
        ExoPlayer.Builder(context).build().apply {
            setMediaItem(MediaItem.fromUri(videoUrl))
            prepare()
            playWhenReady = true
        }
    }
    DisposableEffect(Unit) { onDispose { player.release() } }

    AndroidView(
        factory = { PlayerView(it).apply { this.player = player } },
        modifier = Modifier.fillMaxWidth().height(220.dp)
    )
}

Tracking Playback State

To update your UI (a play/pause icon, a progress bar) you listen to the player with a Player.Listener. It reports state changes such as buffering, ready, and ended, plus whether playback is active.

Add the listener when the screen appears and remove it on dispose.

import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer

fun observe(player: ExoPlayer, onPlaying: (Boolean) -> Unit): Player.Listener {
    val listener = object : Player.Listener {
        override fun onIsPlayingChanged(isPlaying: Boolean) {
            onPlaying(isPlaying)
        }
        override fun onPlaybackStateChanged(state: Int) {
            when (state) {
                Player.STATE_BUFFERING -> { /* show spinner */ }
                Player.STATE_READY -> { /* ready */ }
                Player.STATE_ENDED -> { /* finished */ }
            }
        }
    }
    player.addListener(listener)
    return listener
}

Pausing When the App Goes to Background

For inline video (not a music app) you usually want to pause when the user leaves the screen and resume when they return. You can tie playback to the Compose lifecycle by observing the Lifecycle and pausing on ON_PAUSE.

This avoids playing video the user can't see and saves battery.

import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.media3.exoplayer.ExoPlayer

@Composable
fun PauseWithLifecycle(player: ExoPlayer) {
    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_PAUSE -> player.pause()
                Lifecycle.Event.ON_RESUME -> player.play()
                else -> {}
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
    }
}

Background Audio with MediaSession

For audio that should keep playing when the app is backgrounded (music, podcasts), wrap playback in a MediaSessionService. This integrates with system controls, the lock screen, and Bluetooth buttons.

It's more involved, but the core idea is exposing your player through a MediaSession.

import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService

class PlaybackService : MediaSessionService() {
    private var session: MediaSession? = null

    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        session = MediaSession.Builder(this, player).build()
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = session

    override fun onDestroy() {
        session?.run { player.release(); release() }
        session = null
        super.onDestroy()
    }
}

Quick Check

In a Compose screen that holds an ExoPlayer, which Compose API should you use to release the player when the screen leaves the composition?

Recap

You added audio and video playback:

  • Use Media3 ExoPlayer, the modern replacement for MediaPlayer.
  • Build a player, set a MediaItem, call prepare(), then play().
  • In Compose, remember the player and release it in DisposableEffect's onDispose.
  • Show video by embedding PlayerView with AndroidView.
  • Track state with a Player.Listener; use a MediaSessionService for background audio.

That completes the Images & Media Loading with Coil course. You can now load images smoothly and play media the modern Android way.

Frequently asked questions

Is the “Playing Audio and Video” lesson free?

Yes — the full text of “Playing Audio and Video” is free to read here on the web, and the Android Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Android Academy course, upgrade to CoddyKit PRO.

What will I learn in “Playing Audio and Video”?

Simple media playback in Android. You practise Android Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Android Academy?

No prior experience is required. Android Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Playing Audio and Video” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Android Academy lesson?

Yes. Every Android Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Loading Images with Coil
  2. Placeholders and Error States
  3. Caching and Performance
  4. Playing Audio and Video
← Back to Android Academy