Hintergrundwiedergabe mit MediaSessionService

Es ist oft wünschenswert, Medien abzuspielen, während eine App nicht im Vordergrund ausgeführt wird. Ein Musikplayer spielt beispielsweise in der Regel Musik weiter ab, wenn der Nutzer sein Gerät gesperrt hat oder eine andere App verwendet. Die Media3-Bibliothek bietet eine Reihe von Schnittstellen, mit denen du die Hintergrundwiedergabe unterstützen kannst.

MediaSessionService verwenden

Damit die Hintergrundwiedergabe aktiviert werden kann, sollten Sie Player und MediaSession in einem separaten Service unterbringen. So kann das Gerät weiterhin Medien bereitstellen, auch wenn Ihre App nicht im Vordergrund ausgeführt wird.

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

Wenn Sie einen Player in einem Dienst hosten, sollten Sie ein MediaSessionService verwenden. Erstellen Sie dazu eine Klasse, die MediaSessionService erweitert, und erstellen Sie darin Ihre Mediensitzung.

Durch die Verwendung von MediaSessionService können externe Clients wie Google Assistant, System-Media-Steuerelemente, Media-Schaltflächen auf Peripheriegeräten oder Companion-Geräte wie Wear OS Ihren Dienst erkennen, eine Verbindung zu ihm herstellen und die Wiedergabe steuern, ohne auf die UI-Aktivität Ihrer App zuzugreifen. Tatsächlich können mehrere Client-Apps gleichzeitig mit demselben MediaSessionService verbunden sein, wobei jede App einen eigenen MediaController hat.

Dienstlebenszyklus implementieren

Sie müssen zwei Lebenszyklusmethoden Ihres Dienstes implementieren:

  • onCreate() wird aufgerufen, wenn der erste Controller verbunden wird und der Dienst instanziiert und gestartet wird. Hier können Sie Player und MediaSession am besten entwickeln.
  • onDestroy() wird aufgerufen, wenn der Dienst beendet wird. Alle Ressourcen, einschließlich Player und Sitzung, müssen freigegeben werden.

Optional können Sie onTaskRemoved(Intent) überschreiben, um anzupassen, was passiert, wenn der Nutzer die App aus den letzten Aufgaben schließt. Standardmäßig wird der Dienst weiter ausgeführt, wenn die Wiedergabe läuft, und andernfalls 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();
  }
}

Alternativ dazu, die Wiedergabe im Hintergrund fortzusetzen, können Sie den Dienst 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 können Sie mit isPlaybackOngoing() prüfen, ob die Wiedergabe als laufend betrachtet wird und der Vordergrunddienst gestartet wurde.

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 einem foregroundServiceType deklarieren, das 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 über eine MediaController steuern

In der Aktivität oder dem Fragment, das die Player-UI enthält, können Sie mit einem MediaController eine Verbindung zwischen der UI und der Mediensitzung herstellen. Über die Benutzeroberfläche werden mit dem Media-Controller Befehle von der Benutzeroberfläche an den Player in der Sitzung gesendet. Weitere Informationen zum Erstellen und Verwenden eines MediaController finden Sie im Leitfaden zum Erstellen eines MediaController.

MediaController-Befehle verarbeiten

Das MediaSession empfängt Befehle vom Controller über das MediaSession.Callback. Durch die Initialisierung von MediaSession wird eine Standardimplementierung von MediaSession.Callback erstellt, die automatisch alle Befehle verarbeitet, die ein MediaController an Ihren Player sendet.

Benachrichtigung

Bei einem MediaSessionService wird automatisch ein MediaNotification für Sie erstellt, das in den meisten Fällen funktionieren sollte. Standardmäßig ist die veröffentlichte Benachrichtigung eine MediaStyle-Benachrichtigung, die mit den neuesten Informationen aus deiner Mediensitzung aktualisiert wird und Wiedergabesteuerelemente anzeigt. Das MediaNotification kennt deine Sitzung und kann verwendet werden, um die Wiedergabe für alle anderen Apps zu steuern, die mit derselben Sitzung verbunden sind.

Eine Musikstreaming-App, die MediaSessionService verwendet, würde beispielsweise eine MediaNotification erstellen, in der der Titel, der Künstler und das Albumcover des aktuellen Media-Elements angezeigt werden. Außerdem würden Wiedergabesteuerelemente auf Grundlage Ihrer MediaSession-Konfiguration angezeigt.

