media3-cast-integration について
Android アプリに Jetpack Media3 Cast を導入、または移行するための開発スキルです。依存関係、プレーヤーとメディアセッションの設定、Compose と View の UI 接続を案内します。
主な機能
- 01Media3 Cast の依存関係設定
- 02CastPlayer とメディアセッションの設定
- 03旧 Cast SDK からの移行
- 04Compose のキャストボタン接続
- 05View UI でのプレーヤー再バインド
活用シーン
- 01Media3 アプリへの Cast 機能追加
- 02旧 Google Cast 統合の Media3 移行
- 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.mdcustomize-castoptions.mdindex.md詳細
前提条件
- Jetpack Media3 のバージョンは
>= 1.9.0である必要があります。これより低いバージョンでは Cast を利用できません。
用語集
CastPlayer:ローカルとリモートの両方の Cast デバイスで再生を制御する Media3Playerです。RemoteCastPlayer:Cast レシーバーと通信する Media3Playerで、リモート再生にのみ使用します。- Google Cast SDK:メンテナンスモードに移行しており、Jetpack Media3 に置き換えられた旧式のキャスト SDK です。
OptionsProvider:GMSCastContextの初期化に使用する設定オプションを提供するインターフェースです。
共通ガイドライン
- 旧式の 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-exoplayerandroidx.media3:media3-sessionandroidx.media3:media3-ui-compose
-
アプリケーションで旧式の Views を使用する場合は、
media3-uiを追加します。 -
すべての Media3 依存関係で同じバージョンを適用します。
-
CastPlayer の使い方の「ビルド依存関係を追加する」セクションに記載された設定を、正しい情報源として使用します。
-
既存の Cast 統合がないアプリの場合:
- 旧式の Cast SDK(
libs.play.services.cast.framework)が存在しないことを確認します。
- 旧式の Cast SDK(
-
旧式の 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 のPlayercomposableを使用してください。 -
再生場所が切り替わったときに再コンポジションを強制し、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 への移行:
- レガシーの
PlayerViewをラップするためにAndroidViewを使用しないでください。 - CastPlayer を使ってみるに従って、Material3 の
PlayerコンポーザブルとMediaRouteButtonコンポーザブルを実装してください。 - レガシーの XML レイアウト宣言、メニューファイル、View コンポーネントへの参照を削除してください。
- レガシーの
ステップ 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への参照を削除してください。