Mediensteuerung

Die Mediensteuerelemente in Android befinden sich neben den Schnelleinstellungen. Sitzungen aus mehreren Apps sind in einem wischbaren Karussell angeordnet. Die Sitzungen werden in dieser Reihenfolge aufgelistet:

  • Lokal auf dem Smartphone wiedergegebene Streams
  • Remote-Streams, z. B. Streams, die auf externen Geräten erkannt wurden, oder Streamingsitzungen
  • Vorherige fortsetzbare Sitzungen, in der Reihenfolge, in der sie zuletzt wiedergegeben wurden

Ab Android 13 (API-Level 33), damit Nutzer Zugriff auf eine Vielzahl von Mediensteuerelementen für Apps haben, die Medien abspielen, werden Aktionsschaltflächen in Mediensteuerelementen vom Status Player abgeleitet.

Auf diese Weise können Sie einheitliche Mediensteuerelemente und eine verbesserte Mediensteuerung auf allen Geräten präsentieren.

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

Mediensteuerelemente in Bezug darauf, wie sie auf Smartphones und Tablets angezeigt werden, mit einem Beispiel für einen Beispiel-Track, der zeigt, wie die Schaltflächen aussehen können
Abbildung 1: Mediensteuerung auf Smartphones und Tablets

Das System zeigt je nach Player-Status bis zu fünf Aktionsschaltflächen an, wie in der folgenden Tabelle dargestellt. Im kompakten Modus werden nur die ersten drei Aktionsslots angezeigt. Dies entspricht dem Rendering von Mediensteuerelementen auf anderen Android-Plattformen wie Auto, Assistant und Wear OS.

Stellen Kriterien Aktion
1 playWhenReady ist „false“ oder der aktuelle Wiedergabestatus ist STATE_ENDED. Spielen
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 Spielerbefehl COMMAND_SEEK_TO_PREVIOUS noch COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ist verfügbar. Außerdem steht ein benutzerdefinierter Befehl aus dem benutzerdefinierten Layout zur Verfügung, der noch nicht platziert wurde, um die Anzeigenfläche auszufüllen. Benutzerdefiniert
(noch nicht bei Media3 unterstützt) PlaybackState Extras enthalten 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 Spielerbefehl COMMAND_SEEK_TO_NEXT noch COMMAND_SEEK_TO_NEXT_MEDIA_ITEM ist verfügbar. Außerdem steht ein benutzerdefinierter Befehl aus dem benutzerdefinierten Layout zur Verfügung, der noch nicht platziert wurde, um die Anzeigenfläche auszufüllen. Benutzerdefiniert
(noch nicht bei Media3 unterstützt) PlaybackState Extras enthalten einen booleschen Wert true für den Schlüssel EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT. Leer
4 Für die Anzeigenfläche ist ein benutzerdefinierter Befehl aus dem benutzerdefinierten Layout verfügbar, der noch nicht platziert wurde. Benutzerdefiniert
5 Für die Anzeigenfläche ist ein benutzerdefinierter Befehl aus dem benutzerdefinierten Layout verfügbar, der noch nicht platziert wurde. Benutzerdefiniert

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

Befehlsschaltflächen anpassen

Wenn Sie die Mediensteuerung des Systems mit Jetpack Media3 anpassen möchten, können Sie das benutzerdefinierte Layout der Sitzung und die verfügbaren Controller-Befehle entsprechend festlegen, wenn Sie MediaSessionService implementieren:

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

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

  3. Antworten Sie in MediaSession.Callback.onCustomCommand() auf den benutzerdefinierten Befehl, der vom Nutzer ausgewählt wurde.

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 überlassen.

Wenn Sie mit Jetpack Media3 ein MediaSession implementieren, wird Ihr PlaybackState automatisch mit dem Mediaplayer auf dem neuesten Stand gehalten. Wenn Sie eine MediaSessionService implementieren, veröffentlicht die Bibliothek automatisch eine MediaStyle-Benachrichtigung für Sie und hält sie auf dem neuesten Stand.

Auf Aktionsschaltflächen reagieren

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

Eine Anleitung zum Antworten auf benutzerdefinierte Befehle finden Sie unter Benutzerdefinierte Befehle hinzufügen.

Verhalten vor Android 13

Aus Gründen der Abwärtskompatibilität stellt die System-UI weiterhin ein alternatives Layout bereit, das Benachrichtigungsaktionen für Apps verwendet, die nicht auf Android 13 aktualisiert werden oder keine PlaybackState-Informationen enthalten. Die Aktionsschaltflächen werden von der Notification.Action-Liste abgeleitet, die an die MediaStyle-Benachrichtigung 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 an setShowActionsInCompactView() übergebenen Werte bestimmt werden.

Benutzerdefinierte Aktionen werden in der Reihenfolge angeordnet, 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

Mit der Medienwiederaufnahme können Nutzer frühere Sitzungen über das Karussell neu 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 aktiviert und deaktiviert werden. Die Einstellungen können auch über das Zahnrad-Symbol aufgerufen werden.

