Diffuser du contenu avec MediaLibraryService

Les applications multimédias contiennent souvent des collections d'éléments multimédias, organisées de manière hiérarchique. Par exemple, les titres d'un album ou les épisodes d'une série TV dans une playlist. Cette hiérarchie d'éléments multimédias est appelée bibliothèque multimédia.

Exemples de contenus multimédias organisés en hiérarchie
Figure 1: Exemples de hiérarchies d'éléments multimédias formant une bibliothèque multimédia.

Un MediaLibraryService fournit une API standardisée pour diffuser et accéder à votre bibliothèque multimédia. Cela peut être utile, par exemple, lorsque vous ajoutez la compatibilité avec Android Auto à votre application multimédia, qui fournit sa propre interface utilisateur adaptée aux conducteurs pour votre bibliothèque multimédia.

Créer un MediaLibraryService

L'implémentation d'un MediaLibraryService est semblable à l'implémentation d'un MediaSessionService, à l'exception que dans la méthode onGetSession(), vous devez renvoyer un MediaLibrarySession au lieu d'un MediaSession.

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

N'oubliez pas de déclarer votre Service et les autorisations requises dans le fichier manifeste:

<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" />

Utiliser un MediaLibrarySession

L'API MediaLibraryService s'attend à ce que votre bibliothèque multimédia soit structurée sous forme d'arborescence, avec un seul nœud racine et des nœuds enfants pouvant être lus ou consultés.

Un MediaLibrarySession étend l'API MediaSession pour ajouter des API de navigation dans le contenu. Par rapport au rappel MediaSession, le rappel MediaLibrarySession ajoute des méthodes telles que:

  • onGetLibraryRoot() pour qu'un client demande la racine MediaItem d'une arborescence de contenu
  • onGetChildren() pour lorsqu'un client demande les enfants d'un MediaItem dans l'arborescence de contenu
  • onGetSearchResult() pour qu'un client demande les résultats de recherche de l'arborescence de contenu pour une requête donnée

Les méthodes de rappel pertinentes incluront un objet LibraryParams avec des signaux supplémentaires sur le type d'arborescence de contenu qui intéresse une application cliente.

Boutons de commande pour les éléments multimédias

Une application de session peut déclarer des boutons de commande compatibles avec un MediaItem dans le MediaMetadata. Cela permet d'attribuer une ou plusieurs entrées CommandButton à un élément multimédia qu'un contrôleur peut afficher et utiliser pour envoyer de manière pratique la commande personnalisée de l'élément à la session.

Boutons de commande de configuration du côté de la session

Lors de la création de la session, une application de session déclare l'ensemble des boutons de commande qu'une session peut gérer en tant que commandes personnalisées:

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

Lors de la création d'un élément multimédia, une application de session peut ajouter un ensemble d'ID de commande compatibles qui font référence aux commandes de session des boutons de commande configurés lors de la création de la session:

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

Lorsqu'un contrôleur ou un navigateur se connecte ou appelle une autre méthode de la session Callback, l'application de session peut inspecter le ControllerInfo transmis au rappel pour obtenir le nombre maximal de boutons de commande qu'un contrôleur ou un navigateur peut afficher. L'ControllerInfo transmis à une méthode de rappel fournit un getter pour accéder facilement à cette valeur. Par défaut, la valeur est définie sur 0, ce qui indique que le navigateur ou le contrôleur n'est pas compatible avec cette fonctionnalité:

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

Lors de la gestion d'une action personnalisée envoyée pour un élément multimédia, l'application de session peut obtenir l'ID de l'élément multimédia à partir des arguments Bundle transmis à onCustomCommand:

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

Utiliser les boutons de commande comme navigateur ou manette

Côté MediaController, une application peut déclarer le nombre maximal de boutons de commande qu'elle prend en charge pour un élément multimédia lors de la création de MediaController ou MediaBrowser:

Kotlin

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

Java

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

Lorsqu'elle est connectée à la session, l'application de contrôleur peut recevoir les boutons de commande compatibles avec l'élément multimédia et pour lesquels le contrôleur dispose de la commande disponible accordée par l'application de session:

Kotlin

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

Java

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

Pour plus de commodité, un MediaController peut envoyer des commandes personnalisées spécifiques à l'élément multimédia avec MediaController.sendCustomCommand(SessionCommand, MediaItem, Bundle):

Kotlin

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

Java

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