Hintergrundwiedergabe mit MediaSessionService

Es ist oft wünschenswert, Medien abzuspielen, während eine App nicht im Vordergrund ausgeführt wird. Für Beispiel: Ein Musikplayer spielt Musik in der Regel weiter ab, wenn der Nutzer oder eine andere App verwendet. Die Media3-Bibliothek bietet Oberflächen, die die Hintergrundwiedergabe unterstützen.

MediaSessionService verwenden

Um die Hintergrundwiedergabe zu aktivieren, musst du Player und MediaSession innerhalb eines separaten Service. So kann das Gerät weiterhin Medien bereitstellen, auch wenn sich deine App nicht in im Vordergrund.

<ph type="x-smartling-placeholder">
</ph> Der MediaSessionService ermöglicht die separate Ausführung der Mediensitzung.
  aus den Aktivitäten der App
Abbildung 1: MediaSessionService ermöglicht Medien, Sitzung getrennt von der App-Aktivität ausgeführt werden

Wenn du einen Player innerhalb eines Dienstes hostest, solltest du einen MediaSessionService verwenden. Erstellen Sie dazu eine Klasse, die MediaSessionService erweitert, und erstellen Sie Ihr Mediensitzung darin.

Mit MediaSessionService können externe Kunden wie Google Assistant, die Mediensteuerung des Systems oder Companion-Geräte wie Wear OS, Ihren Dienst zu verwalten, eine Verbindung zu ihm herzustellen und die Wiedergabe zu steuern, ohne auf Ihre die UI-Aktivitäten der App überprüfen. Es können sogar mehrere Client-Apps in dieselbe MediaSessionService, jede App hat eine eigene MediaController.

Dienstlebenszyklus implementieren

Sie müssen drei Lebenszyklusmethoden Ihres Dienstes implementieren:

  • onCreate() wird aufgerufen, wenn der erste Controller eine Verbindung herstellen soll und der instanziiert und gestartet wird. Der beste Ort, um Player und MediaSession.
  • onTaskRemoved(Intent) wird aufgerufen, wenn der Nutzer die App aus der die letzten Aufgaben. Wenn die Wiedergabe läuft, kann die App den Dienst behalten im Vordergrund ausgeführt werden. Wenn der Player pausiert ist, befindet sich der Dienst nicht im und muss beendet werden.
  • onDestroy() wird aufgerufen, wenn der Dienst beendet wird. Alle Ressourcen einschließlich Player und Sitzung müssen freigegeben werden.

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()
  }

  // The user dismissed the app from the recent tasks
  override fun onTaskRemoved(rootIntent: Intent?) {
    val player = mediaSession?.player!!
    if (!player.playWhenReady
        || player.mediaItemCount == 0
        || player.playbackState == Player.STATE_ENDED) {
      // Stop the service if not playing, continue playing in the background
      // otherwise.
      stopSelf()
    }
  }

  // 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();
  }

  // The user dismissed the app from the recent tasks
  @Override
  public void onTaskRemoved(@Nullable Intent rootIntent) {
    Player player = mediaSession.getPlayer();
    if (!player.getPlayWhenReady()
        || player.getMediaItemCount() == 0
        || player.getPlaybackState() == Player.STATE_ENDED) {
      // Stop the service if not playing, continue playing in the background
      // otherwise.
      stopSelf();
    }
  }

  // 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 laufen zu lassen, kann eine App Auf jeden Fall beenden, wenn der Nutzer die App schließt:

Kotlin

override fun onTaskRemoved(rootIntent: Intent?) {
  val player = mediaSession.player
  if (player.playWhenReady) {
    // Make sure the service is not in foreground.
    player.pause()
  }
  stopSelf()
}

Java

@Override
public void onTaskRemoved(@Nullable Intent rootIntent) {
  Player player = mediaSession.getPlayer();
  if (player.getPlayWhenReady()) {
    // Make sure the service is not in foreground.
    player.pause();
  }
  stopSelf();
}

