Mediensteuerung

Die Mediensteuerung in Android befindet sich in der Nähe der Schnelleinstellungen. Sitzungen aus mehreren Apps sind in einem wischbaren Karussell angeordnet. Das Karussell listet Sitzungen in dieser Reihenfolge auf:

  • Streams, die lokal auf dem Smartphone wiedergegeben werden
  • Remote-Streams, z. B. von externen Geräten oder Streamingsitzungen erkannte Streams
  • Vorherige fortsetzbare Sitzungen in der Reihenfolge, in der sie zuletzt gespielt wurden

Ab Android 13 (API-Level 33) werden Aktionsschaltflächen für Mediensteuerelemente vom Status Player abgeleitet, damit Nutzer auf eine Vielzahl von Mediensteuerelementen für Apps zugreifen können, die Medien wiedergeben.

Auf diese Weise kannst du einheitliche Mediensteuerelemente und eine optimierte Mediensteuerung auf allen Geräten bieten.

Abbildung 1 zeigt ein Beispiel dafür, wie dies auf einem Smartphone bzw. Tablet dargestellt wird.

Mediensteuerelemente in Bezug auf die Darstellung auf Smartphones und Tablets, anhand eines Beispiels für einen Beispieltrack, der zeigt, wie die Schaltflächen dargestellt werden können
Abbildung 1 : Mediensteuerelemente auf Smartphone und Tablet

Wie in der folgenden Tabelle beschrieben, werden für den Player-Status bis zu fünf Aktionsschaltflächen angezeigt. Im kompakten Modus werden nur die ersten drei Aktionsslots angezeigt. Das entspricht dem Rendering der Mediensteuerelemente auf anderen Android-Plattformen wie Auto, Assistant und Wear OS.

Stellen Kriterien Aktion
1 playWhenReady ist „false“ oder der aktuelle Wiedergabestatus ist STATE_ENDED. Abspielen
playWhenReady ist „true“ und der aktuelle Wiedergabestatus ist STATE_BUFFERING. Rotierendes Ladesymbol
playWhenReady ist „true“ und der aktuelle Wiedergabestatus ist STATE_READY. Pausieren
2 Player-Befehl COMMAND_SEEK_TO_PREVIOUS oder COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ist verfügbar. Zurück
Weder der Player-Befehl COMMAND_SEEK_TO_PREVIOUS noch COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ist verfügbar. Außerdem ist ein benutzerdefinierter Befehl aus dem benutzerdefinierten Layout, der noch nicht platziert wurde, zum Füllen der Anzeigenfläche verfügbar. Benutzerdefiniert
(noch nicht unterstützt bei Media3) PlaybackState-Extras beinhalten einen booleschen Wert true für den Schlüssel EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV. Leer
3 Player-Befehl COMMAND_SEEK_TO_NEXT oder COMMAND_SEEK_TO_NEXT_MEDIA_ITEM ist verfügbar. Weiter
Weder der Player-Befehl COMMAND_SEEK_TO_NEXT noch COMMAND_SEEK_TO_NEXT_MEDIA_ITEM ist verfügbar. Außerdem ist ein benutzerdefinierter Befehl aus dem benutzerdefinierten Layout, der noch nicht platziert wurde, zum Füllen der Anzeigenfläche verfügbar. Benutzerdefiniert
(noch nicht unterstützt bei Media3) PlaybackState-Extras beinhalten einen booleschen Wert true für den Schlüssel EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT. Leer
4 Ein benutzerdefinierter Befehl aus dem benutzerdefinierten Layout, der noch nicht platziert wurde, ist zum Füllen der Anzeigenfläche verfügbar. Benutzerdefiniert
5 Ein benutzerdefinierter Befehl aus dem benutzerdefinierten Layout, der noch nicht platziert wurde, ist zum Füllen der Anzeigenfläche verfügbar. Benutzerdefiniert

Benutzerdefinierte Befehle werden in der Reihenfolge platziert, in der sie dem benutzerdefinierten Layout hinzugefügt wurden.