Media3 bietet APIs, die die Wiederaufnahme von Medien vereinfachen. Eine Anleitung zur Implementierung dieser Funktion findest du in der Dokumentation zur Wiederaufnahme der Wiedergabe mit Media3.

Legacy-Medien-APIs verwenden

In diesem Abschnitt wird die Integration der Mediensteuerung des Systems mithilfe der Legacy-MediaCompat APIs erläutert.

Das System ruft die folgenden Informationen aus der MediaMetadata des 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 du eine gültige und genaue Mediensteuerungsbenachrichtigung erhältst, setze den Wert der Metadaten METADATA_KEY_TITLE oder METADATA_KEY_DISPLAY_TITLE auf den Titel der aktuell wiedergegebenen Medien.

Im Mediaplayer wird die verstrichene Zeit für das aktuell wiedergegebene Medium zusammen mit einer Steuerleiste angezeigt, die dem MediaSession-PlaybackState zugeordnet ist.

Der Mediaplayer zeigt den Fortschritt für die aktuell wiedergegebenen Medien an, zusammen mit einer Steuerleiste, die dem MediaSession-PlaybackState zugeordnet ist. Über die Steuerleiste können Nutzer die Position ändern und zeigt die verstrichene Zeit für das Medienelement an. Damit die Steuerleiste aktiviert wird, müssen Sie PlaybackState.Builder#setActions implementieren und ACTION_SEEK_TO einfügen.

Stellen Aktion Kriterien
1 Spielen Aktueller Status von PlaybackState ist einer der folgenden:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
Rotierendes Ladesymbol Aktueller Status von PlaybackState ist einer der folgenden:
  • STATE_CONNECTING
  • STATE_BUFFERING
Pausieren Aktueller Status von PlaybackState entspricht keiner der oben genannten Optionen.
2 Zurück PlaybackState Aktionen umfassen ACTION_SKIP_TO_PREVIOUS.
Benutzerdefiniert PlaybackState Aktionen enthalten keine ACTION_SKIP_TO_PREVIOUS und PlaybackState benutzerdefinierte Aktionen enthalten eine benutzerdefinierte Aktion, die noch nicht platziert wurde.
Leer PlaybackState Extras enthalten den 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 keine ACTION_SKIP_TO_NEXT und PlaybackState benutzerdefinierte Aktionen enthalten eine benutzerdefinierte Aktion, die noch nicht platziert wurde.
Leer PlaybackState Extras enthalten den booleschen Wert true für den Schlüssel SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT.
4 Benutzerdefiniert PlaybackState benutzerdefinierte Aktionen umfassen eine benutzerdefinierte Aktion, die noch nicht platziert wurde.
5 Benutzerdefiniert PlaybackState benutzerdefinierte Aktionen umfassen eine benutzerdefinierte Aktion, die noch nicht platziert wurde.

Standardaktionen hinzufügen

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

Legen Sie für „Wiedergabe“, „Pause“, „Zurück“ und „Weiter“ diese Aktionen 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 Sie in den vorherigen oder nächsten Slots keine Schaltflächen haben möchten, 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 auf den Mediensteuerelementen angezeigt werden sollen, kannst du ein PlaybackStateCompat.CustomAction erstellen und es stattdessen der 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 den Wiedergabestatus reagieren

Wenn ein Nutzer auf eine Schaltfläche tippt, verwendet die SystemUI MediaController.TransportControls, um einen Befehl an MediaSession zurückzusenden. Sie müssen 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);
        }
    }
};

Wiederaufnahme von Medien

Damit Ihre Spieler-App im Einstellungsbereich für die Schnelleinstellungen angezeigt wird, müssen Sie eine MediaStyle-Benachrichtigung mit einem gültigen MediaSession-Token erstellen.

Zum Anzeigen des Titels für die MediaStyle-Benachrichtigung verwenden Sie NotificationBuilder.setContentTitle().

Zum Anzeigen des Markensymbols für den Mediaplayer verwenden Sie NotificationBuilder.setSmallIcon().

Damit die Wiedergabe fortgesetzt werden kann, müssen Apps MediaBrowserService und MediaSession implementieren. Dein MediaSession muss den onPlay()-Callback implementieren.

Implementierung von MediaBrowserService

Nach dem Start des Geräts sucht das System nach den fünf zuletzt verwendeten Medien-Apps und bietet Steuerelemente, mit denen sich die Wiedergabe von jeder App neu starten lässt.

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

Verbindungen von SystemUI können anhand des Paketnamens com.android.systemui und der Signatur identifiziert und verifiziert 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 Wiedergabe fortgesetzt werden kann, muss der MediaBrowserService die folgenden Verhaltensweisen implementieren:

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

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

  • MediaBrowserService sollte das zuletzt wiedergegebene Mediaelement zurückgeben, wenn die Abfrage EXTRA_ umfasst. Der zurückgegebene Wert sollte ein tatsächliches Medienelement und keine generische Funktion sein.

  • MediaBrowserService muss eine entsprechende MediaDescription mit title und subtitle angeben, die nicht leer sind. 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);
}