Zugriff auf die Mediensitzung gewähren

Überschreiben Sie die Methode onGetSession(), um anderen Clients Zugriff auf Ihre Medien zu gewähren Sitzung, 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

Eine App benötigt die Berechtigung zum Ausführen eines Dienstes im Vordergrund. Fügen Sie den FOREGROUND_SERVICE für das Manifest und wenn Sie API 34 und über FOREGROUND_SERVICE_MEDIA_PLAYBACK:

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

Außerdem müssen Sie die Klasse Service im Manifest mit einem Intent-Filter deklarieren von MediaSessionService.

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

Sie müssen eine foregroundServiceType einschließlich mediaPlayback, wenn deine App auf einem Gerät mit Android ausgeführt wird 10 (API-Level 29) und höher.

Wiedergabe mit einem MediaController steuern

In der Aktivität oder dem Fragment, die deine Player-Benutzeroberfläche enthält, kannst du einen Link zwischen der UI und der Mediensitzung mit einem MediaController. Ihre UI verwendet Medien-Controller, um Befehle von Ihrer Benutzeroberfläche an den Player innerhalb der Sitzung. Weitere Informationen finden Sie in der MediaController erstellen Leitfaden zum Erstellen und Verwenden von MediaController.

UI-Befehle verarbeiten

MediaSession empfängt Befehle vom Controller über MediaSession.Callback. Beim Initialisieren eines MediaSession wird ein Standardwert erstellt Implementierung von MediaSession.Callback, die automatisch alle Befehle, die MediaController an deinen Player sendet.

Benachrichtigung

Ein MediaSessionService erstellt automatisch eine MediaNotification für Sie, die sollte in den meisten Fällen funktionieren. Standardmäßig ist die veröffentlichte Benachrichtigung MediaStyle Benachrichtigung die immer auf dem neuesten Stand ist, aus Ihrer Mediensitzung und zeigt die Wiedergabesteuerung an. Das MediaNotification erkennt Ihre Sitzung und kann zur Steuerung der Wiedergabe für alle anderen Apps verwendet werden. die mit derselben Sitzung verbunden sind.

Beispiel: Eine Musikstreaming-App mit MediaSessionService würde einen MediaNotification, die den Titel, den Künstler und das Albumcover des das aktuell abgespielte Medienelement zusammen mit der Wiedergabesteuerung basierend auf Ihrer MediaSession-Konfiguration.

Die erforderlichen Metadaten können in den Medien bereitgestellt oder als Teil des Medienelement wie im folgenden Snippet anzuzeigen:

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();

Apps können die Befehlsschaltflächen von Android Media Controls anpassen. Weitere Informationen zur Anpassung von Android Media .

Benachrichtigungsanpassung

Wenn du die Benachrichtigung anpassen möchtest, erstelle ein MediaNotification.Provider mit DefaultMediaNotificationProvider.Builder oder durch Erstellen einer benutzerdefinierten Implementierung der Anbieterschnittstelle. Fügen Sie Ihr auf MediaSessionService mit setMediaNotificationProvider

Wiedergabe fortsetzen

Medienschaltflächen sind Hardwaretasten auf Android-Geräten und anderen Peripheriegeräten Geräte verwenden, z. B. die Wiedergabe- oder Pausetaste an einem Bluetooth-Headset. Medien 3 die Eingaben der Medienschaltfläche für Sie übernehmen, wenn der Dienst ausgeführt wird.

Empfänger der Media3-Medientaste deklarieren

Media3 enthält eine API, mit der Nutzer nachdem eine App beendet wurde oder nachdem das Gerät neu gestartet. Die Wiederaufnahme der Wiedergabe ist standardmäßig deaktiviert. Das bedeutet, dass die Nutzenden können die Wiedergabe nicht fortsetzen, wenn der Dienst nicht ausgeführt wird. Wenn Sie sich anmelden möchten, So deklarieren Sie 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 Wiederaufnahme der Wiedergabe implementieren