Befehlsschaltflächen anpassen

Zum Anpassen der Systemmediensteuerung mit Jetpack Media3 können Sie beim Implementieren eines MediaSessionService das benutzerdefinierte Layout der Sitzung und die verfügbaren Controller-Befehle entsprechend festlegen:

  1. Erstellen Sie in onCreate() ein MediaSession und definieren Sie das benutzerdefinierte Layout der Befehlsschaltflächen.

  2. Autorisieren Sie Controller in MediaSession.Callback.onConnect(). Dazu definieren Sie im ConnectionResult die verfügbaren Befehle, einschließlich benutzerdefinierter Befehle.

  3. Antworten Sie in MediaSession.Callback.onCustomCommand() auf den vom Nutzer ausgewählten benutzerdefinierten Befehl.

Kotlin

class PlaybackService : MediaSessionService() {
  private val customCommandFavorites = SessionCommand(ACTION_FAVORITES, Bundle.EMPTY)
  private var mediaSession: MediaSession? = null

  override fun onCreate() {
    super.onCreate()
    val favoriteButton =
      CommandButton.Builder()
        .setDisplayName("Save to favorites")
        .setIconResId(R.drawable.favorite_icon)
        .setSessionCommand(customCommandFavorites)
        .build()
    val player = ExoPlayer.Builder(this).build()
    // Build the session with a custom layout.
    mediaSession =
      MediaSession.Builder(this, player)
        .setCallback(MyCallback())
        .setCustomLayout(ImmutableList.of(favoriteButton))
        .build()
  }

  private inner class MyCallback : MediaSession.Callback {
    override fun onConnect(
      session: MediaSession,
      controller: MediaSession.ControllerInfo
    ): ConnectionResult {
    // Set available player and session commands.
    return AcceptedResultBuilder(session)
      .setAvailablePlayerCommands(
        ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
          .remove(COMMAND_SEEK_TO_NEXT)
          .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
          .remove(COMMAND_SEEK_TO_PREVIOUS)
          .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
          .build()
      )
      .setAvailableSessionCommands(
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
          .add(customCommandFavorites)
          .build()
      )
      .build()
    }

    override fun onCustomCommand(
      session: MediaSession,
      controller: MediaSession.ControllerInfo,
      customCommand: SessionCommand,
      args: Bundle
    ): ListenableFuture {
      if (customCommand.customAction == ACTION_FAVORITES) {
        // Do custom logic here
        saveToFavorites(session.player.currentMediaItem)
        return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
      }
      return super.onCustomCommand(session, controller, customCommand, args)
    }
  }
}

Java

public class PlaybackService extends MediaSessionService {
  private static final SessionCommand CUSTOM_COMMAND_FAVORITES =
      new SessionCommand("ACTION_FAVORITES", Bundle.EMPTY);
  @Nullable private MediaSession mediaSession;

  public void onCreate() {
    super.onCreate();
    CommandButton favoriteButton =
        new CommandButton.Builder()
            .setDisplayName("Save to favorites")
            .setIconResId(R.drawable.favorite_icon)
            .setSessionCommand(CUSTOM_COMMAND_FAVORITES)
            .build();
    Player player = new ExoPlayer.Builder(this).build();
    // Build the session with a custom layout.
    mediaSession =
        new MediaSession.Builder(this, player)
            .setCallback(new MyCallback())
            .setCustomLayout(ImmutableList.of(favoriteButton))
            .build();
  }

  private static class MyCallback implements MediaSession.Callback {
    @Override
    public ConnectionResult onConnect(
        MediaSession session, MediaSession.ControllerInfo controller) {
      // Set available player and session commands.
      return new AcceptedResultBuilder(session)
          .setAvailablePlayerCommands(
              ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
                .remove(COMMAND_SEEK_TO_NEXT)
                .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
                .remove(COMMAND_SEEK_TO_PREVIOUS)
                .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
                .build())
          .setAvailableSessionCommands(
              ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
                .add(CUSTOM_COMMAND_FAVORITES)
                .build())
          .build();
    }