Die erforderlichen Metadaten können in den Media bereitgestellt oder als Teil des Media-Elements 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 Playlist MediaItem Player enthält.

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

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

Wenn der Player länger als 10 Minuten pausiert, gestoppt oder fehlgeschlagen ist, ohne dass weitere Nutzerinteraktionen erfolgen, wird der Dienst automatisch aus dem Vordergrunddienststatus entfernt, sodass er vom System beendet werden kann. Du kannst die Wiedergabe fortsetzen, damit ein Nutzer den Dienstlebenszyklus neu starten und die Wiedergabe zu einem späteren Zeitpunkt fortsetzen kann.

Benachrichtigungen anpassen

Die Metadaten zum aktuell wiedergegebenen Element können durch Ändern der MediaItem.MediaMetadata angepasst werden. Wenn Sie die Metadaten eines vorhandenen Elements aktualisieren möchten, können Sie 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 Media-Schaltflächen für die Android-Media-Steuerelemente festlegen. Weitere Informationen zum Anpassen der Android-Mediensteuerung

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

Wiedergabe fortsetzen

Nachdem die MediaSessionService beendet wurde und auch nach einem Neustart des Geräts kann die Wiedergabe fortgesetzt werden, sodass Nutzer den Dienst neu starten und die Wiedergabe an der Stelle fortsetzen können, an der sie aufgehört haben. Standardmäßig ist die Wiedergabefortsetzung deaktiviert. Das bedeutet, dass der Nutzer die Wiedergabe nicht fortsetzen kann, wenn Ihr Dienst nicht ausgeführt wird. Wenn Sie dieses Feature aktivieren möchten, müssen Sie einen Media-Button-Receiver deklarieren und die Methode onPlaybackResumption implementieren.

Media3-Empfänger für Medienschaltflächen 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 zum Fortsetzen der Wiedergabe implementieren

Wenn die Wiedergabe entweder von einem Bluetooth-Gerät oder über die Wiederaufnahmefunktion der Android-System-UI angefordert wird, wird die Callback-Methode 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, metadata (like title
    // and artwork) of the current item 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, metadata (like title
    // and artwork) of the current item and the start position to use here.
    MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist();
    settableFuture.set(resumptionPlaylist);
  }, MoreExecutors.directExecutor());
  return settableFuture;
}

Wenn Sie andere Parameter wie Wiedergabegeschwindigkeit, Wiederholungsmodus oder Zufallsmodus gespeichert haben, können Sie den Player in onPlaybackResumption() mit diesen Parametern konfigurieren, bevor Media3 den Player vorbereitet und die Wiedergabe startet, wenn der Callback abgeschlossen ist.

Diese Methode wird während des Bootvorgangs aufgerufen, um nach einem Neustart des Geräts die Benachrichtigung zum Fortsetzen der Android-System-UI zu erstellen. Bei einer Rich Notification empfiehlt es sich, MediaMetadata-Felder wie title und artworkData oder artworkUri des aktuellen Elements mit lokal verfügbaren Werten zu füllen, da der Netzwerkzugriff möglicherweise noch nicht verfügbar ist. Sie können dem MediaMetadata.extras auch MediaConstants.EXTRAS_KEY_COMPLETION_STATUS und MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE hinzufügen, um die Position anzugeben, an der die Wiedergabe fortgesetzt werden soll.

Erweiterte Controllerkonfiguration und Abwärtskompatibilität

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

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.

Media-Benachrichtigungssteuerung verwenden

Es ist wichtig zu wissen, dass diese alten und Plattform-Controller denselben Status haben und die Sichtbarkeit nicht nach Controller angepasst werden kann (z. B. die verfügbaren PlaybackState.getActions() und PlaybackState.getCustomActions()). Mit dem Media Notification Controller kannst du den in der Media-Sitzung der Plattform 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 Medientasten speziell für die Plattformsitzung festzulegen:

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 Sie ein MediaLibraryService verwenden und Android Auto mit der mobilen App unterstützen möchten, 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 Sitzungen hat ein Automotive-Modul, das die Unterstützung für Automotive OS demonstriert, für die eine separate APK erforderlich ist.