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

サービスは、プラットフォームと Media3 サービス インターフェースの両方として登録することをおすすめします。

  • プラットフォーム メディア セッション API を使用するクライアントとの互換性を確保するため、 <action android:name="android.media.browse.MediaBrowserService"/>intent-filter 要素に含めることをおすすめします。Media3 は、このサービス インターフェースとの下位互換性を自動的に提供します。

  • 将来に備えた設定として、Media3 を使用するアプリが Media3 API を使用してサービスと通信できるようにするには、 プラットフォーム オプションとともに <action android:name="androidx.media3.session.MediaLibraryService"/> を 指定する必要があります。

これにより、アプリは PackageManager を介してサービスを検索し、これらのインターフェースのいずれかを介して MediaBrowser に接続できます。

MediaLibrarySession を使用する

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

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