Android Coden
Android 4 min lesen

Media Sessions

Media Sessions verbinden deine App mit Androids Systemsteuerungen und externen Geräten. Du lernst, wie Playback-Controls korrekt integriert werden.

Wer eine Musik- oder Podcast-App entwickelt, stößt früh auf eine zentrale Anforderung: Die App soll auf Steuerungsbefehle reagieren, die nicht aus der eigenen UI kommen – sei es der Sperr-Bildschirm, ein Bluetooth-Headset, eine Smartwatch oder ein Auto-Display. Media Sessions sind die standardisierte Brücke, die genau das ermöglicht, und sie sind kein optionaler Komfort, sondern die offizielle Schnittstelle des Betriebssystems für alle Medienintegrationspunkte.

Was ist das?

Eine Media Session ist ein Protokoll, mit dem eine Android-App ihren Wiedergabe-Zustand dem Betriebssystem mitteilt und im Gegenzug Steuerungsbefehle von außen entgegennimmt. Das System weiß dadurch jederzeit, welche App aktiv spielt, welcher Titel läuft und welche Aktionen – Pause, Weiter, Skip – gerade erlaubt sind.

Technisch bildet MediaSession aus der Jetpack-Bibliothek Media3 das Herzstück. Wichtig: Die Session ist kein Audio-Player. Der eigentliche Ton läuft in einem ExoPlayer; die Session ist nur der Metadaten- und Steuerungs-Kanal, den Android nach außen sieht.

Eine aktive Media Session ist der Schlüssel zu mehreren Systemintegrationen gleichzeitig:

  • Lock-Screen-Controls – Wiedergabe-Buttons direkt auf dem gesperrten Display.
  • Medien-Notification – Der persistente Player in der Benachrichtigungsleiste und im Schnelleinstellungsbereich.
  • Externe Geräte – Bluetooth-Headsets, Android Auto, Wear OS reagieren auf Play/Pause/Skip.
  • Sprachassistenten – Google Assistant kann deine App steuern, sofern eine Session registriert ist.

Ohne Media Session sind all diese Eintrittspunkte blind für deine App.

Wie funktioniert es?

Eine Media Session besteht aus drei zusammenhängenden Teilen: Erstellen, Metadaten pflegen, Callbacks verarbeiten.

Session erstellen und aktivieren

val player = ExoPlayer.Builder(context).build()
val session = MediaSession.Builder(context, player).build()

Mit Media3 übergibst du dem Builder direkt eine Player-Instanz. Die Session übernimmt von da an die Synchronisierung von Zustand und Steuerungsbefehlen automatisch.

Metadaten setzen

Das System zeigt nur das an, was du ihm mitteilst. Mediendaten hinterlegst du pro MediaItem:

val mediaItem = MediaItem.Builder()
    .setMediaId("track-42")
    .setMediaMetadata(
        MediaMetadata.Builder()
            .setTitle("Mein Titel")
            .setArtist("Künstlername")
            .setArtworkUri(artworkUri)
            .build()
    )
    .build()
player.setMediaItem(mediaItem)
player.prepare()
player.play()

PlaybackState (spielend, pausiert, gepuffert) leitet Media3 direkt vom ExoPlayer-Zustand ab – du musst ihn nicht manuell setzen.

Steuerungsbefehle und Custom Commands

Standardaktionen wie Play, Pause und Skip leitet die Session automatisch an den Player weiter. Eigene Aktionen – etwa „Zu Favoriten hinzufügen” – registrierst du als SessionCommand:

val session = MediaSession.Builder(context, player)
    .setCallback(object : MediaSession.Callback {
        override fun onCustomCommand(
            session: MediaSession,
            controller: MediaSession.ControllerInfo,
            customCommand: SessionCommand,
            args: Bundle
        ): ListenableFuture<SessionResult> {
            if (customCommand.customAction == "ADD_FAVORITE") {
                addToFavorites(session.player.currentMediaItem)
            }
            return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
        }
    })
    .build()

In der Praxis

Die empfohlene Architektur lässt den Player und die Session in einem MediaSessionService laufen – einem Foreground Service, der aktiv bleibt, auch wenn die UI geschlossen ist.

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) = session

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

Die UI verbindet sich dann über einen MediaController und spiegelt den Zustand in Compose-State. Der Controller läuft auf dem Main Thread und liefert StateFlow-kompatible Updates.

Stolperfalle 1 – Session nicht freigeben

Wer session.release() in onDestroy() vergisst, hinterlässt eine Zombie-Session. Android zeigt dann veraltete Steuerungselemente auf dem Lock-Screen, die keine Reaktion mehr zeigen. Nutzer erleben eingefrorene Controls; der eigentliche Fehler liegt im Service-Lifecycle.

Stolperfalle 2 – Fehlender Intent-Filter im Manifest

Ohne den korrekten <intent-filter> findet Android Auto, Wear OS oder der Bluetooth-Stack den Service nicht. Die Deklaration muss exakt so lauten:

<service
    android:name=".PlaybackService"
    android:exported="true"
    android:foregroundServiceType="mediaPlayback">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService" />
    </intent-filter>
</service>

Fehlt diese Zeile, kompiliert die App problemlos, aber externe Geräte können die Session schlicht nicht finden – ein Fehler, der im eigenen Emulator oft unbemerkt bleibt und erst beim Test mit echtem Zubehör auffällt.

Fazit

Media Sessions sind die Pflichtschnittstelle für jede Medien-App, die über die eigene UI hinausgehen soll. Sie verbinden Player-Zustand und Metadaten mit Lock-Screen, Benachrichtigungen und externen Geräten – und das alles über einen einzigen, klar definierten Kanal. Überprüfe deine eigene App: Öffne den Schnelleinstellungsbereich, während Musik läuft – erscheint dort der Media-Player-Eintrag? Verbinde anschließend ein Bluetooth-Headset und prüfe, ob Play und Pause korrekt weitergeleitet werden. Schreibe außerdem einen einfachen Instrumentierungstest, der einen MediaController mit deinem PlaybackService verbindet, einen play()-Befehl sendet und dann den PlaybackState verifiziert. Nur wer diese Integrationspunkte aktiv testet, bemerkt rechtzeitig, wenn ein Manifest-Eintrag oder ein release()-Aufruf fehlt.

Quellen (3)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.