mcpField
M
media3-cast-integration

media3-cast-integration

github.com/android開発10.0k839 回インストール2 日前に更新
リポジトリ

media3-cast-integration について

Android アプリに Jetpack Media3 Cast を導入、または移行するための開発スキルです。依存関係、プレーヤーとメディアセッションの設定、Compose と View の UI 接続を案内します。

主な機能

  1. 01Media3 Cast の依存関係設定
  2. 02CastPlayer とメディアセッションの設定
  3. 03旧 Cast SDK からの移行
  4. 04Compose のキャストボタン接続
  5. 05View UI でのプレーヤー再バインド

活用シーン

  1. 01Media3 アプリへの Cast 機能追加
  2. 02旧 Google Cast 統合の Media3 移行
  3. 03Compose 再生画面へのキャスト操作追加

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/android/skills --skill media3-cast-integration

インストール後、Claude Code で/media3-cast-integrationを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/android/media/media3/cast/
create-castplayer.md
customize-castoptions.md
index.md

詳細

前提条件

  • Jetpack Media3 のバージョンは >= 1.9.0 である必要があります。これより低いバージョンでは Cast を利用できません。

用語集

  • CastPlayer:ローカルとリモートの両方の Cast デバイスで再生を制御する Media3 Player です。
  • RemoteCastPlayer:Cast レシーバーと通信する Media3 Player で、リモート再生にのみ使用します。
  • Google Cast SDK:メンテナンスモードに移行しており、Jetpack Media3 に置き換えられた旧式のキャスト SDK です。
  • OptionsProvider:GMS CastContext の初期化に使用する設定オプションを提供するインターフェースです。

共通ガイドライン

  • 旧式の Google Cast SDK はメンテナンスモードです。
  • 新しい Cast のセットアップでは:
    • Jetpack Media3 Cast を使用する必要があります。
    • 明示的に要求されない限り、旧式の Cast SDK を使用してはいけません。

ステップ 1:依存関係をセットアップする

このステップを完了するには、以下を必ず満たしてください。

  • アプリレベルのビルドファイルで、media3-cast の依存関係のバージョンを 1.9.0 以上に指定します。

    implementation("androidx.media3:media3-cast:1.11.1")
    
  • 必要な Media3 の依存関係が含まれていることを確認します。

    • androidx.media3:media3-exoplayer
    • androidx.media3:media3-session
    • androidx.media3:media3-ui-compose
  • アプリケーションで旧式の Views を使用する場合は、media3-ui を追加します。

  • すべての Media3 依存関係で同じバージョンを適用します。

  • CastPlayer の使い方の「ビルド依存関係を追加する」セクションに記載された設定を、正しい情報源として使用します。

  • 既存の Cast 統合がないアプリの場合:

    • 旧式の Cast SDK(libs.play.services.cast.framework)が存在しないことを確認します。
  • 旧式の Cast SDK から移行する場合:

    • まず Media3 Cast の依存関係を追加します。
    • コンパイルエラーを防ぐため、この段階では既存の旧式の依存関係には手を加えません。

ステップ 2:マニフェストを更新する

このステップを完了するには、以下を必ず満たしてください。

  • マニフェストの <application> タグ内で、Cast オプションプロバイダーを宣言します。
  • デフォルトでは DefaultCastOptionsProvider を使用します。CastPlayer の使い方の「OptionsProvider」セクションを参照してください。
  • カスタムの OptionsProvider は、明示的に要求された場合にのみ宣言します。CastOptions をカスタマイズするを参照してください。
  • INTERNET 権限が設定されていることを確認します。不要な権限は追加しないでください。
  • 旧式の Cast SDK から移行する場合:
    • 既存のカスタムオプションプロバイダーのファイルやマニフェストのエントリーは削除しないでください。

ステップ 3:プレーヤーとサービスを実装する

アーキテクチャの基本

Media3 Cast を統合する前の既存アプリは、次のいずれかの構成になっています。

  • ローカル再生のみ: ローカル再生をサポートするために、Media3 ExoPlayer のみを使用します。
  • 旧式の Cast のセットアップ: ローカル再生には ExoPlayer を使用し、リモート再生には旧式の RemoteMediaClient をラップする Player を併用します。UI は ForwardingPlayer と連携する MediaSession と通信し、最終的に操作をローカル再生またはリモート再生に振り分けます。

このステップを完了するには、以下を必ず満たしてください。

  • アプリケーションの MediaSessionService(または MediaLibraryService)の onCreate() メソッド内で、ExoPlayer と CastPlayer を初期化します。
  • 明示的に RemoteCastPlayer が要求されない限り、デフォルトで CastPlayer を使用します。CastPlayer の使い方の「CastPlayer を構築する」セクションを参照してください。
  • CastPlayer の場合は、そのインスタンスを直接 MediaSession.Builder に渡します。
  • 旧式のフォワーディングプレーヤーラッパーをすべて置き換えます。
  • 移行中のコンパイルエラーを防ぐため、旧式のクラスファイルはまだ削除しないでください。
