Hintergrundwiedergabe mit MediaSessionService

Es ist oft wünschenswert, Medien abzuspielen, während eine App nicht im Vordergrund ist. Beispielsweise wird bei einem Musikplayer in der Regel Musik abgespielt, wenn der Nutzer sein Gerät gesperrt hat oder eine andere App verwendet. Die Media3-Bibliothek bietet eine Reihe von Schnittstellen, mit denen Sie die Hintergrundwiedergabe unterstützen können.

MediaSessionService verwenden

Wenn du die Hintergrundwiedergabe aktivieren möchtest, solltest du Player und MediaSession in einem separaten Dienst einfügen. So kann das Gerät auch dann Medien bereitstellen, wenn Ihre App nicht im Vordergrund ist.

Mit dem MediaSessionService kann die Mediensitzung unabhängig von der Aktivität der App ausgeführt werden.
Abbildung 1: Mit der MediaSessionService kann die Mediensitzung unabhängig von der Aktivität der App ausgeführt werden.

Wenn du einen Player in einem Dienst hostest, solltest du einen MediaSessionService verwenden. Erstelle dazu eine Klasse, die MediaSessionService erweitert, und erstelle darin deine Mediensitzung.

Mit MediaSessionService können externe Clients wie Google Assistant, die Mediensteuerung des Systems, Medienschaltflächen auf Peripheriegeräten oder Companion-Geräte wie Wear OS deinen Dienst finden, eine Verbindung dazu herstellen und die Wiedergabe steuern, ohne auf die UI-Aktivitäten deiner App zuzugreifen. Tatsächlich können mehrere Client-Apps gleichzeitig mit derselben MediaSessionService verbunden sein, wobei jede App eine eigene MediaController hat.

Dienstlebenszyklus implementieren

Sie müssen zwei Lebenszyklusmethoden für Ihren Dienst implementieren:

  • onCreate() wird aufgerufen, wenn der erste Controller eine Verbindung herstellen soll und der Dienst instanziiert und gestartet wird. Das ist der beste Ort, um Player und MediaSession zu entwickeln.
  • onDestroy() wird aufgerufen, wenn der Dienst beendet wird. Alle Ressourcen, einschließlich Player und Sitzung, müssen freigegeben werden.

Sie können onTaskRemoved(Intent) optional überschreiben, um anzupassen, was passiert, wenn der Nutzer die App aus den letzten Aufgaben schließt. Standardmäßig bleibt der Dienst aktiv, wenn die Wiedergabe läuft, andernfalls wird er beendet.

Kotlin

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

  // Create your player and media session in the onCreate lifecycle event
  override fun onCreate() {
    super.onCreate()
    val player = ExoPlayer.Builder(this).build()
    mediaSession = MediaSession.Builder(this, player).build()
  }

  // Remember to release the player and media session in onDestroy
  override fun onDestroy() {
    mediaSession?.run {
      player.release()
      release()
      mediaSession = null
    }
    super.onDestroy()
  }
}

Java

public class PlaybackService extends MediaSessionService {
  private MediaSession mediaSession = null;

  // Create your Player and MediaSession in the onCreate lifecycle event
  @Override
  public void onCreate() {
    super.onCreate();
    ExoPlayer player = new ExoPlayer.Builder(this).build();
    mediaSession = new MediaSession.Builder(this, player).build();
  }

  // Remember to release the player and media session in onDestroy
  @Override
  public void onDestroy() {
    mediaSession.getPlayer().release();
    mediaSession.release();
    mediaSession = null;
    super.onDestroy();
  }
}

Anstatt die Wiedergabe im Hintergrund fortzusetzen, kannst du den Dienst auch in jedem Fall beenden, wenn der Nutzer die App schließt:

Kotlin

override fun onTaskRemoved(rootIntent: Intent?) {
  pauseAllPlayersAndStopSelf()
}

Java

@Override
public void onTaskRemoved(@Nullable Intent rootIntent) {
  pauseAllPlayersAndStopSelf();
}

Bei jeder anderen manuellen Implementierung von onTaskRemoved kannst du mit isPlaybackOngoing() prüfen, ob die Wiedergabe als fortlaufend betrachtet wird und der Dienst im Vordergrund gestartet wird.

Zugriff auf die Mediensitzung gewähren

Überschreiben Sie die onGetSession()-Methode, um anderen Clients Zugriff auf Ihre Mediensitzung zu gewähren, die beim Erstellen des Dienstes erstellt wurde.

Kotlin

