Inhalte mit einem MediaLibraryService bereitstellen

Medien-Apps enthalten oft Sammlungen von Medienelementen, die in einer Hierarchie organisiert sind. Das können beispielsweise Titel in einem Album oder Serienfolgen in einer Playlist sein. Diese Hierarchie von Medienelementen wird als Medienbibliothek bezeichnet.

Beispiele für Medieninhalte, die in einer Hierarchie angeordnet sind
Abbildung 1: Beispiele für Hierarchien von Medienelementen, die eine Medienbibliothek bilden.

Ein MediaLibraryService bietet eine standardisierte API zum Bereitstellen und Zugriff auf deine Medienbibliothek. Das kann beispielsweise hilfreich sein, wenn Sie Ihrer Medien-App die Unterstützung für Android Auto hinzufügen, die eine eigene fahrerfreundliche Benutzeroberfläche für Ihre Medienbibliothek bietet.

MediaLibraryService erstellen

Die Implementierung einer MediaLibraryService ähnelt der Implementierung einer MediaSessionService, mit der Ausnahme, dass in der onGetSession()-Methode eine MediaLibrarySession anstelle einer MediaSession zurückgegeben werden sollte.

Kotlin

class PlaybackService : MediaLibraryService() {
  var mediaLibrarySession: MediaLibrarySession? = null
  var callback: MediaLibrarySession.Callback = object : MediaLibrarySession.Callback {...}

  // If desired, validate the controller before returning the media library session
  override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? =
    mediaLibrarySession

  // Create your player and media library session in the onCreate lifecycle event
  override fun onCreate() {
    super.onCreate()
    val player = ExoPlayer.Builder(this).build()
    mediaLibrarySession = MediaLibrarySession.Builder(this, player, callback).build()
  }

  // Remember to release the player and media library session in onDestroy
  override fun onDestroy() {
    mediaLibrarySession?.run { 
      player.release()
      release()
      mediaLibrarySession = null
    }
    super.onDestroy()
  }
}

Java

class PlaybackService extends MediaLibraryService {
  MediaLibrarySession mediaLibrarySession = null;
  MediaLibrarySession.Callback callback = new MediaLibrarySession.Callback() {...};

  @Override
  public MediaLibrarySession onGetSession(MediaSession.ControllerInfo controllerInfo) {
    // If desired, validate the controller before returning the media library session
    return mediaLibrarySession;
  }

  // Create your player and media library session in the onCreate lifecycle event
  @Override
  public void onCreate() {
    super.onCreate();
    ExoPlayer player = new ExoPlayer.Builder(this).build();
    mediaLibrarySession = new MediaLibrarySession.Builder(this, player, callback).build();
  }

  // Remember to release the player and media library session in onDestroy
  @Override
  public void onDestroy() {
    if (mediaLibrarySession != null) {
      mediaLibrarySession.getPlayer().release();
      mediaLibrarySession.release();
      mediaLibrarySession = null;
    }
    super.onDestroy();
  }
}

Deklarieren Sie auch Ihre Service und die erforderlichen Berechtigungen in der Manifestdatei:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- For targetSdk 34+ -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

MediaLibrarySession verwenden

Die MediaLibraryService API erwartet, dass deine Medienbibliothek in einem Baumformat mit einem einzelnen Stammknoten und untergeordneten Knoten strukturiert ist, die abgespielt oder weiter durchsucht werden können.

Eine MediaLibrarySession erweitert die MediaSession API um APIs für die Inhaltssuche. Im Vergleich zum MediaSession-Callback bietet der MediaLibrarySession-Callback zusätzliche Methoden wie:

  • onGetLibraryRoot() wird verwendet, wenn ein Client den Stamm MediaItem eines Inhaltsbaums anfordert
  • onGetChildren() wird verwendet, wenn ein Client die untergeordneten Elemente eines MediaItem im Inhaltsbaum anfordert
  • onGetSearchResult() wird verwendet, wenn ein Client Suchergebnisse aus dem Inhaltsbaum für eine bestimmte Suchanfrage anfordert

