Inhalte mit einem MediaLibraryService bereitstellen

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

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

Ein MediaLibraryService bietet eine standardisierte API, um Ihre Mediathek bereitzustellen und darauf zuzugreifen. Das kann beispielsweise hilfreich sein, wenn du deiner Medien-App Unterstützung für Android Auto hinzufügst, das eine eigene, für Autofahrer sichere Benutzeroberfläche für deine Mediathek bietet.

MediaLibraryService erstellen

Die Implementierung eines MediaLibraryService ähnelt der Implementierung eines MediaSessionService, mit dem Unterschied, dass Sie in der Methode onGetSession() einen MediaLibrarySession anstelle eines 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, auch Ihre Service und die erforderlichen Berechtigungen in der Manifestdatei zu deklarieren:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
        <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" />

Es wird empfohlen, den Dienst sowohl als Plattform- als auch als Media3-Dienstschnittstelle zu registrieren:

  • Aus Gründen der Kompatibilität mit Clients, die die MediaSession APIs der Plattform verwenden, wird empfohlen, <action android:name="android.media.browse.MediaBrowserService"/> in das intent-filter-Element aufzunehmen. Media3 bietet automatisch Abwärtskompatibilität für diese Dienstschnittstelle.

  • Als zukunftssichere Einrichtung und um sicherzustellen, dass Apps, die Media3 verwenden, über die Media3 API mit Ihrem Dienst kommunizieren können, sollte <action android:name="androidx.media3.session.MediaLibraryService"/> zusammen mit der Plattformoption bereitgestellt werden.

So können Apps Ihren Dienst über PackageManager finden und über eine dieser Schnittstellen eine Verbindung zu einem MediaBrowser herstellen.

MediaLibrarySession verwenden

Die MediaLibraryService API erwartet, dass deine Mediathek in einem Baumformat strukturiert ist, mit einem einzelnen Stammknoten und untergeordneten Knoten, die abspielbar oder weiter durchsuchbar sein können.

Ein MediaLibrarySession erweitert die MediaSession API, um APIs zum Durchsuchen von Inhalten hinzuzufügen. Im Vergleich zum MediaSession-Callback werden beim MediaLibrarySession-Callback Methoden wie die folgenden hinzugefügt:

  • onGetLibraryRoot(), wenn ein Client die Stamm-MediaItem eines Inhaltsbaums anfordert
  • onGetChildren(), wenn ein Client die untergeordneten Elemente eines MediaItem im Inhaltsbaum anfordert
  • onGetSearchResult(), wenn 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 Mediendateien

Eine Sitzungs-App kann Befehlsschaltflächen deklarieren, die von einem MediaItem in der MediaMetadata unterstützt werden. So können einer Mediendatei 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 für die Einrichtung auf der Sitzungsseite

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 einer Mediendatei kann eine Sitzungs-App eine Reihe von unterstützten 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 Callback übergebene ControllerInfo prüfen, um die maximale Anzahl von Befehlsschaltflächen zu ermitteln, die ein Controller oder Browser anzeigen kann. Der an eine Callback-Methode übergebene ControllerInfo stellt einen Getter bereit, mit dem Sie bequem auf diesen Wert zugreifen können. Standardmäßig ist der Wert auf 0 gesetzt, 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
  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;
}

Wenn eine benutzerdefinierte Aktion verarbeitet wird, die für ein Medienelement gesendet wurde, kann die Sitzungs-App die Medienelement-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 beim Erstellen des MediaController oder MediaBrowser die maximale Anzahl von Befehlsschaltflächen deklarieren, die sie für eine Mediendatei 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 die Controller-App mit der Session verbunden ist, kann sie die Befehlsschaltflächen empfangen, die vom Medienelement unterstützt werden und für die der Controller den von der Session-App gewährten verfügbaren Befehl 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);