    public ListenableFuture onCustomCommand(
        MediaSession session,
        MediaSession.ControllerInfo controller,
        SessionCommand customCommand,
        Bundle args) {
      if (customCommand.customAction.equals(CUSTOM_COMMAND_FAVORITES.customAction)) {
        // Do custom logic here
        saveToFavorites(session.getPlayer().getCurrentMediaItem());
        return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
      }
      return MediaSession.Callback.super.onCustomCommand(
          session, controller, customCommand, args);
    }
  }
}

Weitere Informationen zum Konfigurieren von MediaSession, damit Clients wie das System eine Verbindung zu Ihrer Medienanwendung herstellen können, finden Sie unter Anderen Clients die Kontrolle gewähren.

Mit Jetpack Media3 wird PlaybackState beim Implementieren eines MediaSession automatisch mit dem Mediaplayer aktualisiert. Wenn du ein MediaSessionService implementierst, veröffentlicht die Bibliothek automatisch eine MediaStyle-Benachrichtigung für dich und hält sie auf dem neuesten Stand.

Auf Aktionsschaltflächen reagieren

Wenn ein Nutzer in der Mediensteuerung des Systems auf eine Aktionsschaltfläche tippt, sendet das MediaController des Systems einen Wiedergabebefehl an die MediaSession. MediaSession leitet diese Befehle dann an den Player weiter. Die auf der Player-Oberfläche von Media3 definierten Befehle werden automatisch von der Mediensitzung verarbeitet.

Eine Anleitung zum Antworten auf einen benutzerdefinierten Befehl finden Sie unter Benutzerdefinierte Befehle hinzufügen.

Verhalten vor Android 13

Aus Gründen der Abwärtskompatibilität bietet die System-UI weiterhin ein alternatives Layout, das Benachrichtigungsaktionen für Apps verwendet, die nicht auf Android 13 aktualisiert werden oder keine PlaybackState-Informationen enthalten. Die Aktionsschaltflächen werden aus der Liste Notification.Action abgeleitet, die an die Benachrichtigung MediaStyle angehängt ist. Das System zeigt bis zu fünf Aktionen in der Reihenfolge an, in der sie hinzugefügt wurden. Im kompakten Modus werden bis zu drei Schaltflächen angezeigt, die anhand der in setShowActionsInCompactView() übergebenen Werte bestimmt werden.

Benutzerdefinierte Aktionen werden in der Reihenfolge platziert, in der sie dem PlaybackState hinzugefügt wurden.

Das folgende Codebeispiel zeigt, wie der MediaStyle-Benachrichtigung Aktionen hinzugefügt werden :

Kotlin

import androidx.core.app.NotificationCompat
import androidx.media3.session.MediaStyleNotificationHelper

var notification = NotificationCompat.Builder(context, CHANNEL_ID)
        // Show controls on lock screen even when user hides sensitive content.
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        // Add media control buttons that invoke intents in your media service
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
        // Apply the media style template
        .setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build()

Java

import androidx.core.app.NotificationCompat;
import androidx.media3.session.MediaStyleNotificationHelper;

NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_ID)
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent)
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent)
        .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build();

Wiederaufnahme von Medien unterstützen

Die Wiederaufnahme von Medien ermöglicht es Nutzern, vorherige Sitzungen aus dem Karussell neu zu starten, ohne die App starten zu müssen. Zu Beginn der Wiedergabe interagiert der Nutzer wie gewohnt mit den Mediensteuerelementen.

Die Funktion zur Wiederaufnahme der Wiedergabe kann in der App „Einstellungen“ unter Ton > Medien ein- und ausgeschaltet werden. Der Nutzer kann die Einstellungen auch aufrufen, indem er auf das Zahnradsymbol tippt, das nach dem Wischen über das maximierte Karussell erscheint.

