Pubblicazione dei contenuti con MediaLibraryService

Le app multimediali spesso contengono raccolte di elementi multimediali, organizzati in una gerarchia. Ad esempio, i brani di un album o le puntate di un programma TV in una playlist. Questa gerarchia di elementi multimediali è nota come raccolta multimediale.

Esempi di contenuti multimediali organizzati in una gerarchia
Figura 1: esempi di gerarchie di elementi multimediali che formano una raccolta multimediale.

Un MediaLibraryService fornisce un'API standardizzata per pubblicare e accedere alla tua raccolta multimediale. Questo può essere utile, ad esempio, quando aggiungi il supporto di Android Auto alla tua app multimediale, che fornisce la propria UI sicura per il conducente per la tua raccolta multimediale.

Crea un MediaLibraryService

L'implementazione di un MediaLibraryService è simile all'implementazione di un MediaSessionService, con la differenza che nel metodo onGetSession() devi restituire un MediaLibrarySession anziché un MediaSession.

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();
  }
}
Ricorda di dichiarare anche Service e le autorizzazioni richieste nel file manifest:

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

Ti consigliamo di registrare il tuo servizio sia come piattaforma sia come interfaccia del servizio Media3:

  • Per la compatibilità con i client che utilizzano le API della sessione multimediale della piattaforma, ti consigliamo di includere <action android:name="android.media.browse.MediaBrowserService"/> nell'elemento intent-filter. Media3 fornisce automaticamente la compatibilità con le versioni precedenti di questa interfaccia di servizio.

  • Per una configurazione a prova di futuro e per garantire che le app che utilizzano Media3 possano comunicare con il tuo servizio utilizzando l'API Media3, <action android:name="androidx.media3.session.MediaLibraryService"/> deve essere fornito insieme all'opzione della piattaforma.

In questo modo, le app possono trovare il tuo servizio tramite PackageManager e connettersi con un MediaBrowser tramite una di queste interfacce.

Utilizza un MediaLibrarySession

L'API MediaLibraryService prevede che la tua raccolta multimediale sia strutturata in un formato ad albero, con un singolo nodo principale e nodi secondari che possono essere riproducibili o ulteriormente sfogliabili.

Un MediaLibrarySession estende l'API MediaSession per aggiungere API di navigazione dei contenuti. Rispetto al callback MediaSession, il callback MediaLibrarySession aggiunge metodi come:

  • onGetLibraryRoot() per quando un client richiede la radice MediaItem di un albero dei contenuti
  • onGetChildren() per quando un client richiede gli elementi secondari di un MediaItem nell'albero dei contenuti
  • onGetSearchResult() per quando un client richiede i risultati di ricerca dall'albero dei contenuti per una determinata query

I metodi di callback pertinenti includeranno un oggetto LibraryParams con indicatori aggiuntivi sul tipo di albero dei contenuti a cui è interessata un'app client.

Pulsanti di comando per gli elementi multimediali

Un'app di sessione può dichiarare i pulsanti di comando supportati da un MediaItem in MediaMetadata. In questo modo, è possibile assegnare una o più voci CommandButton a un elemento multimediale che un controller può visualizzare e utilizzare per inviare il comando personalizzato per l'elemento alla sessione in modo pratico.

Configura i pulsanti di comando sul lato della sessione

Durante la creazione della sessione, un'app di sessione dichiara l'insieme di pulsanti di comando che una sessione può gestire come comandi personalizzati:

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

Durante la creazione di un elemento multimediale, un'app di sessione può aggiungere un insieme di ID comando supportati che fanno riferimento ai comandi di sessione dei pulsanti di comando configurati durante la creazione della sessione:

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

Quando un controller o un browser si connette o chiama un altro metodo della sessione Callback, l'app della sessione può ispezionare il ControllerInfo passato al callback per ottenere il numero massimo di pulsanti di comando che un controller o un browser può visualizzare. L'oggetto ControllerInfo passato a un metodo di callback fornisce un getter per accedere comodamente a questo valore. Per impostazione predefinita, il valore è impostato su 0, il che indica che il browser o il controller non supporta questa funzionalità:

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

Quando gestisce un'azione personalizzata inviata per un elemento multimediale, l'app della sessione può ottenere l'ID articolo dagli argomenti Bundle passati a 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);
}

Utilizza i pulsanti di comando come browser o controller

Sul lato MediaController, un'app può dichiarare il numero massimo di pulsanti di comando che supporta per un elemento multimediale durante la creazione di MediaController o MediaBrowser:

Kotlin

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

Java

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

Quando è connessa alla sessione, l'app controller può ricevere i pulsanti di comando supportati dall'elemento multimediale e per i quali il controller dispone del comando disponibile concesso dall'app sessione:

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