MediaLibraryService を使用してコンテンツを提供する

メディアアプリには、階層構造で整理されたメディア アイテムのコレクションが含まれていることがよくあります。 たとえば、アルバム内の曲やプレイリスト内のテレビ番組などです。このメディア アイテムの階層は、メディア ライブラリと呼ばれます。

階層構造で配置されたメディア コンテンツの例
図 1: メディア ライブラリを構成するメディア アイテムの階層の例。

MediaLibraryService は、メディア ライブラリの提供とアクセスを行うための標準化された API を提供します。これは、たとえば、メディアアプリに Android Auto のサポートを追加する場合に役立ちます。Android Auto は、 メディア ライブラリ用の独自の安全運転 UI を提供します。

MediaLibraryService をビルドする

MediaLibraryService の実装は MediaSessionService の実装と似ていますが、onGetSession() メソッドでは MediaSession ではなく MediaLibrarySession を返す必要があります。

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();
  }
}
マニフェスト ファイルで Service と必要な権限も宣言してください。

<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 を使用する

MediaLibraryService API は、メディア ライブラリが ツリー形式で構造化されていることを想定しています。つまり、1 つのルートノードと、 再生可能またはさらにブラウジング可能な子ノードで構成されていることを想定しています。

MediaLibrarySessionMediaSession API を拡張して、コンテンツ ブラウジング API を追加します。MediaSession コールバックと比較して、 MediaLibrarySession コールバックには次のようなメソッドが追加されています。

  • onGetLibraryRoot() クライアントが コンテンツ ツリーのルート MediaItem をリクエストした場合
  • onGetChildren() クライアントがコンテンツ ツリー内の MediaItem の子をリクエストした場合
  • onGetSearchResult() クライアントが特定のクエリの コンテンツ ツリーから検索結果をリクエストした場合

関連するコールバック メソッドには、LibraryParams オブジェクトと 、クライアント アプリが 関心を持っているコンテンツ ツリーのタイプに関する追加のシグナルが含まれます。

メディア アイテムのコマンドボタン

セッション アプリは、MediaMetadataMediaItem でサポートされているコマンドボタンを宣言できます。これにより、コントローラがメディア アイテムに 1 つ以上の CommandButton エントリを割り当てて表示し、アイテムのカスタム コマンドをセッションに簡単に送信できるようになります。

セッション側でコマンドボタンを設定する

セッションをビルドするときに、セッション アプリは、セッションがカスタム コマンドとして処理できるコマンドボタンのセットを宣言します。

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

メディア アイテムをビルドするときに、セッション アプリは、セッションのビルド時に設定されたコマンドボタンのセッション コマンドを参照する、サポートされているコマンド ID のセットを追加できます。

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

コントローラまたはブラウザが接続するか、セッション Callback の別のメソッドを呼び出すと、セッション アプリはコールバックに渡された ControllerInfo を調べて、コントローラまたはブラウザが表示できるコマンドボタンの最大数を取得できます。コールバック メソッドに渡される ControllerInfo には、この値に簡単にアクセスできるゲッターが用意されています。デフォルトでは、値は 0 に設定されています。これは、ブラウザまたはコントローラがこの機能をサポートしていないことを示します。

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

メディア アイテムに送信されたカスタム アクションを処理するときに、セッション アプリは onCustomCommand に渡された引数 Bundle からメディア アイテム ID を取得できます。

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

コマンドボタンをブラウザまたはコントローラとして使用する

MediaController 側では、アプリは MediaController または MediaBrowser をビルドするときに、メディア アイテムでサポートするコマンドボタンの最大数を宣言できます。

Kotlin

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

Java

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

セッションに接続すると、コントローラ アプリは、メディア アイテムでサポートされている コマンドボタンと、 セッション アプリによってコントローラに付与された使用可能なコマンドを受け取ることができます。

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