Media3 bietet APIs für eine einfachere Wiederaufnahme von Medien. Informationen zur Implementierung dieser Funktion findest du in der Dokumentation zur Wiederaufnahme der Wiedergabe mit Media3.

Legacy-Medien-APIs verwenden

In diesem Abschnitt wird die Einbindung der Mediensteuerung des Systems mithilfe der Legacy-APIs von MediaCompat beschrieben.

Das System ruft die folgenden Informationen aus dem MediaMetadata von MediaSession ab und zeigt sie an, wenn sie verfügbar sind:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (Wenn die Dauer nicht festgelegt ist, zeigt die Steuerleiste keinen Fortschritt an.)

Damit Sie eine gültige und genaue Benachrichtigung zur Mediensteuerung erhalten, legen Sie den Wert der METADATA_KEY_TITLE- oder METADATA_KEY_DISPLAY_TITLE-Metadaten auf den Titel der gerade wiedergegebenen Medien fest.

Im Mediaplayer wird die verstrichene Zeit für das aktuell wiedergegebene Medium angezeigt. Außerdem wird eine Steuerleiste angezeigt, die der MediaSession-PlaybackState zugeordnet ist.

Der Mediaplayer zeigt den Fortschritt für das aktuell wiedergegebene Medium sowie eine Steuerleiste an, die MediaSession PlaybackState zugeordnet ist. Über die Steuerleiste können Nutzer die Position ändern. Außerdem wird die verstrichene Zeit für das Medienelement angezeigt. Damit die Steuerleiste aktiviert wird, müssen Sie PlaybackState.Builder#setActions implementieren und ACTION_SEEK_TO einfügen.

Stellen Aktion Kriterien
1 Abspielen Der aktuelle Status des PlaybackState ist einer der folgenden:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
Rotierendes Ladesymbol Der aktuelle Status des PlaybackState ist einer der folgenden:
  • STATE_CONNECTING
  • STATE_BUFFERING
Pausieren Der aktuelle Status von PlaybackState entspricht keinem der oben genannten Werte.
2 Zurück PlaybackState Aktionen umfassen ACTION_SKIP_TO_PREVIOUS.
Benutzerdefiniert PlaybackState Aktionen enthalten ACTION_SKIP_TO_PREVIOUS nicht und PlaybackState benutzerdefinierte Aktionen enthalten eine benutzerdefinierte Aktion, die noch nicht platziert wurde.
Leer PlaybackState Extras enthalten einen booleschen Wert true für den Schlüssel SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV.
3 Weiter PlaybackState Aktionen umfassen ACTION_SKIP_TO_NEXT.
Benutzerdefiniert PlaybackState Aktionen enthalten ACTION_SKIP_TO_NEXT nicht und PlaybackState benutzerdefinierte Aktionen enthalten eine benutzerdefinierte Aktion, die noch nicht platziert wurde.
Leer PlaybackState Extras enthalten einen booleschen Wert true für den Schlüssel SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT.
4 Benutzerdefiniert PlaybackState benutzerdefinierte Aktionen enthalten eine benutzerdefinierte Aktion, die noch nicht platziert wurde.
5 Benutzerdefiniert PlaybackState benutzerdefinierte Aktionen enthalten eine benutzerdefinierte Aktion, die noch nicht platziert wurde.

Standardaktionen hinzufügen

Die folgenden Codebeispiele veranschaulichen, wie Standard- und benutzerdefinierte Aktionen von PlaybackState hinzugefügt werden.

Legen Sie diese Aktionen für „Wiedergabe“, „Pause“, „Zurück“ und „Weiter“ in der PlaybackState für die Mediensitzung fest.

Kotlin

val session = MediaSessionCompat(context, TAG)
val playbackStateBuilder = PlaybackStateCompat.Builder()
val style = NotificationCompat.MediaStyle()

// For this example, the media is currently paused:
val state = PlaybackStateCompat.STATE_PAUSED
val position = 0L
val playbackSpeed = 1f
playbackStateBuilder.setState(state, position, playbackSpeed)

