Skip to main content

Media3 playback architecture

Jetpack Media3 supplies a consistent playback stack. ExoPlayer performs playback, MediaSession exposes it to system/external controllers, MediaSessionService owns background playback, and MediaController lets UI send commands.

Compose/View UI -> MediaController
↓ commands/state
MediaSessionService
↓
MediaSession
↓
Player

Keep the player out of composables​

class PlaybackService : MediaSessionService() {
private lateinit var player: ExoPlayer
private lateinit var session: MediaSession

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

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

override fun onDestroy() {
session.release()
player.release()
super.onDestroy()
}
}

For foreground-only video, an Activity/Fragment-scoped player may be enough. For music/podcast playback expected to continue with screen off or UI gone, a session service gives the correct ownership and system integration.

UI observes player state​

The UI should render controller state—current item, position, buffering, play state—and issue intents such as play/pause/seek. It should not own a parallel “isPlaying” boolean that drifts from the actual player.

Lifecycle decisions​

  • Decide whether leaving a screen pauses video or only detaches its surface.
  • Release expensive resources when product playback ends.
  • Restore queue/item/position from durable state where expected; never assume the service lived forever.
  • Avoid polling position too frequently; update display at a reasonable cadence while playing.
  • Handle audio becoming noisy (headphones disconnected), interruptions, and playback errors explicitly.

Network and caching​

Streaming needs timeouts, retry policy, seek/range support, and clear errors. Media cache needs size limits and eviction. DRM, downloaded/offline media, and signed URLs introduce license/expiry/security lifecycles separate from the UI.

A player is not application state

Persist business-relevant resume position and queue identity intentionally. Player memory can disappear with process death, and callbacks may arrive during lifecycle transitions.

Sources​

Exercise​

Design a podcast player with queue restoration, offline episodes, 30-second skip, Bluetooth controls, process-death recovery, and expired download licenses. Identify service, session, controller, database, and cache ownership.