class PlaybackService : MediaSessionService() {
  private var mediaSession: MediaSession? = null
  // [...] lifecycle methods omitted

  override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
    mediaSession
}

Java

public class PlaybackService extends MediaSessionService {
  private MediaSession mediaSession = null;
  // [...] lifecycle methods omitted

  @Override
  public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
    return mediaSession;
  }
}

Dienst im Manifest deklarieren

Für eine App sind die Berechtigungen FOREGROUND_SERVICE und FOREGROUND_SERVICE_MEDIA_PLAYBACK erforderlich, um einen Dienst im Vordergrund für die Wiedergabe auszuführen:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

Außerdem müssen Sie Ihre Service-Klasse im Manifest mit einem Intent-Filter von MediaSessionService und einer foregroundServiceType deklarieren, die mediaPlayback enthält.

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

Wiedergabe mit MediaController steuern

In der Aktivität oder dem Fragment, das die Player-UI enthält, kannst du mithilfe einer MediaController eine Verknüpfung zwischen der UI und deiner Mediensitzung herstellen. Über den Mediencontroller sendet deine Benutzeroberfläche Befehle von der Benutzeroberfläche an den Player innerhalb der Sitzung. Weitere Informationen zum Erstellen und Verwenden von MediaController finden Sie im Leitfaden MediaController erstellen.

MediaController-Befehle verarbeiten

Die MediaSession empfängt Befehle vom Controller über ihre MediaSession.Callback. Wenn du einen MediaSession initialisierst, wird eine Standardimplementierung von MediaSession.Callback erstellt, die automatisch alle Befehle verarbeitet, die ein MediaController an deinen Player sendet.

Benachrichtigung

Mit einer MediaSessionService wird automatisch eine MediaNotification für Sie erstellt, die in den meisten Fällen funktionieren sollte. Standardmäßig ist die veröffentlichte Benachrichtigung eine MediaStyle-Benachrichtigung, die immer mit den neuesten Informationen aus Ihrer Mediensitzung aktualisiert wird und Wiedergabesteuerungen enthält. Der MediaNotification ist sich deiner Sitzung bewusst und kann verwendet werden, um die Wiedergabe für alle anderen Apps zu steuern, die mit derselben Sitzung verbunden sind.

In einer Musik-Streaming-App mit einer MediaSessionService wird beispielsweise ein MediaNotification erstellt, in dem der Titel, der Künstler und das Albumcover des aktuell wiedergegebenen Medienelements sowie die Wiedergabesteuerung basierend auf der MediaSession-Konfiguration angezeigt werden.

Die erforderlichen Metadaten können in den Medien bereitgestellt oder als Teil des Medienelements deklariert werden, wie im folgenden Snippet:

Kotlin

val mediaItem =
    MediaItem.Builder()
      .setMediaId("media-1")
      .setUri(mediaUri)
      .setMediaMetadata(
        MediaMetadata.Builder()
          .setArtist("David Bowie")
          .setTitle("Heroes")
          .setArtworkUri(artworkUri)
          .build()
      )
      .build()

mediaController.setMediaItem(mediaItem)
mediaController.prepare()
mediaController.play()

Java

MediaItem mediaItem =
    new MediaItem.Builder()
        .setMediaId("media-1")
        .setUri(mediaUri)
        .setMediaMetadata(
            new MediaMetadata.Builder()
                .setArtist("David Bowie")
                .setTitle("Heroes")
                .setArtworkUri(artworkUri)
                .build())
        .build();

mediaController.setMediaItem(mediaItem);
mediaController.prepare();
mediaController.play();

Benachrichtigungslebenszyklus

Die Benachrichtigung wird erstellt, sobald die Player MediaItem Instanzen in ihrer Playlist hat.

Alle Benachrichtigungsaktualisierungen erfolgen automatisch basierend auf dem Status Player und MediaSession.

Die Benachrichtigung kann nicht entfernt werden, während der Dienst im Vordergrund ausgeführt wird. Wenn Sie die Benachrichtigung sofort entfernen möchten, müssen Sie Player.release() aufrufen oder die Playlist mit Player.clearMediaItems() löschen.

Wenn der Player länger als 10 Minuten lang pausiert, angehalten oder fehlgeschlagen ist, ohne dass es weitere Nutzerinteraktionen gibt, wird der Dienst automatisch aus dem Dienststatus im Vordergrund entfernt, damit er vom System gelöscht werden kann. Du kannst die Wiedergabe fortsetzen, damit Nutzer den Dienstzyklus neu starten und die Wiedergabe zu einem späteren Zeitpunkt fortsetzen können.

