メディア セッションは音声や動画をユニバーサルな方法で操作できる
表示されます。Media3 のデフォルト プレーヤーは ExoPlayer
クラスです。このクラスは、
Player
インターフェース。メディア セッションをプレーヤーに接続すると、アプリが
メディア再生を外部にアドバタイズし、メディアの再生コマンドを
外部ソースです。
コマンドは、メディアの再生ボタンなどの物理的なボタンから ヘッドセットやテレビのリモコンで操作できます。クライアント アプリの場合もあります。 メディア コントローラ(「一時停止」の指示など)Google アシスタントへ。メディア セッションは、これらのコマンドをメディアアプリのプレーヤーに委任します。
メディア セッションを選択する場合
MediaSession
を実装すると、ユーザーが再生を制御できるようになります。
- ヘッドフォンを使用する。多くの場合 ボタンやタップ操作は ユーザーはヘッドフォンでメディアの再生や一時停止、次のメディアへの移動ができます。 確認できます。
- Google アシスタントに話しかける。よくあるパターンは、「OK Google, 一時停止」など)。
- Wear OS スマートウォッチから操作する。これにより 一般的な再生コントロールを使用できます。
- メディア コントロールを使用する。このカルーセルには、各設定のコントロールが できます。
- テレビ物理的な再生ボタン、プラットフォームの再生による操作を許可します 電源管理(例: テレビ、サウンドバー、AV レシーバーなど) がオフになったり、入力が切り替わった場合は、アプリで再生が停止します。
- 再生に影響を与える必要のあるその他の外部プロセス。
これは多くのユースケースに適しています。特に、Google Cloud での使用について
次の場合に MediaSession
を使用:
- 映画やライブテレビなどの長尺動画コンテンツをストリーミングしている。
- ポッドキャストや音楽などの長時間の音声コンテンツをストリーミングしている 再生リスト
- TV アプリを作成している。
ただし、すべてのユースケースが MediaSession
に適しているわけではありません。おすすめ
次の場合は Player
のみを使用します。
- ユーザー エンゲージメントやインタラクションが発生するショート動画を表示している 非常に重要です
- アクティブな動画が 1 本ない(ユーザーがリストをスクロールしているなど) 複数の動画が同時に画面に表示されます
- 1 回限りの紹介動画または説明動画を再生しており、 ユーザーの積極的な視聴を期待できます
- コンテンツがプライバシーに配慮し、外部プロセスに公開されるのを望まない メディア メタデータへのアクセス(ブラウザのシークレット モードなど)
ユースケースが上記のいずれにも当てはまらない場合は、
ユーザーが積極的に操作していないときにアプリが再生を続行しても問題ない
関連付けられます。答えが「はい」であれば、おそらく
MediaSession
。回答が「いいえ」の場合は、Player
を使用することをおすすめします。
してください。
メディア セッションを作成する
メディア セッションは、それが管理するプレーヤーとともに存在します。新しい Pod を
Context
オブジェクトと Player
オブジェクトを持つメディア セッション。データ アナリストとして
必要に応じてメディア セッションを初期化する(onStart()
や
Activity
、Fragment
、または onCreate()
の onResume()
ライフサイクル メソッド
メディア セッションとそれに関連付けられたプレーヤーを所有する Service
のメソッド。
メディア セッションを作成するには、Player
を初期化して
MediaSession.Builder
が高く評価:
Kotlin
val player = ExoPlayer.Builder(context).build() val mediaSession = MediaSession.Builder(context, player).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context).build(); MediaSession mediaSession = new MediaSession.Builder(context, player).build();
自動の状態処理
Media3 ライブラリは、 渡します。そのため、API プロバイダからのマッピングを手動で処理 必要があります。
これは、従来の手法とは別個に作成、維持する必要があった
プレーヤー自体から独立した PlaybackStateCompat
。たとえば、
エラーを示します。
一意のセッション ID
デフォルトでは、MediaSession.Builder
は空の文字列のセッションを作成します。
渡されます。アプリが 1 つだけ作成する場合は、これで十分です。
これは最も一般的なケースです
アプリで複数のセッション インスタンスを同時に管理する場合、
各セッションのセッション ID が一意である必要がありますセッション ID は
MediaSession.Builder.setId(String id)
でセッションを作成するときに設定します。
エラーによりアプリをクラッシュさせる IllegalStateException
が表示される場合
メッセージ IllegalStateException: Session ID must be unique. ID=
の場合は、
以前作成されたセッションが予期せず作成された可能性がある
インスタンスが解放されます。外部 IP アドレスによってセッションがリークしないようにするため、
そのようなケースが検出され、
発生します。
他のクライアントに制御権限を付与する
メディア セッションは、再生を制御する鍵となります。これにより、 再生するプレーヤーに外部ソースからのコマンドを できます。こうしたソースには、メディアの再生ボタンなどの ヘッドセットやテレビのリモコン、「一時停止」などの間接的なコマンド Google アシスタントへ。同様に、Android または iOS モバイル デバイスに 通知やロック画面のコントロールを容易にするシステム、または Wear OS への ウォッチフェイスから再生を操作できるようになります。外部クライアントは メディア コントローラを使用して、メディアアプリに再生コマンドを発行する。これらは デリゲートされ、最終的にコマンドがキューにデリゲートされます。 できます。
<ph type="x-smartling-placeholder">で確認できます。コントローラがメディア セッションに接続しようとすると、
onConnect()
メソッドが呼び出されます。提供されている ControllerInfo
を使用できます。
承諾するかどうかを決定する
または拒否
表示されます。接続リクエストの受け入れ例については、宣言
使用可能なコマンドのセクションがあります。
接続後、コントローラからセッションに再生コマンドを送信できます。「
セッションはそれらのコマンドをプレーヤーに委任します。再生とプレイリスト
Player
インターフェースで定義されたコマンドは、
あります。
他のコールバック メソッドを使用すると、
カスタム再生コマンド
再生リストの変更など)。
これらのコールバックにも同様に ControllerInfo
オブジェクトが含まれているため、
コントローラ単位で各リクエストにどのように応答するかを指定できます。
再生リストを変更する
メディア セッションは、
プレイリストに関する ExoPlayer ガイド。
コントローラは、次のいずれかの場合にも再生リストを変更できます。
COMMAND_SET_MEDIA_ITEM
または COMMAND_CHANGE_MEDIA_ITEMS
コントローラで利用可能になります。
再生リストに新しいアイテムを追加する場合、通常は MediaItem
が必要です。
インスタンスが
定義済み URI
プレイ可能にしますデフォルトでは、新しく追加したアイテムは自動的に転送されます。
URI が定義されている場合は、player.addMediaItem
などのプレーヤー メソッドに追加します。
プレーヤーに追加された MediaItem
インスタンスをカスタマイズする場合は、次の操作を行います。
オーバーライド
onAddMediaItems()
。
この手順は、メディアをリクエストするコントローラをサポートする場合に必要です。
渡されます。代わりに、通常、MediaItem
には、
リクエストされたメディアを説明するために設定された、次のフィールドの 1 つ以上:
MediaItem.id
: メディアを識別する汎用 ID。MediaItem.RequestMetadata.mediaUri
: カスタム URL を使用できるリクエスト URI 直接プレイできるとは限りません。MediaItem.RequestMetadata.searchQuery
: テキスト検索クエリ。例: 操作できます。MediaItem.MediaMetadata
: 構造化メタデータ(「title」など)入力します
まったく新しい再生リストをさらにカスタマイズするには、
さらにオーバーライドして
onSetMediaItems()
を使用して、再生リストの開始アイテムと位置を定義できます。たとえば
リクエストした 1 つのアイテムを再生リスト全体に展開して、
プレーヤーは最初にリクエストされたアイテムのインデックスから再生を開始します。
onSetMediaItems()
の実装例
セッションのデモアプリで確認できます。
カスタム レイアウトとカスタム コマンドを管理する
以降のセクションでは、カスタム レイアウトの広告を コントローラがカスタム モードを送信することを承認し、 使用できます。
セッションのカスタム レイアウトを定義する
どの再生コントロールを表示するかをクライアント アプリに示すため
セッションのカスタム レイアウトを設定します。
アプリケーションの onCreate()
メソッドで MediaSession
をビルドする場合、
あります。
Kotlin
override fun onCreate() { super.onCreate() val likeButton = CommandButton.Builder() .setDisplayName("Like") .setIconResId(R.drawable.like_icon) .setSessionCommand(SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING)) .build() val favoriteButton = CommandButton.Builder() .setDisplayName("Save to favorites") .setIconResId(R.drawable.favorite_icon) .setSessionCommand(SessionCommand(SAVE_TO_FAVORITES, Bundle())) .build() session = MediaSession.Builder(this, player) .setCallback(CustomMediaSessionCallback()) .setCustomLayout(ImmutableList.of(likeButton, favoriteButton)) .build() }
Java
@Override public void onCreate() { super.onCreate(); CommandButton likeButton = new CommandButton.Builder() .setDisplayName("Like") .setIconResId(R.drawable.like_icon) .setSessionCommand(new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING)) .build(); CommandButton favoriteButton = new CommandButton.Builder() .setDisplayName("Save to favorites") .setIconResId(R.drawable.favorite_icon) .setSessionCommand(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); Player player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player) .setCallback(new CustomMediaSessionCallback()) .setCustomLayout(ImmutableList.of(likeButton, favoriteButton)) .build(); }
利用可能なプレーヤーとカスタム コマンドを宣言する
メディア アプリケーションでは、カスタム コマンドを定義して、
作成できますたとえば、特定の Google サービス内で
ユーザーは、お気に入りアイテムのリストにメディア アイテムを保存できます。MediaController
カスタム コマンドを送信し、MediaSession.Callback
がそれを受信します。
ユーザーで使用可能なカスタム セッション コマンドを定義できます。
MediaController
: メディア セッションに接続したとき。これを実現するには
MediaSession.Callback.onConnect()
をオーバーライドする。設定と戻り
使用可能なコマンドのセットは、
onConnect
コールバック メソッド内の MediaController
:
Kotlin
private inner class CustomMediaSessionCallback: MediaSession.Callback { // Configure commands available to the controller in onConnect() override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): MediaSession.ConnectionResult { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY)) .build() return AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build() } }
Java
class CustomMediaSessionCallback implements MediaSession.Callback { // Configure commands available to the controller in onConnect() @Override public ConnectionResult onConnect( MediaSession session, ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); return new AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build(); } }
MediaController
からカスタム コマンド リクエストを受信するには、
Callback
の onCustomCommand()
メソッド。
Kotlin
private inner class CustomMediaSessionCallback: MediaSession.Callback { ... override fun onCustomCommand( session: MediaSession, controller: MediaSession.ControllerInfo, customCommand: SessionCommand, args: Bundle ): ListenableFuture<SessionResult> { if (customCommand.customAction == SAVE_TO_FAVORITES) { // Do custom logic here saveToFavorites(session.player.currentMediaItem) return Futures.immediateFuture( SessionResult(SessionResult.RESULT_SUCCESS) ) } ... } }
Java
class CustomMediaSessionCallback implements MediaSession.Callback { ... @Override public ListenableFuture<SessionResult> onCustomCommand( MediaSession session, ControllerInfo controller, SessionCommand customCommand, Bundle args ) { if(customCommand.customAction.equals(SAVE_TO_FAVORITES)) { // Do custom logic here saveToFavorites(session.getPlayer().getCurrentMediaItem()); return Futures.immediateFuture( new SessionResult(SessionResult.RESULT_SUCCESS) ); } ... } }
リクエストを行っているメディア コントローラをトラックするには、
次の MediaSession.ControllerInfo
オブジェクトの packageName
プロパティ。
Callback
メソッドに渡されます。これにより、アプリのニーズや
特定のコマンドに対する動作(そのコマンドの実行元がシステム、
他のクライアント アプリにも適用できます。
ユーザー操作後にカスタム レイアウトを更新する
カスタム コマンドなど、プレーヤーの操作を処理した後で、
コントローラ UI に表示されるレイアウトを更新したい場合があります。典型的な例
は、関連付けられたアクションをトリガーした後にアイコンを変更する切り替えボタンです。
このボタンで操作できます。レイアウトを更新するには、次のコマンドを使用します。
MediaSession.setCustomLayout
:
Kotlin
val removeFromFavoritesButton = CommandButton.Builder() .setDisplayName("Remove from favorites") .setIconResId(R.drawable.favorite_remove_icon) .setSessionCommand(SessionCommand(REMOVE_FROM_FAVORITES, Bundle())) .build() mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton))
Java
CommandButton removeFromFavoritesButton = new CommandButton.Builder() .setDisplayName("Remove from favorites") .setIconResId(R.drawable.favorite_remove_icon) .setSessionCommand(new SessionCommand(REMOVE_FROM_FAVORITES, new Bundle())) .build(); mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton));
再生コマンドの動作をカスタマイズする
Player
インターフェースで定義されたコマンドの動作をカスタマイズする場合(
play()
または seekToNext()
として、Player
を ForwardingPlayer
でラップします。
Kotlin
val player = ExoPlayer.Builder(context).build() val forwardingPlayer = object : ForwardingPlayer(player) { override fun play() { // Add custom logic super.play() } override fun setPlayWhenReady(playWhenReady: Boolean) { // Add custom logic super.setPlayWhenReady(playWhenReady) } } val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context).build(); ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player) { @Override public void play() { // Add custom logic super.play(); } @Override public void setPlayWhenReady(boolean playWhenReady) { // Add custom logic super.setPlayWhenReady(playWhenReady); } }; MediaSession mediaSession = new MediaSession.Builder(context, forwardingPlayer).build();
ForwardingPlayer
の詳細については、ExoPlayer ガイドをご覧ください。
カスタマイズ。
プレーヤー コマンドのリクエスト元コントローラを特定する
Player
メソッドの呼び出しが MediaController
から行われた場合は、次のことが可能です。
MediaSession.controllerForCurrentRequest
で送信元を特定する
現在のリクエストの ControllerInfo
を取得します。
Kotlin
class CallerAwareForwardingPlayer(player: Player) : ForwardingPlayer(player) { override fun seekToNext() { Log.d( "caller", "seekToNext called from package ${session.controllerForCurrentRequest?.packageName}" ) super.seekToNext() } }
Java
public class CallerAwareForwardingPlayer extends ForwardingPlayer { public CallerAwareForwardingPlayer(Player player) { super(player); } @Override public void seekToNext() { Log.d( "caller", "seekToNext called from package: " + session.getControllerForCurrentRequest().getPackageName()); super.seekToNext(); } }
メディアボタンに反応する
メディアボタンは、Android デバイスやその他の周辺機器に搭載されているハードウェア ボタンです。
(Bluetooth ヘッドセットの再生/一時停止ボタンなど)。Media3 ハンドル
メディアボタン イベントがセッションに到着し、
セッション プレーヤーの適切な Player
メソッド。
デフォルトの動作は、デフォルト動作をオーバーライドするために
MediaSession.Callback.onMediaButtonEvent(Intent)
。その場合、アプリは
すべての API 仕様を独自に処理する必要があります。
エラー処理と報告
セッションによって出力され、コントローラに報告されるエラーには 2 種類があります。 致命的なエラーは、セッションの技術的な再生エラーを報告する 再生を中断する場合もあります。致命的なエラーがコントローラに報告される 自動的に適用されます。非致命的なエラーは、技術またはポリシーに関するものではない エラーは再生を中断せず、メディア リクエストによって 手動で行う必要はありません。
致命的な再生エラー
致命的な再生エラーがプレーヤーによってセッションに報告され、その後
コントローラに報告され、
Player.Listener.onPlayerError(PlaybackException)
、
Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException)
。
その場合、再生状態は STATE_IDLE
に移行し、
MediaController.getPlaybackError()
は、発生した PlaybackException
を返します。
おすすめします。コントローラは PlayerException.errorCode
を検査して、
エラーの理由に関する情報が表示されます。
相互運用性を確保するため、致命的なエラーが PlaybackStateCompat
に複製されます。
プラットフォーム セッションの状態が STATE_ERROR
に移行し、
PlaybackException
に応じたエラーコードとメッセージ。
致命的なエラーのカスタマイズ
ローカライズされた意味のある情報、エラーコード、
致命的な再生エラーのエラー メッセージとエラー エクストラは、
セッションの作成時に ForwardingPlayer
を使用します。
Kotlin
val forwardingPlayer = ErrorForwardingPlayer(player) val session = MediaSession.Builder(context, forwardingPlayer).build()
Java
Player forwardingPlayer = new ErrorForwardingPlayer(player); MediaSession session = new MediaSession.Builder(context, forwardingPlayer).build();
転送プレーヤーが Player.Listener
を実際のプレーヤーに登録する
エラーを報告するコールバックをインターセプトします。カスタマイズされた
その後、PlaybackException
は、
転送プレーヤーに登録されます。これを機能させるには、転送プレーヤーが
Player.addListener
と Player.removeListener
をオーバーライドして、
カスタマイズしたエラーコード、メッセージ、エクストラを送信するリスナー:
Kotlin
class ErrorForwardingPlayer(private val context: Context, player: Player) : ForwardingPlayer(player) { private val listeners: MutableList<Player.Listener> = mutableListOf() private var customizedPlaybackException: PlaybackException? = null init { player.addListener(ErrorCustomizationListener()) } override fun addListener(listener: Player.Listener) { listeners.add(listener) } override fun removeListener(listener: Player.Listener) { listeners.remove(listener) } override fun getPlayerError(): PlaybackException? { return customizedPlaybackException } private inner class ErrorCustomizationListener : Player.Listener { override fun onPlayerErrorChanged(error: PlaybackException?) { customizedPlaybackException = error?.let { customizePlaybackException(it) } listeners.forEach { it.onPlayerErrorChanged(customizedPlaybackException) } } override fun onPlayerError(error: PlaybackException) { listeners.forEach { it.onPlayerError(customizedPlaybackException!!) } } private fun customizePlaybackException( error: PlaybackException, ): PlaybackException { val buttonLabel: String val errorMessage: String when (error.errorCode) { PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW -> { buttonLabel = context.getString(R.string.err_button_label_restart_stream) errorMessage = context.getString(R.string.err_msg_behind_live_window) } // Apps can customize further error messages by adding more branches. else -> { buttonLabel = context.getString(R.string.err_button_label_ok) errorMessage = context.getString(R.string.err_message_default) } } val extras = Bundle() extras.putString("button_label", buttonLabel) return PlaybackException(errorMessage, error.cause, error.errorCode, extras) } override fun onEvents(player: Player, events: Player.Events) { listeners.forEach { it.onEvents(player, events) } } // Delegate all other callbacks to all listeners without changing arguments like onEvents. } }
Java
private static class ErrorForwardingPlayer extends ForwardingPlayer { private final Context context; private List<Player.Listener> listeners; @Nullable private PlaybackException customizedPlaybackException; public ErrorForwardingPlayer(Context context, Player player) { super(player); this.context = context; listeners = new ArrayList<>(); player.addListener(new ErrorCustomizationListener()); } @Override public void addListener(Player.Listener listener) { listeners.add(listener); } @Override public void removeListener(Player.Listener listener) { listeners.remove(listener); } @Nullable @Override public PlaybackException getPlayerError() { return customizedPlaybackException; } private class ErrorCustomizationListener implements Listener { @Override public void onPlayerErrorChanged(@Nullable PlaybackException error) { customizedPlaybackException = error != null ? customizePlaybackException(error, context) : null; for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onPlayerErrorChanged(customizedPlaybackException); } } @Override public void onPlayerError(PlaybackException error) { for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onPlayerError(checkNotNull(customizedPlaybackException)); } } private PlaybackException customizePlaybackException( PlaybackException error, Context context) { String buttonLabel; String errorMessage; switch (error.errorCode) { case PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW: buttonLabel = context.getString(R.string.err_button_label_restart_stream); errorMessage = context.getString(R.string.err_msg_behind_live_window); break; // Apps can customize further error messages by adding more case statements. default: buttonLabel = context.getString(R.string.err_button_label_ok); errorMessage = context.getString(R.string.err_message_default); break; } Bundle extras = new Bundle(); extras.putString("button_label", buttonLabel); return new PlaybackException(errorMessage, error.getCause(), error.errorCode, extras); } @Override public void onEvents(Player player, Events events) { for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onEvents(player, events); } } // Delegate all other callbacks to all listeners without changing arguments like onEvents. } }
致命的でないエラー
技術的な例外に起因しない致命的でないエラーを送信できます。 または特定のコントローラに送信する場合:
Kotlin
val sessionError = SessionError( SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, context.getString(R.string.error_message_authentication_expired), ) // Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError) // Interoperability: Sending a nonfatal error to the media notification controller to set the // error code and error message in the playback state of the platform media session. mediaSession.mediaNotificationControllerInfo?.let { mediaSession.sendError(it, sessionError) }
Java
SessionError sessionError = new SessionError( SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, context.getString(R.string.error_message_authentication_expired)); // Sending a nonfatal error to all controllers. mediaSession.sendError(sessionError); // Interoperability: Sending a nonfatal error to the media notification controller to set the // error code and error message in the playback state of the platform media session. ControllerInfo mediaNotificationControllerInfo = mediaSession.getMediaNotificationControllerInfo(); if (mediaNotificationControllerInfo != null) { mediaSession.sendError(mediaNotificationControllerInfo, sessionError); }
メディア通知コントローラに送信された非致命的なエラーは、
プラットフォーム セッションの PlaybackStateCompat
。これにより、エラーコードと
それに応じてエラー メッセージが PlaybackStateCompat
に設定され、
PlaybackStateCompat.state
は STATE_ERROR
に変更されません。
致命的でないエラーを受け取る
MediaController
が致命的でないエラーを受信するには、
MediaController.Listener.onError
:
Kotlin
val future = MediaController.Builder(context, sessionToken) .setListener(object : MediaController.Listener { override fun onError(controller: MediaController, sessionError: SessionError) { // Handle nonfatal error. } }) .buildAsync()
Java
MediaController.Builder future = new MediaController.Builder(context, sessionToken) .setListener( new MediaController.Listener() { @Override public void onError(MediaController controller, SessionError sessionError) { // Handle nonfatal error. } });