Wenn die Wiederaufnahme der Wiedergabe von einem Bluetooth-Gerät oder vom Wiederaufnahmefunktion der Android-System-UI onPlaybackResumption() Callback-Methode 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 Sie andere Parameter wie die Wiedergabegeschwindigkeit, den Wiederholungsmodus oder Zufallsmix aktiviert ist, eignet sich onPlaybackResumption() gut, um den Player mit diesen Parametern ein, bevor Media3 den Player vorbereitet und die Wiedergabe startet, der Callback abgeschlossen wird.

Erweiterte Controllerkonfiguration und Abwärtskompatibilität

Ein häufiges Szenario ist die Verwendung einer MediaController in der App-UI zur Steuerung. Wiedergabe und Wiedergabe der Playlist. Gleichzeitig wird die Sitzung wie die Mediensteuerung von Android und Assistant auf Mobilgeräten oder Fernsehern, Wear OS für Smartwatches und Android Auto in Autos Die Demo-App für die Media3-Sitzung ist ein Beispiel für eine App, in der ein solches Szenario implementiert ist.

Diese externen Clients verwenden möglicherweise APIs wie MediaControllerCompat der Legacy-Version AndroidX-Bibliothek oder android.media.session.MediaController des Android- Framework. Media3 ist vollständig abwärtskompatibel mit der alten Bibliothek und ermöglicht Interoperabilität mit der Android Framework API.

Media Notification Controller verwenden

Wichtig ist, dass Legacy- oder Framework-Controller die dieselben Werte aus dem Framework PlaybackState.getActions() und PlaybackState.getCustomActions(). Um Aktionen und benutzerdefinierte Aktionen von Framework-Sitzung kann eine App den Media Notification Controller verwenden. und die verfügbaren Befehle und das benutzerdefinierte Layout festlegen. Der Dienst verbindet die Medien, Benachrichtigungs-Controller zu Ihrer Sitzung hinzu und diese verwendet die ConnectionResult, die zum Konfigurieren vom onConnect() deines Callbacks zurückgegeben werden Aktionen und benutzerdefinierten Aktionen der Framework-Sitzung.

Bei einem rein mobilen Szenario kann eine App die Implementierung einer MediaSession.Callback.onConnect() zum Festlegen der verfügbaren Befehle und ein benutzerdefiniertes Layout speziell für die Framework-Sitzung:

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 layout and available commands to configure the legacy/framework session.
    return AcceptedResultBuilder(session)
      .setCustomLayout(
        ImmutableList.of(
          createSeekBackwardButton(customCommandSeekBackward),
          createSeekForwardButton(customCommandSeekForward))
      )
      .setAvailablePlayerCommands(playerCommands)
      .setAvailableSessionCommands(sessionCommands)
      .build()
  }
  // Default commands with default custom layout 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 layout and available commands to configure the legacy/framework session.
    return new AcceptedResultBuilder(session)
        .setCustomLayout(
            ImmutableList.of(
                createSeekBackwardButton(customCommandSeekBackward),
                createSeekForwardButton(customCommandSeekForward)))
        .setAvailablePlayerCommands(playerCommands)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
  // Default commands without default custom layout for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

Android Auto autorisieren, benutzerdefinierte Befehle zu senden

Bei Verwendung eines MediaLibraryService und zur Unterstützung von Android Auto mit der mobilen App den Android Auto-Controller erfordert die entsprechenden verfügbaren Befehle. Andernfalls würde Media3 die von diesem Controller empfangene benutzerdefinierte Befehle:

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 with default custom layout 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 without default custom layout for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

Die Sitzungs-Demo-App hat eine Automobilmodul , in dem die Unterstützung von Automotive OS demonstriert wird, für die ein separates APK erforderlich ist.