メディアアプリに接続する

メディアアプリに接続するには、次の 2 つの方法があります。

  1. MediaController
  2. MediaBrowser

MediaController

メディア コントローラは、メディア セッションとやり取りして、メディアアプリの再生をクエリして制御します。Media3 では、MediaController API が Player インターフェースを実装します。メディア コントローラを使用するクライアント アプリの例を次に示します。

メディア コントローラは、メディアアプリ内でも役立ちます。たとえば、プレーヤーとメディア セッションが、UI を含む Activity または Fragment とは別の Service に存在する場合です。

MediaController を作成する

MediaController を作成するには、まず、対応する MediaSession の SessionToken を作成します。これには、Activity または Fragment の onStart() メソッドが適しています。

Kotlin

val sessionToken = SessionToken(context, ComponentName(context, PlaybackService::class.java))

Java

SessionToken sessionToken =
    new SessionToken(context, new ComponentName(context, PlaybackService.class));

この SessionToken を使用して MediaController をビルドすると、コントローラが指定されたセッションに接続されます。この処理は非同期で行われるため、結果をリッスンし、利用可能になった時点で結果を使用する必要があります。

Kotlin

val controllerFuture = MediaController.Builder(context, sessionToken).buildAsync()
controllerFuture.addListener(
  {
    // MediaController is available here with controllerFuture.get()
  },
  MoreExecutors.directExecutor(),
)

Java

ListenableFuture<MediaController> controllerFuture =
    new MediaController.Builder(context, sessionToken).buildAsync();
controllerFuture.addListener(
    () -> {
      // MediaController is available here with controllerFuture.get()
    },
    MoreExecutors.directExecutor());

MediaController を使用する

MediaController は Player インターフェースを実装しているため、インターフェースで定義されているコマンドを使用して、接続された MediaSession の再生を制御できます。つまり、MediaController で play() を呼び出すと、接続された MediaSession にコマンドが送信され、その後、基盤となる Player にコマンドが委任されます。

コントローラに Player.Listener を追加して、Player 状態の変化をリッスンできます。Player.Listener の使用方法について詳しくは、プレーヤー イベントのガイドをご覧ください。

MediaController.Listener インターフェースは、接続された MediaSession からのイベントとカスタム コマンドの追加のコールバックを定義します。たとえば、セッションがカスタム コマンドを送信した場合は onCustomCommand()、セッションが利用可能なセッション コマンドを変更した場合は onAvailableSessionCommandsChanged()、コントローラがセッションから切断された場合は onDisconnected() です。

MediaController.Listener は、Builder を使用してコントローラをビルドするときに設定できます。

Kotlin

val controllerFuture =
  MediaController.Builder(context, sessionToken)
    .setListener(
      object : MediaController.Listener {
        override fun onCustomCommand(
          controller: MediaController,
          command: SessionCommand,
          args: Bundle,
        ): ListenableFuture<SessionResult> {
          // Handle custom command.
          return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
        }

        override fun onDisconnected(controller: MediaController) {
          // Handle disconnection.
        }
      }
    )
    .buildAsync()

Java

ListenableFuture<MediaController> controllerFuture =
    new MediaController.Builder(context, sessionToken)
        .setListener(
            new MediaController.Listener() {
              @Override
              public ListenableFuture<SessionResult> onCustomCommand(
                  MediaController controller, SessionCommand command, Bundle args) {
                // Handle custom command.
                return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
              }

              @Override
              public void onDisconnected(MediaController controller) {
                // Handle disconnection.
              }
            })
        .buildAsync();

他のコンポーネントと同様に、MediaController が不要になったら(Activity または Fragment の onStop() メソッドなど)、解放する必要があります。

Kotlin

MediaController.releaseFuture(controllerFuture)

Java

MediaController.releaseFuture(controllerFuture);

コントローラを解放しても、セッションに送信された保留中のコマンドはすべて配信されます。これらのコマンドが処理された後、またはタイムアウト期間が経過した後のいずれか早い方のタイミングで、セッション サービスからバインド解除されます。

MediaBrowser

MediaBrowser は、MediaController が提供する機能をベースに構築されており、メディアアプリの MediaLibraryService が提供するメディア ライブラリの閲覧も可能にします。

MediaBrowser を作成する

Kotlin

