Обслуживание контента с помощью MediaLibraryService

Медиа-приложения часто содержат коллекции медиа-элементов, организованных в иерархию. Например, песни в альбоме или сериалы в плейлисте. Эта иерархия медиа-элементов известна как медиа-библиотека.

Примеры медиаконтента, расположенные в иерархии
Рисунок 1. Примеры иерархий медиа-элементов, образующих медиа-библиотеку.

MediaLibraryService предоставляет стандартизированный API для обслуживания и доступа к вашей медиатеке. Это может быть полезно, например, при добавлении поддержки Android Auto в ваше медиа-приложение, которое предоставляет собственный безопасный для драйверов пользовательский интерфейс для вашей медиатеки.

Создание службы MediaLibraryService

Реализация MediaLibraryService аналогична реализации MediaSessionService , за исключением того, что в методе onGetSession() вы должны возвращать MediaLibrarySession вместо MediaSession .

Котлин

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

Ява

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

Не забудьте также объявить свою Service и необходимые разрешения в файле манифеста:

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

Используйте сеанс MediaLibrarySession

API MediaLibraryService предполагает, что ваша медиатека будет структурирована в виде дерева с одним корневым узлом и дочерними узлами, которые можно воспроизводить или просматривать в дальнейшем.

MediaLibrarySession расширяет API MediaSession , добавляя API просмотра контента. По сравнению с обратным вызовом MediaSession , обратный вызов MediaLibrarySession добавляет такие методы, как:

  • onGetLibraryRoot() , когда клиент запрашивает корневой MediaItem дерева контента.
  • onGetChildren() , когда клиент запрашивает дочерние элементы MediaItem в дереве контента
  • onGetSearchResult() когда клиент запрашивает результаты поиска из дерева контента для данного запроса

Соответствующие методы обратного вызова будут включать объект LibraryParams с дополнительными сигналами о типе дерева контента, который интересует клиентское приложение.

Кнопки управления для элементов мультимедиа

Приложение сеанса может объявлять командные кнопки, которые поддерживаются MediaItem в MediaMetadata . Это позволяет назначить одну или несколько записей CommandButton элементу мультимедиа, который контроллер может отображать и использовать для отправки пользовательской команды для элемента в сеанс удобным способом.

Настройка командных кнопок на стороне сеанса

При построении сеанса приложение сеанса объявляет набор командных кнопок, которые сеанс может обрабатывать, как пользовательские команды:

Котлин

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

Ява

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

При создании элемента мультимедиа приложение сеанса может добавить набор поддерживаемых идентификаторов команд, которые ссылаются на команды сеанса командных кнопок, которые были настроены при создании сеанса:

Котлин

val mediaItem =
  MediaItem.Builder()
    .setMediaMetadata(
      MediaMetadata.Builder()
        .setSupportedCommands(listOf(COMMAND_PLAYLIST_ADD, COMMAND_RADIO))
        .build())
    .build()

Ява

MediaItem mediaItem =
    new MediaItem.Builder()
        .setMediaMetadata(
            new MediaMetadata.Builder()
                .setSupportedCommands(ImmutableList.of(COMMAND_PLAYLIST_ADD, COMMAND_RADIO))
                .build())
        .build();

Когда контроллер или браузер подключается или вызывает другой метод Callback сеанса, приложение сеанса может проверить ControllerInfo переданное в обратный вызов, чтобы получить максимальное количество командных кнопок, которые может отобразить контроллер или браузер. ControllerInfo , переданный в метод обратного вызова, предоставляет метод получения для удобного доступа к этому значению. По умолчанию значение установлено на 0, что указывает на то, что браузер или контроллер не поддерживает эту функцию:

Котлин

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
}

Ява

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

При обработке специального действия, отправленного для элемента мультимедиа, приложение сеанса может получить идентификатор элемента мультимедиа из аргументов Bundle переданных в onCustomCommand :

Котлин

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

Ява

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

Используйте командные кнопки в качестве браузера или контроллера

На стороне MediaController приложение может объявить максимальное количество поддерживаемых командных кнопок для медиа-элемента при создании MediaController или MediaBrowser :

Котлин

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

Ява

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

При подключении к сеансу приложение-контроллер может получать командные кнопки, которые поддерживаются элементом мультимедиа и для которых у контроллера есть доступная команда, предоставленная приложением сеанса :

Котлин

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

Ява

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

Для удобства MediaController может отправлять специальные команды для конкретных медиа-элементов с помощью MediaController.sendCustomCommand(SessionCommand, MediaItem, Bundle) :

Котлин

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

Ява

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