Es ist oft wünschenswert, Medien abzuspielen, während eine App nicht im Vordergrund ausgeführt wird. Ein Musikplayer spielt Musik in der Regel beispielsweise weiter, wenn der Nutzer sein Gerät gesperrt hat oder eine andere App verwendet. Die Media3-Bibliothek bietet eine Reihe von Oberflächen, die die Hintergrundwiedergabe unterstützen.
MediaSessionService verwenden
Wenn du die Hintergrundwiedergabe aktivieren möchtest, musst du Player
und MediaSession
in einem separaten Dienst speichern.
So kann das Gerät weiterhin Medien bereitstellen, auch wenn die App nicht im Vordergrund ausgeführt wird.
Wenn du einen Player innerhalb eines Dienstes hostest, solltest du einen MediaSessionService
verwenden.
Erstellen Sie dazu eine Klasse, die MediaSessionService
erweitert, und erstellen Sie darin Ihre Mediensitzung.
Mit MediaSessionService
können externe Clients wie Google Assistant, Mediensteuerungen von Systemen oder Begleitgeräte wie Wear OS deinen Dienst finden, eine Verbindung zu ihm herstellen und die Wiedergabe steuern, ohne auf die UI-Aktivitäten deiner App zuzugreifen. Tatsächlich können mehrere Clientanwendungen gleichzeitig mit derselben MediaSessionService
verbunden sein, wobei jede Anwendung eine eigene MediaController
hat.
Dienstlebenszyklus implementieren
Sie müssen drei Lebenszyklusmethoden Ihres Dienstes implementieren:
onCreate()
wird aufgerufen, wenn der erste Controller eine Verbindung herstellen soll und der Dienst instanziiert und gestartet wird. Es ist der beste Ort, umPlayer
undMediaSession
zu erstellen.onTaskRemoved(Intent)
wird aufgerufen, wenn der Nutzer die App aus den letzten Aufgaben schließt. Wenn die Wiedergabe läuft, kann die App festlegen, dass der Dienst im Vordergrund ausgeführt wird. Wenn der Player pausiert wird, befindet sich der Dienst nicht im Vordergrund und muss beendet werden.onDestroy()
wird aufgerufen, wenn der Dienst beendet wird. Alle Ressourcen, einschließlich Spieler 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(); } }
Als Alternative zur fortlaufenden Wiedergabe im Hintergrund kann eine App den Dienst jederzeit 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 die 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
Eine App benötigt die Berechtigung zum Ausführen eines Dienstes im Vordergrund. Fügen Sie dem Manifest die Berechtigung FOREGROUND_SERVICE
hinzu. Wenn Sie API 34 und höher als Ziel verwenden, gilt außerdem 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 dem Intent-Filter MediaSessionService
deklarieren.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
Du musst eine foregroundServiceType
definieren, die mediaPlayback
enthält, wenn deine App auf einem Gerät mit Android 10 (API-Level 29) oder höher ausgeführt wird.
Wiedergabe mit einem MediaController
steuern
In der Aktivität oder dem Fragment, die bzw. das deine Player-UI enthält, kannst du mithilfe eines MediaController
eine Verknüpfung zwischen der UI und deiner Mediensitzung herstellen. Ihre UI verwendet den Media Controller, um Befehle von Ihrer UI an den Player innerhalb der Sitzung zu senden. Weitere Informationen zum Erstellen und Verwenden eines MediaController
finden Sie im Leitfaden zum Erstellen eines MediaController
.
UI-Befehle verarbeiten
MediaSession
empfängt Befehle vom Controller über die MediaSession.Callback
. Beim Initialisieren eines MediaSession
wird eine Standardimplementierung von MediaSession.Callback
erstellt, die automatisch alle Befehle verarbeitet, die ein MediaController
an deinen Player sendet.
Benachrichtigung
Ein MediaSessionService
erstellt automatisch eine MediaNotification
, die in den meisten Fällen funktionieren sollte. Standardmäßig ist die veröffentlichte Benachrichtigung eine MediaStyle
-Benachrichtigung, die mit den neuesten Informationen aus Ihrer Mediensitzung aktualisiert wird und die Wiedergabesteuerung anzeigt. MediaNotification
erkennt Ihre Sitzung und kann verwendet werden, um die Wiedergabe anderer Apps zu steuern, die mit derselben Sitzung verbunden sind.
Beispielsweise würde eine Musikstreaming-App, die ein MediaSessionService
verwendet, eine MediaNotification
erstellen, die gemäß deiner MediaSession
-Konfiguration den Titel, den Künstler und das Albumcover des aktuell abgespielten Medienelements zusammen mit den Steuerelementen für die Wiedergabe anzeigt.
Die erforderlichen Metadaten können in den Medien bereitgestellt oder als Teil des Medienelements wie im folgenden Snippet deklariert werden:
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 zum Anpassen von Android Media-Steuerelementen
Benachrichtigungsanpassung
Wenn du die Benachrichtigung anpassen möchtest, erstelle eine MediaNotification.Provider
mit DefaultMediaNotificationProvider.Builder
oder eine benutzerdefinierte Implementierung der Anbieterschnittstelle. Fügen Sie Ihren Anbieter mit setMediaNotificationProvider
zu MediaSessionService
hinzu.
Wiedergabe fortsetzen
Medientasten sind Hardwaretasten auf Android-Geräten und anderen Peripheriegeräten, z. B. die Wiedergabe- oder Pausetaste an einem Bluetooth-Headset. Media3 verarbeitet die Medienschaltflächen für Sie, wenn der Dienst ausgeführt wird.
Empfänger der Media3-Medientaste deklarieren
Media3 enthält eine API, mit der Nutzer die Wiedergabe fortsetzen können, nachdem eine App beendet wurde und sogar nachdem das Gerät neu gestartet wurde. Die Wiederaufnahme der Wiedergabe ist standardmäßig deaktiviert. Das bedeutet, dass der Nutzer die Wiedergabe nicht fortsetzen kann, wenn dein Dienst nicht ausgeführt wird. Deklariere zuerst MediaButtonReceiver
in deinem Manifest, um die Funktion zu aktivieren:
<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 der 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 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, eignet sich onPlaybackResumption()
gut, um den Player mit diesen Parametern zu konfigurieren, bevor Media3 den Player vorbereitet und die Wiedergabe nach Abschluss des Callbacks startet.
Erweiterte Controllerkonfiguration und Abwärtskompatibilität
Ein gängiges Szenario ist die Verwendung einer MediaController
in der App-Benutzeroberfläche zur Steuerung der Wiedergabe und zum Anzeigen der Playlist. Gleichzeitig ist die Sitzung externen Clients wie der Android-Mediensteuerung und Assistant auf Mobilgeräten oder Fernsehern, Wear OS für Smartwatches und Android Auto in Autos zugänglich. 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 können APIs wie MediaControllerCompat
der alten AndroidX-Bibliothek oder android.media.session.MediaController
des Android-Frameworks verwenden. Media3 ist vollständig abwärtskompatibel mit der Legacy-Bibliothek und bietet Interoperabilität mit der Android Framework API.
Media Notification Controller verwenden
Wichtig: Diese Legacy- oder Framework-Controller lesen dieselben Werte aus dem Framework PlaybackState.getActions()
und PlaybackState.getCustomActions()
. Um Aktionen und benutzerdefinierte Aktionen der Framework-Sitzung zu bestimmen, kann eine App den Media Notification Controller verwenden und die verfügbaren Befehle und das benutzerdefinierte Layout festlegen. Der Dienst verbindet den Medien-Benachrichtigungs-Controller mit Ihrer Sitzung. Die Sitzung verwendet das ConnectionResult
, das vom onConnect()
Ihres Callbacks zurückgegeben wird, um Aktionen und benutzerdefinierte Aktionen der Framework-Sitzung zu konfigurieren.
Bei einem Szenario nur für Mobilgeräte bietet eine App eine Implementierung von MediaSession.Callback.onConnect()
, um verfügbare Befehle und ein benutzerdefiniertes Layout speziell für die Framework-Sitzung 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 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
Wenn du ein MediaLibraryService
verwendest und Android Auto mit der mobilen App unterstützt, sind für den Android Auto-Controller die entsprechenden verfügbaren Befehle erforderlich. Andernfalls würde Media3 eingehende benutzerdefinierte Befehle über diesen Controller ablehnen:
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 enthält ein Automotive-Modul, das die Unterstützung von Automotive OS zeigt, für das ein separates APK erforderlich ist.