Deine App muss die MediaBrowserService
mit einem Intent-Filter im Manifest deklarieren. Sie können Ihren eigenen Dienstnamen auswählen. Im folgenden Beispiel ist das „MediaWiedergabeService“.
<service android:name=".MediaPlaybackService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
Hinweis : Die empfohlene Implementierung von MediaBrowserService
ist MediaBrowserServiceCompat
.
die in den
media-compat-Supportbibliothek.
Auf dieser Seite wird der Begriff "MediaBrowserService" bezieht sich auf eine Instanz
von MediaBrowserServiceCompat
.
Mediensitzung initialisieren
Wenn der Dienst die Lebenszyklus-Callback-Methode onCreate()
empfängt, sollte er die folgenden Schritte ausführen:
- Mediensitzung erstellen und initialisieren
- Callback für Mediensitzung festlegen
- Mediensitzungstoken festlegen
Der folgende onCreate()
-Code veranschaulicht diese Schritte:
Kotlin
private const val MY_MEDIA_ROOT_ID = "media_root_id" private const val MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id" class MediaPlaybackService : MediaBrowserServiceCompat() { private var mediaSession: MediaSessionCompat? = null private lateinit var stateBuilder: PlaybackStateCompat.Builder override fun onCreate() { super.onCreate() // Create a MediaSessionCompat mediaSession = MediaSessionCompat(baseContext, LOG_TAG).apply { // Enable callbacks from MediaButtons and TransportControls setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS ) // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player stateBuilder = PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PLAY_PAUSE ) setPlaybackState(stateBuilder.build()) // MySessionCallback() has methods that handle callbacks from a media controller setCallback(MySessionCallback()) // Set the session's token so that client activities can communicate with it. setSessionToken(sessionToken) } } }
Java
public class MediaPlaybackService extends MediaBrowserServiceCompat { private static final String MY_MEDIA_ROOT_ID = "media_root_id"; private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id"; private MediaSessionCompat mediaSession; private PlaybackStateCompat.Builder stateBuilder; @Override public void onCreate() { super.onCreate(); // Create a MediaSessionCompat mediaSession = new MediaSessionCompat(context, LOG_TAG); // Enable callbacks from MediaButtons and TransportControls mediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player stateBuilder = new PlaybackStateCompat.Builder() .setActions( PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE); mediaSession.setPlaybackState(stateBuilder.build()); // MySessionCallback() has methods that handle callbacks from a media controller mediaSession.setCallback(new MySessionCallback()); // Set the session's token so that client activities can communicate with it. setSessionToken(mediaSession.getSessionToken()); } }
Clientverbindungen verwalten
Ein MediaBrowserService
hat zwei Methoden zum Verarbeiten von Clientverbindungen:
onGetRoot()
-Einstellungen
Zugriff auf den Dienst und
onLoadChildren()
bietet einem Client die Möglichkeit, ein Menü der Contenthierarchie der MediaBrowserService
zu erstellen und anzuzeigen.
Clientverbindungen mit onGetRoot()
steuern
Die Methode onGetRoot()
gibt den Stammknoten der Contenthierarchie zurück. Wenn der Parameter
gibt null zurück, wird die Verbindung abgelehnt.
Damit Clients eine Verbindung zu Ihrem Dienst herstellen und dessen Medieninhalte durchsuchen können, onGetRoot() muss eine BrowserRoot-ID ungleich null zurückgeben, bei der es sich um eine Stamm-ID handelt, Ihre Contenthierarchie darstellt.
Damit Clients ohne Surfen eine Verbindung zu Ihrer MediaSession herstellen können, verwenden Sie onGetRoot() muss trotzdem einen BrowserRoot ungleich null zurückgeben, aber die Stamm-ID sollte für eine leere Contenthierarchie.
Eine typische Implementierung von onGetRoot()
könnte so aussehen:
Kotlin
override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): MediaBrowserServiceCompat.BrowserRoot { // (Optional) Control the level of access for the specified package name. // You'll need to write your own logic to do this. return if (allowBrowsing(clientPackageName, clientUid)) { // Returns a root ID that clients can use with onLoadChildren() to retrieve // the content hierarchy. MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null) } else { // Clients can connect, but this BrowserRoot is an empty hierarchy // so onLoadChildren returns nothing. This disables the ability to browse for content. MediaBrowserServiceCompat.BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null) } }
Java
@Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { // (Optional) Control the level of access for the specified package name. // You'll need to write your own logic to do this. if (allowBrowsing(clientPackageName, clientUid)) { // Returns a root ID that clients can use with onLoadChildren() to retrieve // the content hierarchy. return new BrowserRoot(MY_MEDIA_ROOT_ID, null); } else { // Clients can connect, but this BrowserRoot is an empty hierarchy // so onLoadChildren returns nothing. This disables the ability to browse for content. return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null); } }
In einigen Fällen möchten Sie vielleicht festlegen,
auf dein MediaBrowserService
. Eine Möglichkeit ist die Verwendung einer Access Control List (ACL)
gibt an, welche Verbindungen zulässig sind, oder listet alternativ auf,
welche Verbindungen unzulässig sind. Ein Beispiel für die Implementierung einer ACL
die bestimmte Verbindungen ermöglichen,
PackageValidator
im Kurs Universal Android Music Player
Beispiel-App.
Es empfiehlt sich, unterschiedliche Contenthierarchien bereitzustellen,
welche Art von Client die Anfrage stellt. Android Auto schränkt insbesondere
wie Nutzer mit Audio-Apps interagieren. Weitere Informationen finden Sie unter Audio wiedergeben für
Automatisch: Ich
clientPackageName
zur Verbindungszeit ansehen, um den Client zu ermitteln
und geben Sie je nach Client (oder rootHints
) ein anderes BrowserRoot
zurück.
falls zutreffend).
Inhalte mit onLoadChildren()
kommunizieren
Nachdem der Client eine Verbindung hergestellt hat, kann er die Inhaltshierarchie durchlaufen, indem er wiederholt MediaBrowserCompat.subscribe()
aufruft, um eine lokale Darstellung der UI zu erstellen. Die Methode subscribe()
sendet den Callback onLoadChildren()
an den Dienst, der eine Liste von MediaBrowser.MediaItem
-Objekten zurückgibt.
Jedes MediaItem hat einen eindeutigen ID-String, bei dem es sich um ein intransparentes Token handelt. Wenn ein Kunde ein Untermenü öffnen oder ein Element abspielen möchte, wird die ID übergeben. Ihr Dienst ist dafür verantwortlich, die ID mit dem entsprechenden Menüknoten oder Inhaltselement zu verknüpfen.
Eine einfache Implementierung von onLoadChildren()
könnte so aussehen:
Kotlin
override fun onLoadChildren( parentMediaId: String, result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>> ) { // Browsing not allowed if (MY_EMPTY_MEDIA_ROOT_ID == parentMediaId) { result.sendResult(null) return } // Assume for example that the music catalog is already loaded/cached. val mediaItems = emptyList<MediaBrowserCompat.MediaItem>() // Check if this is the root menu: if (MY_MEDIA_ROOT_ID == parentMediaId) { // Build the MediaItem objects for the top level, // and put them in the mediaItems list... } else { // Examine the passed parentMediaId to see which submenu we're at, // and put the children of that menu in the mediaItems list... } result.sendResult(mediaItems) }
Java
@Override public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) { // Browsing not allowed if (TextUtils.equals(MY_EMPTY_MEDIA_ROOT_ID, parentMediaId)) { result.sendResult(null); return; } // Assume for example that the music catalog is already loaded/cached. List<MediaItem> mediaItems = new ArrayList<>(); // Check if this is the root menu: if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) { // Build the MediaItem objects for the top level, // and put them in the mediaItems list... } else { // Examine the passed parentMediaId to see which submenu we're at, // and put the children of that menu in the mediaItems list... } result.sendResult(mediaItems); }
Hinweis : MediaItem
-Objekte, die vom MediaBrowserService bereitgestellt werden
sollte keine Symbol-Bitmaps enthalten. Verwenden Sie stattdessen ein Uri
, indem Sie Folgendes aufrufen:
setIconUri()
wenn Sie die MediaDescription
für jedes Element erstellen.
Ein Beispiel für die Implementierung von onLoadChildren()
findest du in der Beispiel-App für Universal Android Music Player.
Lebenszyklus des Medienbrowser-Dienstes
Das Verhalten eines Android-Dienstes hängt davon ab, ob er an einen oder mehrere Clients gestartet oder gebunden ist. Nachdem ein Dienst erstellt wurde, kann er gestartet und/oder gebunden werden. In allen diesen Zuständen ist sie voll funktionsfähig und kann die für sie vorgesehene Arbeit ausführen. Der Unterschied bezieht sich darauf, wie lange der Dienst existiert. Ein gebundener Dienst wird erst gelöscht, wenn die Bindung aller gebundenen Clients aufgehoben wurde. Ein gestarteter Dienst kann explizit angehalten und gelöscht werden (vorausgesetzt, er ist nicht mehr an Clients gebunden).
Wenn eine MediaBrowser
, die in einer anderen Aktivität ausgeführt wird, eine Verbindung zu einer MediaBrowserService
herstellt, bindet sie die Aktivität an den Dienst, wodurch der Dienst gebunden, aber nicht gestartet wird. Dieses Standardverhalten ist in die Klasse MediaBrowserServiceCompat
integriert.
Ein Dienst, der nur gebunden (und nicht gestartet) ist, wird gelöscht, wenn die Bindung aller seiner Clients aufgehoben wird. Wenn die Verbindung zu Ihrer UI-Aktivität an diesem Punkt unterbrochen wird, wird der Dienst gelöscht. Wenn du noch keine Musik angehört hast, ist das kein Problem. Zu Beginn der Wiedergabe erwarten Nutzer jedoch wahrscheinlich, dass sie auch nach einem App-Wechsel weiterhören werden. Sie möchten den Spieler nicht zerstören, wenn Sie die Bindung zur Benutzeroberfläche aufheben, um mit einer anderen App zu arbeiten.
Aus diesem Grund muss der Dienst gestartet werden, sobald er beginnt.
zum Abspielen, indem du startService()
aufrufst. A
gestarteter Dienst muss explizit beendet werden, unabhängig davon, ob er gebunden ist. Dieses
stellt sicher, dass Ihr Player auch dann funktioniert, wenn die steuernde Benutzeroberfläche
Bindungen von Aktivitäten aufheben.
Rufen Sie Context.stopService()
oder stopSelf()
auf, um einen gestarteten Dienst zu beenden. Das System stoppt und beendet den Dienst so schnell wie möglich. Wenn jedoch noch ein oder mehrere Clients an den Dienst gebunden sind, wird der Aufruf zum Beenden des Dienstes verzögert, bis alle Clients die Bindung aufheben.
Der Lebenszyklus der MediaBrowserService
wird durch die Art ihrer Erstellung, die Anzahl der an sie gebundenen Clients und die Aufrufe gesteuert, die sie von Mediensitzungs-Callbacks erhält. Zusammenfassung:
- Der Dienst wird erstellt, wenn er als Reaktion auf eine Medienschaltfläche gestartet wird oder wenn eine Aktivität an sie gebunden wird (nachdem eine Verbindung über die
MediaBrowser
hergestellt wurde). - Der
onPlay()
-Callback für die Mediensitzung sollte Code zum Aufrufen vonstartService()
enthalten. Dadurch wird sichergestellt, dass der Dienst gestartet und weiter ausgeführt wird, auch wenn alle an ihn gebundenen UI-MediaBrowser
-Aktivitäten die Bindung aufheben. - Der
onStop()
-Callback solltestopSelf()
aufrufen. Wenn der Dienst gestartet wurde, wird er dadurch beendet. Außerdem wird der Dienst gelöscht, wenn keine Aktivitäten an ihn gebunden sind. Andernfalls bleibt der Dienst gebunden, bis die Bindung aller Aktivitäten aufgehoben wird. Wenn ein nachfolgenderstartService()
-Aufruf eingeht, bevor der Dienst gelöscht wird, wird der ausstehende Stopp abgebrochen.
Das folgende Flussdiagramm zeigt, wie der Lebenszyklus eines Dienstes verwaltet wird. Der Variablenzähler verfolgt die Anzahl der gebundenen Clients:
MediaStyle-Benachrichtigungen mit einem Dienst im Vordergrund verwenden
Wenn ein Dienst wiedergegeben wird, sollte er im Vordergrund ausgeführt werden. Dadurch weiß das System, dass der Dienst eine nützliche Funktion ausführt, und sollte nicht beendet werden, wenn das System nur noch wenig Arbeitsspeicher hat. Über einen Dienst im Vordergrund muss eine Benachrichtigung angezeigt werden, damit der Nutzer darüber informiert wird und diese optional steuern kann. Der onPlay()
-Callback sollte den Dienst im Vordergrund platzieren. (Beachten Sie, dass dies eine besondere Bedeutung des Begriffs „Vordergrund“ ist. Während bei Android der Dienst im Vordergrund für die Prozessverwaltung betrachtet wird, wird für den Nutzer der Player im Hintergrund wiedergegeben, während eine andere App im Vordergrund zu sehen ist. auf dem Bildschirm angezeigt werden.)
Wenn ein Dienst im Vordergrund ausgeführt wird, muss eine Benachrichtigung angezeigt werden, idealerweise mit einer oder mehreren Transportsteuerelementen. Die Benachrichtigung sollte auch nützliche Informationen aus den Metadaten der Sitzung enthalten.
Erstellen Sie die Benachrichtigung und zeigen Sie sie an, wenn der Player mit dem Abspielen beginnt. Dazu eignet sich die Methode MediaSessionCompat.Callback.onPlay()
am besten.
Im folgenden Beispiel wird die Methode
NotificationCompat.MediaStyle
,
das für Medien-Apps gedacht ist. Es wird gezeigt, wie Sie eine Benachrichtigung erstellen, die Metadaten und Transportsteuerelemente anzeigt. Die Convenience-Methode
getController()
ermöglicht es Ihnen, einen Mediencontroller direkt in Ihrer Mediensitzung zu erstellen.
Kotlin
// Given a media session and its context (usually the component containing the session) // Create a NotificationCompat.Builder // Get the session's metadata val controller = mediaSession.controller val mediaMetadata = controller.metadata val description = mediaMetadata.description val builder = NotificationCompat.Builder(context, channelId).apply { // Add the metadata for the currently playing track setContentTitle(description.title) setContentText(description.subtitle) setSubText(description.description) setLargeIcon(description.iconBitmap) // Enable launching the player by clicking the notification setContentIntent(controller.sessionActivity) // Stop the service when the notification is swiped away setDeleteIntent( MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_STOP ) ) // Make the transport controls visible on the lockscreen setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // Add an app icon and set its accent color // Be careful about the color setSmallIcon(R.drawable.notification_icon) color = ContextCompat.getColor(context, R.color.primaryDark) // Add a pause button addAction( NotificationCompat.Action( R.drawable.pause, getString(R.string.pause), MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_PLAY_PAUSE ) ) ) // Take advantage of MediaStyle features setStyle(android.support.v4.media.app.NotificationCompat.MediaStyle() .setMediaSession(mediaSession.sessionToken) .setShowActionsInCompactView(0) // Add a cancel button .setShowCancelButton(true) .setCancelButtonIntent( MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_STOP ) ) ) } // Display the notification and place the service in the foreground startForeground(id, builder.build())
Java
// Given a media session and its context (usually the component containing the session) // Create a NotificationCompat.Builder // Get the session's metadata MediaControllerCompat controller = mediaSession.getController(); MediaMetadataCompat mediaMetadata = controller.getMetadata(); MediaDescriptionCompat description = mediaMetadata.getDescription(); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId); builder // Add the metadata for the currently playing track .setContentTitle(description.getTitle()) .setContentText(description.getSubtitle()) .setSubText(description.getDescription()) .setLargeIcon(description.getIconBitmap()) // Enable launching the player by clicking the notification .setContentIntent(controller.getSessionActivity()) // Stop the service when the notification is swiped away .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP)) // Make the transport controls visible on the lockscreen .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // Add an app icon and set its accent color // Be careful about the color .setSmallIcon(R.drawable.notification_icon) .setColor(ContextCompat.getColor(context, R.color.primaryDark)) // Add a pause button .addAction(new NotificationCompat.Action( R.drawable.pause, getString(R.string.pause), MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_PLAY_PAUSE))) // Take advantage of MediaStyle features .setStyle(new MediaStyle() .setMediaSession(mediaSession.getSessionToken()) .setShowActionsInCompactView(0) // Add a cancel button .setShowCancelButton(true) .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP))); // Display the notification and place the service in the foreground startForeground(id, builder.build());
Beachten Sie bei der Verwendung von MediaStyle-Benachrichtigungen das Verhalten dieser NotificationCompat-Einstellungen:
- Wenn Sie
setContentIntent()
verwenden, wird Ihr Dienst automatisch gestartet, sobald die Benachrichtigung angeklickt wird, ist das eine praktische Funktion. - In einer „nicht vertrauenswürdigen“ Situation
wie der Sperrbildschirm, ist die Standardsichtbarkeit für Benachrichtigungsinhalte
VISIBILITY_PRIVATE
. Wahrscheinlich möchten Sie Mobilitätsoptionen auf dem Sperrbildschirm,VISIBILITY_PUBLIC
ist also die richtige Wahl. - Gehen Sie beim Festlegen der Hintergrundfarbe vorsichtig vor. In einer normalen Benachrichtigung Android-Version 5.0 oder höher wird die Farbe nur auf den Hintergrund des kleines App-Symbol. Für MediaStyle-Benachrichtigungen vor Android 7.0 ist die Farbe wird für den gesamten Benachrichtigungshintergrund verwendet. Testen Sie die Hintergrundfarbe. Los schonend für die Augen und vermeiden Sie extrem helle oder fluoreszierende Farben.
Diese Einstellungen sind nur verfügbar, wenn Sie NotificationCompat.MediaStyle verwenden:
setMediaSession()
verwenden um die Benachrichtigung mit Ihrer Sitzung zu verknüpfen. Dadurch sind Drittanbieter-Apps erlaubt und Companion-Geräten, um auf die Sitzung zuzugreifen und sie zu steuern.- Mit
setShowActionsInCompactView()
kannst du bis zu 3 Aktionen hinzufügen, die in der ContentView in Standardgröße der Benachrichtigung. (Hier ist die Pause-Schaltfläche angegeben.) - Unter Android 5.0 (API-Level 21) und höher können Sie eine Benachrichtigung wegwischen, um das
sobald der Dienst nicht mehr im Vordergrund ausgeführt wird. Folgendes ist nicht möglich:
in früheren Versionen. Nutzern zu erlauben, die Benachrichtigung zu entfernen und die Wiedergabe zu beenden
vor Android 5.0 (API-Ebene 21) abgeschlossen haben, können Sie in der oberen rechten Ecke der
indem du
setShowCancelButton(true)
undsetCancelButtonIntent()
anrufst.
Wenn Sie die Schaltflächen zum Pausieren und Abbrechen hinzufügen, müssen Sie einen PendingIntent zum Anhängen hinzufügen.
Wiedergabeaktion hinzu. Die Konvertierung erfolgt durch die Methode MediaButtonReceiver.buildMediaButtonPendingIntent()
eine WiedergabeState-Aktion in einen PendingIntent um.