Benachrichtigungen anpassen

Die Metadaten zum gerade wiedergegebenen Element können angepasst werden, indem du die MediaItem.MediaMetadata änderst. Wenn du die Metadaten eines vorhandenen Elements aktualisieren möchtest, kannst du Player.replaceMediaItem verwenden, um die Metadaten zu aktualisieren, ohne die Wiedergabe zu unterbrechen.

Sie können auch einige der in der Benachrichtigung angezeigten Schaltflächen anpassen, indem Sie benutzerdefinierte Einstellungen für die Medienschaltflächen für die Android-Mediensteuerung festlegen. Weitere Informationen zum Anpassen der Mediensteuerung unter Android

Wenn Sie die Benachrichtigung weiter anpassen möchten, erstellen Sie eine MediaNotification.Provider mit DefaultMediaNotificationProvider.Builder oder eine benutzerdefinierte Implementierung der Anbieteroberfläche. Fügen Sie Ihren Anbieter mit setMediaNotificationProvider zu MediaSessionService hinzu.

Wiedergabe fortsetzen

Nach der Beendigung der MediaSessionService und auch nach dem Neustart des Geräts kann die Wiedergabe fortgesetzt werden, damit Nutzer den Dienst neu starten und die Wiedergabe an der Stelle fortsetzen können, an der sie unterbrochen wurde. Die Wiedergabefortsetzung ist standardmäßig deaktiviert. Das bedeutet, dass Nutzer die Wiedergabe nicht fortsetzen können, wenn dein Dienst nicht ausgeführt wird. Wenn Sie diese Funktion aktivieren möchten, müssen Sie einen Empfänger für Medienschaltflächen deklarieren und die Methode onPlaybackResumption implementieren.

Empfänger für Medienschaltflächen vom Typ „Media3“ deklarieren

Deklarieren Sie zuerst das MediaButtonReceiver in Ihrem Manifest:

<receiver android:name="androidx.media3.session.MediaButtonReceiver"
  android:exported="true">
  <intent-filter>
    <action android:name="android.intent.action.MEDIA_BUTTON" />
  </intent-filter>
</receiver>

Callback für die Wiedergabefortsetzung implementieren

Wenn die Wiedergabe entweder von einem Bluetooth-Gerät oder über die Wiedergabefunktion der Android-System-UI fortgesetzt werden soll, wird die Rückrufmethode onPlaybackResumption() aufgerufen.

Kotlin

override fun onPlaybackResumption(
    mediaSession: MediaSession,
    controller: ControllerInfo
): ListenableFuture<MediaItemsWithStartPosition> {
  val settable = SettableFuture.create<MediaItemsWithStartPosition>()
  scope.launch {
    // Your app is responsible for storing the playlist and the start position
    // to use here
    val resumptionPlaylist = restorePlaylist()
    settable.set(resumptionPlaylist)
  }
  return settable
}

Java

@Override
public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption(
    MediaSession mediaSession,
    ControllerInfo controller
) {
  SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create();
  settableFuture.addListener(() -> {
    // Your app is responsible for storing the playlist and the start position
    // to use here
    MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist();
    settableFuture.set(resumptionPlaylist);
  }, MoreExecutors.directExecutor());
  return settableFuture;
}

Wenn du andere Parameter wie Wiedergabegeschwindigkeit, Wiederholungsmodus oder Zufallsmix gespeichert hast, kannst du den Player mit diesen Parametern in onPlaybackResumption() konfigurieren, bevor Media3 den Player vorbereitet und die Wiedergabe startet, sobald der Rückruf abgeschlossen ist.

Erweiterte Controllerkonfiguration und Abwärtskompatibilität

Ein gängiges Szenario ist die Verwendung eines MediaController in der App-Benutzeroberfläche zur Steuerung der Wiedergabe und zum Anzeigen der Playlist. Gleichzeitig ist die Sitzung für externe Clients wie die Android-Mediensteuerung und Assistant auf Mobilgeräten oder Fernsehern, Wear OS für Smartwatches und Android Auto in Autos verfügbar. Die Media3-Demoanwendung für Sitzungen ist ein Beispiel für eine App, die ein solches Szenario implementiert.

Diese externen Clients können APIs wie MediaControllerCompat der alten AndroidX-Bibliothek oder android.media.session.MediaController der Android-Plattform verwenden. Media3 ist vollständig abwärtskompatibel mit der alten Bibliothek und bietet Interoperabilität mit der Android-Plattform-API.