// And the user can play, skip to next or previous, and seek
val stateActions = PlaybackStateCompat.ACTION_PLAY
    or PlaybackStateCompat.ACTION_PLAY_PAUSE
    or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    or PlaybackStateCompat.ACTION_SEEK_TO // adding the seek action enables seeking with the seekbar
playbackStateBuilder.setActions(stateActions)

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build())
style.setMediaSession(session.sessionToken)
notificationBuilder.setStyle(style)

Java

MediaSessionCompat session = new MediaSessionCompat(context, TAG);
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle();

// For this example, the media is currently paused:
int state = PlaybackStateCompat.STATE_PAUSED;
long position = 0L;
float playbackSpeed = 1f;
playbackStateBuilder.setState(state, position, playbackSpeed);

// And the user can play, skip to next or previous, and seek
long stateActions = PlaybackStateCompat.ACTION_PLAY
    | PlaybackStateCompat.ACTION_PLAY_PAUSE
    | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    | PlaybackStateCompat.ACTION_SEEK_TO; // adding this enables the seekbar thumb
playbackStateBuilder.setActions(stateActions);

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build());
style.setMediaSession(session.getSessionToken());
notificationBuilder.setStyle(style);

Wenn in den vorherigen oder nächsten Slots keine Schaltflächen angezeigt werden sollen, fügen Sie ACTION_SKIP_TO_PREVIOUS oder ACTION_SKIP_TO_NEXT nicht hinzu, sondern fügen Sie der Sitzung Extras hinzu:

Kotlin

session.setExtras(Bundle().apply {
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
})

Java

Bundle extras = new Bundle();
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true);
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true);
session.setExtras(extras);

Benutzerdefinierte Aktionen hinzufügen

Für andere Aktionen, die in den Mediensteuerelementen angezeigt werden sollen, kannst du eine PlaybackStateCompat.CustomAction erstellen und diese stattdessen dem PlaybackState hinzufügen. Diese Aktionen werden in der Reihenfolge angezeigt, in der sie hinzugefügt wurden.

Kotlin

val customAction = PlaybackStateCompat.CustomAction.Builder(
    "com.example.MY_CUSTOM_ACTION", // action ID
    "Custom Action", // title - used as content description for the button
    R.drawable.ic_custom_action
).build()

playbackStateBuilder.addCustomAction(customAction)

Java

PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction.Builder(
        "com.example.MY_CUSTOM_ACTION", // action ID
        "Custom Action", // title - used as content description for the button
        R.drawable.ic_custom_action
).build();

playbackStateBuilder.addCustomAction(customAction);

Auf Aktionen für Wiedergabestatus reagieren

Wenn ein Nutzer auf eine Schaltfläche tippt, sendet SystemUI mit MediaController.TransportControls einen Befehl zurück an MediaSession. Du musst einen Callback registrieren, der korrekt auf diese Ereignisse reagieren kann.

Kotlin

val callback = object: MediaSession.Callback() {
    override fun onPlay() {
        // start playback
    }

    override fun onPause() {
        // pause playback
    }

    override fun onSkipToPrevious() {
        // skip to previous
    }

    override fun onSkipToNext() {
        // skip to next
    }

    override fun onSeekTo(pos: Long) {
        // jump to position in track
    }

    override fun onCustomAction(action: String, extras: Bundle?) {
        when (action) {
            CUSTOM_ACTION_1 -> doCustomAction1(extras)
            CUSTOM_ACTION_2 -> doCustomAction2(extras)
            else -> {
                Log.w(TAG, "Unknown custom action $action")
            }
        }
    }

}

session.setCallback(callback)

Java