上級編:RemoteCastPlayer
  • ユーザーから明示的に要求された場合にのみ、RemoteCastPlayer を使用します。

  • localPlayer を使用して MediaSession を初期化し、RemoteCastPlayer に SessionAvailabilityListener を設定して、Cast セッションの利用可能状態が変化したときに再生状態を転送します。

    class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null private lateinit var localPlayer: ExoPlayer private lateinit var remotePlayer: RemoteCastPlayer

    override fun onCreate() {
      super.onCreate()
    
      localPlayer = ExoPlayer.Builder(this).build()
      remotePlayer = RemoteCastPlayer.Builder(this).build()
      mediaSession = MediaSession.Builder(this, localPlayer).build()
    
      remotePlayer.setSessionAvailabilityListener(
        object : SessionAvailabilityListener {
          override fun onCastSessionAvailable() {
            transferPlaybackState(localPlayer, remotePlayer)
          }
    
          override fun onCastSessionUnavailable() {
            transferPlaybackState(remotePlayer, localPlayer)
          }
        }
      )
    }
    
    private fun transferPlaybackState(previousPlayer: Player, newPlayer: Player) {
      if (previousPlayer.mediaItemCount > 0) {
        val transferStateBuilder = PlayerTransferState.builderFromPlayer(previousPlayer)
        if (previousPlayer.playbackState == Player.STATE_ENDED ||
            previousPlayer.currentPosition == C.TIME_END_OF_SOURCE) {
          transferStateBuilder.setCurrentMediaItemIndex(0)
          transferStateBuilder.setCurrentPosition(0)
        }
        transferStateBuilder.build().setToPlayer(newPlayer)
      }
    
      previousPlayer.stop()
      previousPlayer.clearMediaItems()
      newPlayer.prepare()
      mediaSession?.setPlayer(newPlayer)
    }
    

    }

ステップ 4:UI を設定する

Compose ベースの UI

このステップを完了するには、以下を必ず確認してください。

  • Compose との統合方法については、Getting started with CastPlayer の「Add a MediaRouteButton Composable to the Player」セクションを参照してください。

  • androidx.media3.cast パッケージの MediaRouteButton コンポーザブルを使用してください。

  • Compose UI 階層で AndroidView を使用しないでください。

  • MediaRouteButton は再生コントロールの隣に配置してください。システム UI の背後に隠さないでください。

  • カスタムプレーヤー UI には PlayerSurface を使用しないでください。Material3 の Player composableを使用してください。

  • 再生場所が切り替わったときに再コンポジションを強制し、UI が同期されるようにしてください。DeviceInfo の変更をキー制約として使用します。

    @OptIn(UnstableApi::class)
    @Composable
    fun MainScreen() {
       val player = rememberMediaController()
       val deviceInfo = rememberDeviceInfo(player)
       player?.let { activePlayer -> key(deviceInfo) { PlayerScreen(player = activePlayer) } }
    }
    
    @Composable
    private fun rememberMediaController(): Player? {
       // Logic to connect MediaController to MediaSession and release it
    }
    
    @Composable
    private fun rememberDeviceInfo(player: Player?): DeviceInfo? {
       var deviceInfo by remember(player) { mutableStateOf(player?.deviceInfo) }
       DisposableEffect(player) {
           val activePlayer = player ?: return@DisposableEffect onDispose {}
           deviceInfo = activePlayer.deviceInfo
           val listener = object : Player.Listener {
               override fun onDeviceInfoChanged(info: DeviceInfo) {
                   deviceInfo = info
               }
           }
          activePlayer.addListener(listener)
          onDispose { activePlayer.removeListener(listener) }
       }
       return deviceInfo
    }
    
View ベースの UI

この手順を完了するには、以下を必ず実施してください。

  • View ベースの UI を設定する場合は、CastPlayer を使ってみるの「UI 要素を追加する」セクションを参照してください。

  • キャスト用 Activity は AppCompatActivity または FragmentActivity を継承し、Theme.AppCompat の派生テーマを使用する必要があります。

  • オプションメニューに MediaRouteButton を追加する場合は、AppCompat テーマで ActionBar が表示されることを確認してください。

  • CastButtonFactory のすべてのインスタンスとインポートを MediaRouteButtonFactory に置き換えてください。

  • 黒い画面や UI のフリーズを防ぐため、onDeviceInfoChanged イベントの発生時に PlayerView.player の参照を再バインドしてください。

    private val playerListener: Player.Listener =
      object : Player.Listener {
        override fun onDeviceInfoChanged(deviceInfo: DeviceInfo) {
          // null にリセットすると、PlayerView.setPlayer() のインスタンス等価性チェック
          // (this.player == player)が回避され、動画サーフェスがコントローラーに再バインドされます。
          playerView.player = null
          playerView.player = controller
        }
      }
    
  • Compose への移行:

ステップ 5:レガシーの Cast SDK コードを整理する

警告

警告: 直接整理作業を行わないでください。ユーザーから明示的に依頼された場合に限り、レガシーファイルと依存関係を削除してください。

この手順を完了するには、以下を必ず実施してください。

  • レガシーの GMS Cast SDK(libs.play.services.cast.framework)と MediaRouter(libs.androidx.mediarouter)の依存関係を削除してください。
  • DefaultCastOptionsProvider を採用している場合は、カスタムの OptionsProvider クラスとマニフェストのエントリを削除してください。
  • 存在する場合は、レガシーの MediaTransferReceiver マニフェスト宣言を削除してください。
  • レガシーのヘルパーラッパー、フォワーディングプレーヤー、RemoteMediaClient インターフェースなど、レガシーの Cast SDK コンポーネントへの参照をすべて削除してください。
  • Compose への移行が完了している場合は、レガシーの View XML レイアウト、メニューファイル、PlayerView への参照を削除してください。

コメント