Relevante Rückrufmethoden enthalten ein LibraryParams-Objekt mit zusätzlichen Signalen zur Art des Inhaltsbaums, für den sich eine Client-App interessiert.

Befehlsschaltflächen für Medienelemente

Eine Sitzungs-App kann Befehlsschaltflächen deklarieren, die von einem MediaItem in der MediaMetadata unterstützt werden. So können einem Medienelement ein oder mehrere CommandButton-Einträge zugewiesen werden, die ein Controller anzeigen und verwenden kann, um den benutzerdefinierten Befehl für das Element auf einfache Weise an die Sitzung zu senden.

Befehlsschaltflächen auf Sitzungsebene einrichten

Beim Erstellen der Sitzung deklariert eine Sitzungs-App die Befehlsschaltflächen, die eine Sitzung als benutzerdefinierte Befehle verarbeiten kann:

Kotlin

val allCommandButtons =
  listOf(
    CommandButton.Builder(CommandButton.ICON_PLAYLIST_ADD)
      .setDisplayName(context.getString(R.string.add_to_playlist))
      .setDisplayName("Add to playlist")
      .setIconResId(R.drawable.playlist_add)
      .setSessionCommand(SessionCommand(COMMAND_PLAYLIST_ADD, Bundle.EMPTY))
      .setExtras(playlistAddExtras)
      .build(),
    CommandButton.Builder(CommandButton.ICON_RADIO)
      .setDisplayName(context.getString(R.string.radio_station))
      .setIconResId(R.drawable.radio)
      .setSessionCommand(SessionCommand(COMMAND_RADIO, Bundle.EMPTY))
      .setExtras(radioExtras)
      .build(),
    // possibly more here
  )

// Add all command buttons for media items supported by the session.
val session =
  MediaSession.Builder(context, player)
    .setCommandButtonsForMediaItems(allCommandButtons)
    .build()

Java

ImmutableList<CommandButton> allCommandButtons =
    ImmutableList.of(
        new CommandButton.Builder(CommandButton.ICON_PLAYLIST_ADD)
            .setDisplayName("Add to playlist")
            .setIconUri(Uri.parse("http://www.example.com/icon/playlist_add"))
            .setSessionCommand(new SessionCommand(COMMAND_PLAYLIST_ADD, Bundle.EMPTY))
            .setExtras(playlistAddExtras)
            .build(),
        new CommandButton.Builder(CommandButton.ICON_RADIO)
            .setDisplayName("Radio station")
            .setIconUri(Uri.parse("http://www.example.com/icon/radio"))
            .setSessionCommand(new SessionCommand(COMMAND_RADIO, Bundle.EMPTY))
            .setExtras(radioExtras)
            .build());

// Add all command buttons for media items supported by the session.
MediaSession session =
    new MediaSession.Builder(context, player)
        .setCommandButtonsForMediaItems(allCommandButtons)
        .build();

Beim Erstellen eines Medienelements kann eine Sitzungs-App eine Reihe unterstützter Befehls-IDs hinzufügen, die auf Sitzungsbefehle von Befehlsschaltflächen verweisen, die beim Erstellen der Sitzung eingerichtet wurden:

Kotlin

val mediaItem =
  MediaItem.Builder()
    .setMediaMetadata(
      MediaMetadata.Builder()
        .setSupportedCommands(listOf(COMMAND_PLAYLIST_ADD, COMMAND_RADIO))
        .build())
    .build()

Java

MediaItem mediaItem =
    new MediaItem.Builder()
        .setMediaMetadata(
            new MediaMetadata.Builder()
                .setSupportedCommands(ImmutableList.of(COMMAND_PLAYLIST_ADD, COMMAND_RADIO))
                .build())
        .build();

Wenn ein Controller oder Browser eine Verbindung herstellt oder eine andere Methode der Sitzung Callback aufruft, kann die Sitzungs-App die an den Rückruf übergebene ControllerInfo prüfen, um die maximale Anzahl von Befehlsschaltflächen abzurufen, die ein Controller oder Browser anzeigen kann. Der ControllerInfo, der an eine Callback-Methode übergeben wird, bietet einen Getter, um bequem auf diesen Wert zuzugreifen. Standardmäßig ist der Wert auf „0“ festgelegt, was bedeutet, dass der Browser oder Controller diese Funktion nicht unterstützt:

