Medienbrowser-Dienst erstellen

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:

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 die 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 getrennt 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 von startService() 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 sollte stopSelf() 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 nachfolgender startService()-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:

Dienstlebenszyklus

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) und setCancelButtonIntent() 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.