Inhalte mit einem MediaLibraryService bereitstellen

Media-Apps enthalten häufig Sammlungen von Media-Elementen, die in einer Hierarchie organisiert sind. Das können beispielsweise Titel in einem Album oder Folgen einer Serie in einer Playlist sein. Diese Hierarchie von Medienelementen wird als Mediathek bezeichnet.

Beispiele für hierarchisch angeordnete Media-Inhalte
Abbildung 1: Beispiele für Hierarchien von Medienelementen, aus denen eine Mediathek besteht.

MediaLibraryService bietet eine standardisierte API für die Bereitstellung und den Zugriff 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 Benutzeroberfläche für Ihre Mediathek bietet.

MediaLibraryService erstellen

Die Implementierung von MediaLibraryService ähnelt der Implementierung von MediaSessionService. In der Methode onGetSession() sollten Sie jedoch MediaLibrarySession anstelle von MediaSession zurückgeben.

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();
  }
}

Denken Sie daran, dass Sie auch Ihre Service und die erforderlichen Berechtigungen in der Manifestdatei deklarieren müssen:

<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 wiedergegeben oder weiter durchsucht werden können.

Eine MediaLibrarySession erweitert die MediaSession API um APIs zum Durchsuchen von Inhalten. Im Vergleich zum MediaSession-Callback werden dem MediaLibrarySession-Callback Methoden wie die folgenden hinzugefügt:

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

Relevante Callback-Methoden enthalten ein LibraryParams-Objekt mit zusätzlichen Signalen zum Typ 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 im MediaMetadata unterstützt werden. So können einem Media-Element ein oder mehrere CommandButton-Einträge zugewiesen werden, 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 Gruppe von 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(),
    // 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(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 Media-Elements 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 das an den Callback übergebene ControllerInfo prüfen, um die maximale Anzahl von Schaltflächen zu ermitteln, die ein Controller oder Browser anzeigen kann. Die ControllerInfo, die an eine Callback-Methode übergeben wird, bietet einen Getter für den einfachen Zugriff auf diesen Wert. Standardmäßig ist der Wert auf 0 festgelegt. Das 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;
}

Wenn eine benutzerdefinierte Aktion für ein Media-Element verarbeitet wird, kann die Sitzungs-App die Media-Element-ID aus den Argumenten Bundle abrufen, die an onCustomCommand übergeben werden:

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 die maximale Anzahl von Befehlsschaltflächen deklarieren, die sie für ein Media-Element unterstützt, wenn sie die MediaController oder MediaBrowser erstellt:

Kotlin

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

Java

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

Wenn die Controller-App mit der Sitzung verbunden ist, kann sie die Befehlsschaltflächen empfangen, die vom Media-Element unterstützt werden und für die der Controller die von der Sitzungs-App gewährte verfügbare Berechtigung hat:

Kotlin

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

Java

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

Mit MediaController können Sie ganz einfach medienelementspezifische benutzerdefinierte Befehle mit MediaController.sendCustomCommand(SessionCommand, MediaItem, Bundle) senden:

Kotlin

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

Java

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