Inhalte mit einem MediaLibraryService bereitstellen

Medien-Apps enthalten oft Sammlungen von Medienelementen, die in einer Hierarchie organisiert sind. Beispiele sind Songs in einem Album oder Folgen einer Fernsehserie in einer Playlist. Diese Hierarchie von Medienelementen wird als Mediathek bezeichnet.

Beispiele für hierarchisch angeordnete Media-Inhalte
Abbildung 1: Beispiele für Hierarchien von Medienelementen, die eine Mediathek bilden.

Ein MediaLibraryService bietet eine standardisierte API zum Bereitstellen und Zugreifen auf Ihre Mediathek. Das kann beispielsweise hilfreich sein, wenn Sie Ihrer Medien-App Unterstützung für Android Auto hinzufügen, das eine eigene fahrersichere UI für Ihre Mediathek bietet.

MediaLibraryService erstellen

Die Implementierung eines MediaLibraryService ähnelt der Implementierung eines MediaSessionService. Der Unterschied besteht darin, dass Sie in der Methode onGetSession() eine MediaLibrarySession anstelle einer MediaSession zurückgeben sollten.

Kotlin

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

  override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? {
    // 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 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();
  }
}
Denken Sie daran, Ihren Service und die erforderlichen Berechtigungen auch in der Manifestdatei zu deklarieren:

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

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

MediaLibrarySession verwenden

Die MediaLibraryService API erwartet, dass Ihre Mediathek in einem Baumformat strukturiert ist, mit einem einzelnen Stammknoten und untergeordneten Knoten, die möglicherweise abspielbar oder weiter durchsuchbar sind.

Eine MediaLibrarySession erweitert die MediaSession API um APIs zum Durchsuchen von Inhalten. Im Vergleich zum MediaSession Callback, fügt das MediaLibrarySession Callback Methoden wie die folgenden hinzu:

  • onGetLibraryRoot() für den Fall, dass ein Client den Stammknoten (MediaItem) eines Inhaltsbaums anfordert
  • onGetChildren() für den Fall, dass ein Client die untergeordneten Elemente eines MediaItem im Inhaltsbaum anfordert
  • onGetSearchResult() für den Fall, dass ein Client Suchergebnisse aus dem Inhaltsbaum für eine bestimmte Abfrage anfordert

Relevante Callback-Methoden enthalten ein LibraryParams Objekt mit zusätzlichen Signalen zum Typ des Inhaltsbaums, an dem eine Client-App interessiert ist.

Befehlsschaltflächen für Medienelemente

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

Befehlsschaltflächen auf der Sitzungsseite 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))
      .setSessionCommand(SessionCommand(COMMAND_PLAYLIST_ADD, Bundle.EMPTY))
      .setExtras(playlistAddExtras)
      .build(),
    CommandButton.Builder(CommandButton.ICON_RADIO)
      .setDisplayName(context.getString(R.string.radio_station))
      .setSessionCommand(SessionCommand(COMMAND_RADIO, Bundle.EMPTY))
      .setExtras(radioExtras)
      .build(),
  )
// 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(context.getString(R.string.add_to_playlist))
            .setSessionCommand(new SessionCommand(COMMAND_PLAYLIST_ADD, Bundle.EMPTY))
            .setExtras(playlistAddExtras)
            .build(),
        new CommandButton.Builder(CommandButton.ICON_RADIO)
            .setDisplayName(context.getString(R.string.radio_station))
            .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 des Sitzungs-Callback aufruft, kann die Sitzungs-App die an das Callback übergebene ControllerInfo prüfen, um die maximale Anzahl von Befehlsschaltflächen zu ermitteln, die ein Controller oder Browser anzeigen kann. Die an eine Callback-Methode übergebene ControllerInfo bietet eine Getter-Methode, um bequem auf diesen Wert zuzugreifen. Standardmäßig ist der Wert auf 0 gesetzt, was darauf hinweist, 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
  loadMediaItemAsync(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;
}

Beim Verarbeiten einer benutzerdefinierten Aktion, die für ein Medienelement gesendet wurde, kann die Sitzungs-App die Medienelement-ID aus dem Bundle der Argumente abrufen, das an onCustomCommand übergeben wurde:

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 der MediaController-Seite kann eine App beim Erstellen des MediaController oder MediaBrowser die maximale Anzahl von Befehlsschaltflächen deklarieren, die sie für ein Medienelement unterstützt:

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 Controller-App die Befehlsschaltflächen empfangen, die vom Medienelement unterstützt werden und für die der Controller den verfügbaren Befehl von der Sitzungs-App erhalten hat:

Kotlin

val commandButtonsForMediaItem = controller.getCommandButtonsForMediaItem(mediaItem)

Java

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

Kotlin

val future =
  controller.sendCustomCommand(
    requireNotNull(addToPlaylistButton.sessionCommand),
    mediaItem,
    Bundle.EMPTY,
  )

Java

ListenableFuture<SessionResult> future =
    controller.sendCustomCommand(
        checkNotNull(addToPlaylistButton.sessionCommand), mediaItem, Bundle.EMPTY);