Medienbenachrichtigungssteuerung verwenden

Diese alten und Plattform-Controller haben denselben Status und die Sichtbarkeit kann nicht für jeden Controller individuell angepasst werden (z. B. PlaybackState.getActions() und PlaybackState.getCustomActions()). Mit dem Controller für Medienbenachrichtigungen kannst du den in der Plattformmediensitzung festgelegten Status für die Kompatibilität mit diesen alten und Plattform-Controllern konfigurieren.

Eine App kann beispielsweise eine Implementierung von MediaSession.Callback.onConnect() bereitstellen, um verfügbare Befehle und Einstellungen für Medienschaltflächen speziell für die Plattformsitzung festzulegen. Dazu wird Folgendes ausgeführt:

Kotlin

override fun onConnect(
  session: MediaSession,
  controller: MediaSession.ControllerInfo
): ConnectionResult {
  if (session.isMediaNotificationController(controller)) {
    val sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(customCommandSeekBackward)
        .add(customCommandSeekForward)
        .build()
    val playerCommands =
      ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
        .remove(COMMAND_SEEK_TO_PREVIOUS)
        .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
        .remove(COMMAND_SEEK_TO_NEXT)
        .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
        .build()
    // Custom button preferences and commands to configure the platform session.
    return AcceptedResultBuilder(session)
      .setMediaButtonPreferences(
        ImmutableList.of(
          createSeekBackwardButton(customCommandSeekBackward),
          createSeekForwardButton(customCommandSeekForward))
      )
      .setAvailablePlayerCommands(playerCommands)
      .setAvailableSessionCommands(sessionCommands)
      .build()
  }
  // Default commands with default button preferences for all other controllers.
  return AcceptedResultBuilder(session).build()
}

Java

@Override
public ConnectionResult onConnect(
    MediaSession session, MediaSession.ControllerInfo controller) {
  if (session.isMediaNotificationController(controller)) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS
            .buildUpon()
            .add(customCommandSeekBackward)
            .add(customCommandSeekForward)
            .build();
    Player.Commands playerCommands =
        ConnectionResult.DEFAULT_PLAYER_COMMANDS
            .buildUpon()
            .remove(COMMAND_SEEK_TO_PREVIOUS)
            .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
            .remove(COMMAND_SEEK_TO_NEXT)
            .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
            .build();
    // Custom button preferences and commands to configure the platform session.
    return new AcceptedResultBuilder(session)
        .setMediaButtonPreferences(
            ImmutableList.of(
                createSeekBackwardButton(customCommandSeekBackward),
                createSeekForwardButton(customCommandSeekForward)))
        .setAvailablePlayerCommands(playerCommands)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
  // Default commands with default button preferences for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

Android Auto zum Senden benutzerdefinierter Befehle autorisieren

Wenn du einen MediaLibraryService verwendest und Android Auto mit der mobilen App unterstützen möchtest, benötigt der Android Auto-Controller entsprechende verfügbare Befehle. Andernfalls lehnt Media3 eingehende benutzerdefinierte Befehle von diesem Controller ab:

Kotlin

override fun onConnect(
  session: MediaSession,
  controller: MediaSession.ControllerInfo
): ConnectionResult {
  val sessionCommands =
    ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
      .add(customCommandSeekBackward)
      .add(customCommandSeekForward)
      .build()
  if (session.isMediaNotificationController(controller)) {
    // [...] See above.
  } else if (session.isAutoCompanionController(controller)) {
    // Available session commands to accept incoming custom commands from Auto.
    return AcceptedResultBuilder(session)
      .setAvailableSessionCommands(sessionCommands)
      .build()
  }
  // Default commands for all other controllers.
  return AcceptedResultBuilder(session).build()
}

Java

@Override
public ConnectionResult onConnect(
    MediaSession session, MediaSession.ControllerInfo controller) {
  SessionCommands sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS
          .buildUpon()
          .add(customCommandSeekBackward)
          .add(customCommandSeekForward)
          .build();
  if (session.isMediaNotificationController(controller)) {
    // [...] See above.
  } else if (session.isAutoCompanionController(controller)) {
    // Available commands to accept incoming custom commands from Auto.
    return new AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
  // Default commands for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

Die Demo-App für die Sitzung enthält ein Automotive-Modul, das die Unterstützung für Automotive OS demonstriert, für das ein separates APK erforderlich ist.