MediaSession.Callback callback = new MediaSession.Callback() {
    @Override
    public void onPlay() {
        // start playback
    }

    @Override
    public void onPause() {
        // pause playback
    }

    @Override
    public void onSkipToPrevious() {
        // skip to previous
    }

    @Override
    public void onSkipToNext() {
        // skip to next
    }

    @Override
    public void onSeekTo(long pos) {
        // jump to position in track
    }

    @Override
    public void onCustomAction(String action, Bundle extras) {
        if (action.equals(CUSTOM_ACTION_1)) {
            doCustomAction1(extras);
        } else if (action.equals(CUSTOM_ACTION_2)) {
            doCustomAction2(extras);
        } else {
            Log.w(TAG, "Unknown custom action " + action);
        }
    }
};

Medienwiederaufnahme

Damit deine Spieler-App im Bereich mit den Schnelleinstellungen angezeigt wird, musst du eine MediaStyle-Benachrichtigung mit einem gültigen MediaSession-Token erstellen.

Verwenden Sie NotificationBuilder.setContentTitle(), um den Titel der MediaStyle-Benachrichtigung anzuzeigen.

Verwenden Sie NotificationBuilder.setSmallIcon(), um das Markensymbol für den Mediaplayer anzuzeigen.

Apps müssen MediaBrowserService und MediaSession implementieren, um die Wiederaufnahme der Wiedergabe zu unterstützen. Dein MediaSession muss den onPlay()-Callback implementieren.

MediaBrowserService-Implementierung

Nach dem Hochfahren sucht das System nach den fünf zuletzt verwendeten Medien-Apps und stellt Steuerelemente bereit, mit denen die Wiedergabe aus jeder App neu gestartet werden kann.

Das System versucht, MediaBrowserService über eine Verbindung über die SystemUI zu kontaktieren. Ihre Anwendung muss solche Verbindungen zulassen, da sie andernfalls die Wiedergabewiederaufnahme nicht unterstützt.

Verbindungen von SystemUI können anhand des Paketnamens com.android.systemui und der Signatur identifiziert und bestätigt werden. Die SystemUI ist mit der Plattformsignatur signiert. Ein Beispiel für den Abgleich mit der Plattformsignatur finden Sie in der UAMP-App.

Damit die Wiederaufnahme der Wiedergabe unterstützt wird, muss dein MediaBrowserService die folgenden Verhaltensweisen implementieren:

  • onGetRoot() muss schnell einen Stamm ungleich null zurückgeben. Andere komplexe Logik sollten in onLoadChildren() behandelt werden.

  • Wenn onLoadChildren() für die Stamm-Media-ID aufgerufen wird, muss das Ergebnis ein untergeordnetes FLAG_PLAYABLE-Element enthalten.

  • Bei MediaBrowserService sollte das zuletzt wiedergegebene Medienelement zurückgegeben werden, wenn eine EXTRA_let-Abfrage empfangen wird. Der zurückgegebene Wert sollte ein tatsächliches Medienelement und keine generische Funktion sein.

  • Für MediaBrowserService muss eine entsprechende MediaDescription mit einem nicht leeren Titel und Untertitel angegeben werden. Außerdem sollte ein Symbol-URI oder eine Symbol-Bitmap festgelegt werden.

Die folgenden Codebeispiele veranschaulichen, wie onGetRoot() implementiert wird.

Kotlin

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your 
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        rootHints?.let {
            if (it.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                val extras = Bundle().apply {
                    putBoolean(BrowserRoot.EXTRA_RECENT, true)
                }
                return BrowserRoot(MY_RECENTS_ROOT_ID, extras)
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return BrowserRoot(MY_MEDIA_ROOT_ID, null)
    }
    // Return an empty tree to disallow browsing.
    return BrowserRoot(MY_EMPTY_ROOT_ID, null)

Java

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        if (rootHints != null) {
            if (rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                Bundle extras = new Bundle();
                extras.putBoolean(BrowserRoot.EXTRA_RECENT, true);
                return new BrowserRoot(MY_RECENTS_ROOT_ID, extras);
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    }
    // Return an empty tree to disallow browsing.
    return new BrowserRoot(MY_EMPTY_ROOT_ID, null);
}