Kotlin

override fun onGetItem(
  session: MediaLibrarySession,
  browser: MediaSession.ControllerInfo,
  mediaId: String,
): ListenableFuture<LibraryResult<MediaItem>> {

  val settableFuture = SettableFuture.create<LibraryResult<MediaItem>>()

  val maxCommandsForMediaItems = browser.maxCommandsForMediaItems
  scope.launch {
    loadMediaItem(settableFuture, mediaId, maxCommandsForMediaItems)
  }

  return settableFuture
}

Java

@Override
public ListenableFuture<LibraryResult<MediaItem>> onGetItem(
    MediaLibraryService.MediaLibrarySession session, ControllerInfo browser, String mediaId) {

  SettableFuture<LibraryResult<MediaItem>> settableFuture = SettableFuture.create();

  int maxCommandsForMediaItems = browser.getMaxCommandsForMediaItems();
  loadMediaItemAsync(settableFuture, mediaId, maxCommandsForMediaItems);

  return settableFuture;
}

Bei der Verarbeitung einer benutzerdefinierten Aktion, die für ein Medienelement gesendet wurde, kann die Sitzungs-App die Medienelement-ID aus den Argumenten Bundle abrufen, die an onCustomCommand übergeben wurden:

Kotlin

override fun onCustomCommand(
  session: MediaSession,
  controller: MediaSession.ControllerInfo,
  customCommand: SessionCommand,
  args: Bundle,
): ListenableFuture<SessionResult> {
  val mediaItemId = args.getString(MediaConstants.EXTRA_KEY_MEDIA_ID)
  return if (mediaItemId != null)
    handleCustomCommandForMediaItem(controller, customCommand, mediaItemId, args)
  else handleCustomCommand(controller, customCommand, args)
}

Java

@Override
public ListenableFuture<SessionResult> onCustomCommand(
    MediaSession session,
    ControllerInfo controller,
    SessionCommand customCommand,
    Bundle args) {
  String mediaItemId = args.getString(MediaConstants.EXTRA_KEY_MEDIA_ID);
  return mediaItemId != null
      ? handleCustomCommandForMediaItem(controller, customCommand, mediaItemId, args)
      : handleCustomCommand(controller, customCommand, args);
}

Befehlsschaltflächen als Browser oder Controller verwenden

Auf MediaController-Seite kann eine App beim Erstellen der MediaController oder MediaBrowser die maximale Anzahl von Befehlsschaltflächen angeben, die für ein Medienelement unterstützt werden:

Kotlin

val browserFuture =
  MediaBrowser.Builder(context, sessionToken)
    .setMaxCommandsForMediaItems(3)
    .buildAsync()

Java

ListenableFuture<MediaBrowser> browserFuture =
    new MediaBrowser.Builder(context, sessionToken)
        .setMaxCommandsForMediaItems(3)
        .buildAsync();

Wenn eine Verbindung zur Sitzung besteht, kann die Steuerelement-App die Befehlsschaltflächen empfangen, die vom Medienelement unterstützt werden und für die der Controller den von der Sitzungs-App gewährten Befehl hat:

Kotlin

val commandButtonsForMediaItem: List<CommandButton> =
  controller.getCommandButtonsForMediaItem(mediaItem)

Java

ImmutableList<CommandButton> commandButtonsForMediaItem =
    controller.getCommandButtonsForMediaItem(mediaItem);

Für den Komfort kann ein MediaController benutzerdefinierte Befehle für Medienelemente mit MediaController.sendCustomCommand(SessionCommand, MediaItem, Bundle) senden:

Kotlin

controller.sendCustomCommand(addToPlaylistButton.sessionCommand!!, mediaItem, Bundle.EMPTY)

Java

controller.sendCustomCommand(
    checkNotNull(addToPlaylistButton.sessionCommand), mediaItem, Bundle.EMPTY);