val browserFuture = MediaBrowser.Builder(context, sessionToken).buildAsync()
browserFuture.addListener(
  {
    // MediaBrowser is available here with browserFuture.get()
  },
  MoreExecutors.directExecutor(),
)

Java

ListenableFuture<MediaBrowser> browserFuture =
    new MediaBrowser.Builder(context, sessionToken).buildAsync();
browserFuture.addListener(
    () -> {
      // MediaBrowser is available here with browserFuture.get()
    },
    MoreExecutors.directExecutor());

MediaBrowser を使用する

メディアアプリのコンテンツ ライブラリのブラウジングを開始するには、まず getLibraryRoot() を使用してルートノードを取得します。

Kotlin

// Get the library root to start browsing the library tree.
val rootFuture = mediaBrowser.getLibraryRoot(/* params= */ null)
rootFuture.addListener(
  {
    // Root node MediaItem is available here with rootFuture.get().value
  },
  MoreExecutors.directExecutor(),
)

Java

// Get the library root to start browsing the library tree.
ListenableFuture<LibraryResult<MediaItem>> rootFuture =
    mediaBrowser.getLibraryRoot(/* params= */ null);
rootFuture.addListener(
    () -> {
      // Root node MediaItem is available here with rootFuture.get().value
    },
    MoreExecutors.directExecutor());

その後、getChildren() を使用してライブラリ内の MediaItem の子を取得することで、メディア ライブラリを移動できます。たとえば、ルートノード MediaItem の子を取得するには、次のようにします。

Kotlin

// Get the library root to start browsing the library tree.
val childrenFuture = mediaBrowser.getChildren(rootMediaItem.mediaId, 0, Int.MAX_VALUE, null)
childrenFuture.addListener(
  {
    // List of children MediaItem nodes is available here with
    // childrenFuture.get().value
  },
  MoreExecutors.directExecutor(),
)

Java

ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> childrenFuture =
    mediaBrowser.getChildren(rootMediaItem.mediaId, 0, Integer.MAX_VALUE, null);
childrenFuture.addListener(
    () -> {
      // List of children MediaItem nodes is available here with
      // childrenFuture.get().value
    },
    MoreExecutors.directExecutor());

別のメディアアプリの再生コントロールを表示する

別のメディアアプリのボタンを含む UI コントロールを表示する場合は、そのアプリで宣言されているメディアボタンの設定に従うことが重要です。

アプリの設定を UI の制約と要件に合わせて解決するには、CommandButton.DisplayConstraints を使用します。UI で実行できる制限を定義できます。resolve() メソッドは、アイコン、位置、目的のアクションとともに表示するボタンの明確なリストを提供します。ユーザーがこれらのボタンのいずれかをクリックすると、CommandButton.executeAction を使用して、メディアアプリで関連するアクションをトリガーできます。

Kotlin

// Get media button preferences from media app
val mediaButtonPreferences = controller.getMediaButtonPreferences()
// Declare constraints of UI (example: limit overflow button to one)
val displayConstraints =
  DisplayConstraints.Builder().setMaxButtonsForSlot(CommandButton.SLOT_OVERFLOW, 1).build()
// Resolve media app preferences with constraints
val resolvedButtons = displayConstraints.resolve(mediaButtonPreferences, controller)
// Display buttons in UI
for (button in resolvedButtons) {
  generateUiButton(
    uiPosition = button.slots[0],
    icon = getIconRes(button.icon),
    onClick = { button.executeAction(controller) },
  )
}

Java

// Get media button preferences from media app
List<CommandButton> mediaButtonPreferences = controller.getMediaButtonPreferences();
// Declare constraints of UI (example: limit overflow button to one)
DisplayConstraints displayConstraints =
    new DisplayConstraints.Builder()
        .setMaxButtonsForSlot(CommandButton.SLOT_OVERFLOW, 1)
        .build();
// Resolve media app preferences with constraints
List<CommandButton> resolvedButtons =
    displayConstraints.resolve(mediaButtonPreferences, controller);
// Display buttons in UI
for (CommandButton button : resolvedButtons) {
  generateUiButton(
      /* uiPosition= */ button.slots.get(0),
      /* icon= */ getIconRes(button.icon),
      /* onClick= */ () -> button.executeAction(controller));
}