出力スイッチャー

出力切り替えは、Android 13 以降のコンテンツのローカル再生とリモート再生をシームレスに転送できる Cast SDK の機能です。送信元アプリがコンテンツの再生場所を簡単にすばやく制御できるようにすることが目的です。Output Switcher は MediaRouter ライブラリを使用して、スマートフォンのスピーカー、ペア設定された Bluetooth デバイス、リモートの Cast 対応デバイス間でコンテンツの再生を切り替えます。ユースケースは次のシナリオに分類できます。

アプリに出力切り替えツールを実装する方法については、CastVideos-android サンプルアプリをダウンロードして使用してください。

このガイドで説明する手順に沿って、ローカルからリモート、リモートからローカル、リモートからリモートをサポートするように出力切り替え機能を有効にする必要があります。ローカル デバイスのスピーカーとペア設定された Bluetooth デバイス間の転送をサポートするために、追加の手順は必要ありません。

出力切り替え UI

出力切り替えツールには、使用可能なローカル デバイスとリモート デバイスのほか、デバイスの選択状況、接続状況、現在の音量レベルなどのデバイスの現在の状態が表示されます。現在のデバイス以外に他のデバイスがある場合は、[他のデバイス] をクリックして、選択したデバイスにメディアの再生を転送できます。

既知の問題

  • ローカル再生用に作成されたメディア セッションは、Cast SDK 通知に切り替えると閉じられ、再作成されます。

エントリ ポイント

メディア通知

ローカル再生(ローカル再生)の MediaSession を使用してアプリがメディア通知を投稿すると、メディア通知の右上隅に、現在コンテンツが再生されているデバイス名(スマートフォンのスピーカーなど)を含む通知チップが表示されます。通知チップをタップすると、出力切り替えダイアログのシステム UI が開きます。

音量の設定

出力切り替えダイアログ システム UI は、デバイスの物理的な音量ボタンをクリックし、下部にある設定アイコンをタップして、[<アプリ名> を <キャスト デバイス> で再生] テキストをタップすることでもトリガーできます。

ステップの概要

前提条件

  1. 既存の Android アプリを AndroidX に移行します。
  2. 出力切り替えツールに必要な最小バージョンの Android Sender SDK を使用するように、アプリの build.gradle を更新します。
    dependencies {
      ...
      implementation 'com.google.android.gms:play-services-cast-framework:21.2.0'
      ...
    }
  3. アプリがメディア通知をサポートしている。
  4. Android 13 を搭載したデバイス。

メディアの通知を設定する

出力切り替えツールを使用するには、音声アプリと動画アプリでメディア通知を作成し、ローカル再生用のメディアの再生ステータスとコントロールを表示する必要があります。これを行うには、MediaSession を作成し、MediaSession のトークンで MediaStyle を設定し、通知にメディア コントロールを設定する必要があります。

現在 MediaStyleMediaSession を使用していない場合は、以下のスニペットに設定方法を示します。オーディオアプリと動画アプリのメディア セッション コールバックの設定については、ガイドをご覧ください。

Kotlin
// Create a media session. NotificationCompat.MediaStyle
// PlayerService is your own Service or Activity responsible for media playback.
val mediaSession = MediaSessionCompat(this, "PlayerService")

// Create a MediaStyle object and supply your media session token to it.
val mediaStyle = Notification.MediaStyle().setMediaSession(mediaSession.sessionToken)

// Create a Notification which is styled by your MediaStyle object.
// This connects your media session to the media controls.
// Don't forget to include a small icon.
val notification = Notification.Builder(this@PlayerService, CHANNEL_ID)
    .setStyle(mediaStyle)
    .setSmallIcon(R.drawable.ic_app_logo)
    .build()

// Specify any actions which your users can perform, such as pausing and skipping to the next track.
val pauseAction: Notification.Action = Notification.Action.Builder(
        pauseIcon, "Pause", pauseIntent
    ).build()
notification.addAction(pauseAction)
Java
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    // Create a media session. NotificationCompat.MediaStyle
    // PlayerService is your own Service or Activity responsible for media playback.
    MediaSession mediaSession = new MediaSession(this, "PlayerService");

    // Create a MediaStyle object and supply your media session token to it.
    Notification.MediaStyle mediaStyle = new Notification.MediaStyle().setMediaSession(mediaSession.getSessionToken());

    // Specify any actions which your users can perform, such as pausing and skipping to the next track.
    Notification.Action pauseAction = Notification.Action.Builder(pauseIcon, "Pause", pauseIntent).build();

    // Create a Notification which is styled by your MediaStyle object.
    // This connects your media session to the media controls.
    // Don't forget to include a small icon.
    String CHANNEL_ID = "CHANNEL_ID";
    Notification notification = new Notification.Builder(this, CHANNEL_ID)
        .setStyle(mediaStyle)
        .setSmallIcon(R.drawable.ic_app_logo)
        .addAction(pauseAction)
        .build();
}

また、通知にメディアの情報を入力するには、メディアのメタデータと再生状態MediaSession に追加する必要があります。

MediaSession にメタデータを追加するには、setMetaData() を使用し、メディアに関連するすべての MediaMetadata 定数を MediaMetadataCompat.Builder() に指定します。

Kotlin
mediaSession.setMetadata(MediaMetadataCompat.Builder()
    // Title
    .putString(MediaMetadata.METADATA_KEY_TITLE, currentTrack.title)

    // Artist
    // Could also be the channel name or TV series.
    .putString(MediaMetadata.METADATA_KEY_ARTIST, currentTrack.artist)

    // Album art
    // Could also be a screenshot or hero image for video content
    // The URI scheme needs to be "content", "file", or "android.resource".
    .putString(
        MediaMetadata.METADATA_KEY_ALBUM_ART_URI, currentTrack.albumArtUri)
    )

    // Duration
    // If duration isn't set, such as for live broadcasts, then the progress
    // indicator won't be shown on the seekbar.
    .putLong(MediaMetadata.METADATA_KEY_DURATION, currentTrack.duration)

    .build()
)
Java
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    mediaSession.setMetadata(
        new MediaMetadataCompat.Builder()
        // Title
        .putString(MediaMetadata.METADATA_KEY_TITLE, currentTrack.title)

        // Artist
        // Could also be the channel name or TV series.
        .putString(MediaMetadata.METADATA_KEY_ARTIST, currentTrack.artist)

        // Album art
        // Could also be a screenshot or hero image for video content
        // The URI scheme needs to be "content", "file", or "android.resource".
        .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, currentTrack.albumArtUri)

        // Duration
        // If duration isn't set, such as for live broadcasts, then the progress
        // indicator won't be shown on the seekbar.
        .putLong(MediaMetadata.METADATA_KEY_DURATION, currentTrack.duration)

        .build()
    );
}

再生状態を MediaSession に追加するには、setPlaybackState() を使用し、PlaybackStateCompat.Builder() にメディアに関連するすべての PlaybackStateCompat 定数を指定します。

Kotlin
mediaSession.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setState(
            PlaybackStateCompat.STATE_PLAYING,

            // Playback position
            // Used to update the elapsed time and the progress bar.
            mediaPlayer.currentPosition.toLong(),

            // Playback speed
            // Determines the rate at which the elapsed time changes.
            playbackSpeed
        )

        // isSeekable
        // Adding the SEEK_TO action indicates that seeking is supported
        // and makes the seekbar position marker draggable. If this is not
        // supplied seek will be disabled but progress will still be shown.
        .setActions(PlaybackStateCompat.ACTION_SEEK_TO)
        .build()
)
Java
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    mediaSession.setPlaybackState(
        new PlaybackStateCompat.Builder()
            .setState(
                 PlaybackStateCompat.STATE_PLAYING,

                // Playback position
                // Used to update the elapsed time and the progress bar.
                mediaPlayer.currentPosition.toLong(),

                // Playback speed
                // Determines the rate at which the elapsed time changes.
                playbackSpeed
            )

        // isSeekable
        // Adding the SEEK_TO action indicates that seeking is supported
        // and makes the seekbar position marker draggable. If this is not
        // supplied seek will be disabled but progress will still be shown.
        .setActions(PlaybackStateCompat.ACTION_SEEK_TO)
        .build()
    );
}

動画アプリの通知の動作

バックグラウンドでのローカル再生をサポートしていない動画アプリや音声アプリは、再生がサポートされていない状況でメディア コマンドを送信する際の問題を回避するため、メディア通知に対して特定の動作を設定する必要があります。

  • ローカルでメディアを再生し、アプリがフォアグラウンドにあるときにメディア通知を送信します。
  • アプリがバックグラウンドにあるときに、ローカル再生を一時停止して通知を閉じる。
  • アプリがフォアグラウンドに戻ると、ローカル再生が再開され、通知が再投稿されます。

AndroidManifest.xml で出力切り替え機能を有効にする

出力切り替えを有効にするには、アプリの AndroidManifest.xmlMediaTransferReceiver を追加する必要があります。有効になっていない場合、この機能は有効にされず、リモートからローカルへの機能フラグも無効になります。

<application>
    ...
    <receiver
         android:name="androidx.mediarouter.media.MediaTransferReceiver"
         android:exported="true">
    </receiver>
    ...
</application>

MediaTransferReceiver は、システム UI を使用するデバイス間でのメディア転送を可能にするブロードキャスト レシーバです。詳細については、MediaTransferReceiver リファレンスをご覧ください。

ローカルからリモート

ユーザーがローカルからリモートに再生を切り替えると、Cast SDK はキャスト セッションを自動的に開始します。ただし、アプリはローカルからリモートへの切り替えを処理する必要があります。たとえば、ローカル再生を停止してキャスト デバイスにメディアを読み込むなどです。アプリは、onSessionStarted() コールバックと onSessionEnded() コールバックを使用して Cast SessionManagerListener をリッスンし、Cast SessionManager コールバックを受信したときにアクションを処理する必要があります。アプリは、出力切り替えツールのダイアログが開いていて、アプリがフォアグラウンドにない場合でも、これらのコールバックが存続するようにする必要があります。

バックグラウンド キャスト用に SessionManagerListener を更新

従来の Cast は、アプリがフォアグラウンドにあるときにローカルからリモートへの転送をすでにサポートしています。一般的なキャスト エクスペリエンスは、ユーザーがアプリのキャスト アイコンをクリックして、メディアをストリーミングするデバイスを選択すると開始されます。この場合、アプリは onCreate() または onStart()SessionManagerListener に登録し、アプリのアクティビティの onStop() または onDestroy() でリスナーの登録を解除する必要があります。

出力切り替えツールを使用した新しいキャスト機能では、アプリがバックグラウンドで実行されているときにキャストを開始できます。これは、バックグラウンドで再生中に通知を送信するオーディオアプリで特に便利です。アプリは、サービスの onCreate()SessionManager リスナーを登録し、サービスの onDestroy() で登録を解除できます。アプリがバックグラウンドにあるときは、ローカルからリモートへのコールバック(onSessionStarted など)を常に受信する必要があります。

アプリが MediaBrowserService を使用する場合は、そこに SessionManagerListener を登録することをおすすめします。

Kotlin
class MyService : Service() {
    private var castContext: CastContext? = null
    protected fun onCreate() {
        castContext = CastContext.getSharedInstance(this)
        castContext
            .getSessionManager()
            .addSessionManagerListener(sessionManagerListener, CastSession::class.java)
    }

    protected fun onDestroy() {
        if (castContext != null) {
            castContext
                .getSessionManager()
                .removeSessionManagerListener(sessionManagerListener, CastSession::class.java)
        }
    }
}
Java
public class MyService extends Service {
  private CastContext castContext;

  @Override
  protected void onCreate() {
     castContext = CastContext.getSharedInstance(this);
     castContext
        .getSessionManager()
        .addSessionManagerListener(sessionManagerListener, CastSession.class);
  }

  @Override
  protected void onDestroy() {
    if (castContext != null) {
       castContext
          .getSessionManager()
          .removeSessionManagerListener(sessionManagerListener, CastSession.class);
    }
  }
}

このアップデートにより、アプリがバックグラウンドにある場合、ローカルからリモートへの転送は従来のキャスティングと同じように動作し、Bluetooth デバイスから Cast デバイスに切り替えるために追加の作業は必要ありません。

リモートからローカル

出力スイッチャーを使用すると、リモート再生からスマートフォンのスピーカーやローカルの Bluetooth デバイスに転送できます。これは、CastOptionssetRemoteToLocalEnabled フラグを true に設定することで有効にできます。

現在の送信デバイスが複数の送信者を含む既存のセッションに参加していて、アプリが現在のメディアをローカルに転送できるかどうかを確認する必要がある場合は、SessionTransferCallbackonTransferred コールバックを使用して SessionState を確認する必要があります。

setRemoteToLocalEnabled フラグを設定する

CastOptions.Builder には、アクティブなキャスト セッションがある場合に、出力スイッチャー ダイアログでスマートフォンのスピーカーとローカル Bluetooth デバイスを転送先ターゲットとして表示または非表示にする setRemoteToLocalEnabled が用意されています。

Kotlin
class CastOptionsProvider : OptionsProvider {
    fun getCastOptions(context: Context?): CastOptions {
        ...
        return Builder()
            ...
            .setRemoteToLocalEnabled(true)
            .build()
    }
}
Java
public class CastOptionsProvider implements OptionsProvider {
    @Override
    public CastOptions getCastOptions(Context context) {
        ...
        return new CastOptions.Builder()
            ...
            .setRemoteToLocalEnabled(true)
            .build()
  }
}

ローカルで再生を続行する

リモートからローカルへの転送をサポートするアプリは、SessionTransferCallback を登録して、イベントが発生したときに通知を受け取るようにする必要があります。これにより、メディアの転送を許可してローカルで再生を続行する必要があるかどうかを確認できます。

CastContext#addSessionTransferCallback(SessionTransferCallback) を使用すると、アプリは SessionTransferCallback を登録し、送信者がローカル再生に転送されたときに onTransferred コールバックと onTransferFailed コールバックをリッスンできます。

アプリが SessionTransferCallback の登録を解除すると、アプリは SessionTransferCallback を受信しなくなります。

SessionTransferCallback は既存の SessionManagerListener コールバックの拡張機能であり、onSessionEnded がトリガーされた後にトリガーされます。リモートからローカルへのコールバックの順序は次のとおりです。

  1. onTransferring
  2. onSessionEnding
  3. onSessionEnded
  4. onTransferred

アプリがバックグラウンドでキャストしているときに、メディア通知チップによって出力切り替えツールを開くことができるため、アプリはバックグラウンド再生をサポートしているかどうかに応じて、ローカルへの転送を異なる方法で処理する必要があります。転送に失敗した場合、エラーが発生するたびに onTransferFailed がトリガーされます。

バックグラウンド再生をサポートするアプリ

バックグラウンドでの再生をサポートするアプリ(通常はオーディオ アプリ)の場合は、ServiceMediaBrowserService など)を使用することをおすすめします。サービスは onTransferred コールバックをリッスンし、アプリがフォアグラウンドまたはバックグラウンドのどちらにある場合でもローカルで再生を再開する必要があります。

Kotlin
class MyService : Service() {
    private var castContext: CastContext? = null
    private var sessionTransferCallback: SessionTransferCallback? = null
    protected fun onCreate() {
        castContext = CastContext.getSharedInstance(this)
        castContext.getSessionManager()
                   .addSessionManagerListener(sessionManagerListener, CastSession::class.java)
        sessionTransferCallback = MySessionTransferCallback()
        castContext.addSessionTransferCallback(sessionTransferCallback)
    }

    protected fun onDestroy() {
        if (castContext != null) {
            castContext.getSessionManager()
                       .removeSessionManagerListener(sessionManagerListener, CastSession::class.java)
            if (sessionTransferCallback != null) {
                castContext.removeSessionTransferCallback(sessionTransferCallback)
            }
        }
    }

    class MySessionTransferCallback : SessionTransferCallback() {
        fun onTransferring(@SessionTransferCallback.TransferType transferType: Int) {
            // Perform necessary steps prior to onTransferred
        }

        fun onTransferred(@SessionTransferCallback.TransferType transferType: Int,
                          sessionState: SessionState?) {
            if (transferType == SessionTransferCallback.TRANSFER_TYPE_FROM_REMOTE_TO_LOCAL) {
                // Remote stream is transferred to the local device.
                // Retrieve information from the SessionState to continue playback on the local player.
            }
        }

        fun onTransferFailed(@SessionTransferCallback.TransferType transferType: Int,
                             @SessionTransferCallback.TransferFailedReason transferFailedReason: Int) {
            // Handle transfer failure.
        }
    }
}
Java
public class MyService extends Service {
    private CastContext castContext;
    private SessionTransferCallback sessionTransferCallback;

    @Override
    protected void onCreate() {
        castContext = CastContext.getSharedInstance(this);
        castContext.getSessionManager()
                   .addSessionManagerListener(sessionManagerListener, CastSession.class);
        sessionTransferCallback = new MySessionTransferCallback();
        castContext.addSessionTransferCallback(sessionTransferCallback);
    }

    @Override
    protected void onDestroy() {
        if (castContext != null) {
            castContext.getSessionManager()
                       .removeSessionManagerListener(sessionManagerListener, CastSession.class);
            if (sessionTransferCallback != null) {
                castContext.removeSessionTransferCallback(sessionTransferCallback);
            }
        }
    }

    public static class MySessionTransferCallback extends SessionTransferCallback {
        public MySessionTransferCallback() {}

        @Override
        public void onTransferring(@SessionTransferCallback.TransferType int transferType) {
            // Perform necessary steps prior to onTransferred
        }

        @Override
        public void onTransferred(@SessionTransferCallback.TransferType int transferType,
                                  SessionState sessionState) {
            if (transferType==SessionTransferCallback.TRANSFER_TYPE_FROM_REMOTE_TO_LOCAL) {
                // Remote stream is transferred to the local device.
                // Retrieve information from the SessionState to continue playback on the local player.
            }
        }

        @Override
        public void onTransferFailed(@SessionTransferCallback.TransferType int transferType,
                                     @SessionTransferCallback.TransferFailedReason int transferFailedReason) {
            // Handle transfer failure.
        }
    }
}

バックグラウンド再生に対応していないアプリ

バックグラウンド再生をサポートしていないアプリ(通常は動画アプリ)の場合は、onTransferred コールバックをリッスンし、アプリがフォアグラウンドにある場合はローカルで再生を再開することをおすすめします。

アプリがバックグラウンドにある場合は、再生を一時停止し、SessionState から必要な情報(メディアのメタデータや再生位置など)を保存する必要があります。アプリがバックグラウンドからフォアグラウンドに移行された場合、ローカル再生は保存された情報を使用して続行する必要があります。

Kotlin
class MyActivity : AppCompatActivity() {
    private var castContext: CastContext? = null
    private var sessionTransferCallback: SessionTransferCallback? = null
    protected fun onCreate() {
        castContext = CastContext.getSharedInstance(this)
        castContext.getSessionManager()
                   .addSessionManagerListener(sessionManagerListener, CastSession::class.java)
        sessionTransferCallback = MySessionTransferCallback()
        castContext.addSessionTransferCallback(sessionTransferCallback)
    }

    protected fun onDestroy() {
        if (castContext != null) {
            castContext.getSessionManager()
                       .removeSessionManagerListener(sessionManagerListener, CastSession::class.java)
            if (sessionTransferCallback != null) {
                castContext.removeSessionTransferCallback(sessionTransferCallback)
            }
        }
    }

    class MySessionTransferCallback : SessionTransferCallback() {
        fun onTransferring(@SessionTransferCallback.TransferType transferType: Int) {
            // Perform necessary steps prior to onTransferred
        }

        fun onTransferred(@SessionTransferCallback.TransferType transferType: Int,
                          sessionState: SessionState?) {
            if (transferType == SessionTransferCallback.TRANSFER_TYPE_FROM_REMOTE_TO_LOCAL) {
                // Remote stream is transferred to the local device.

                // Retrieve information from the SessionState to continue playback on the local player.
            }
        }

        fun onTransferFailed(@SessionTransferCallback.TransferType transferType: Int,
                             @SessionTransferCallback.TransferFailedReason transferFailedReason: Int) {
            // Handle transfer failure.
        }
    }
}
Java
public class MyActivity extends AppCompatActivity {
  private CastContext castContext;
  private SessionTransferCallback sessionTransferCallback;

  @Override
  protected void onCreate() {
     castContext = CastContext.getSharedInstance(this);
     castContext
        .getSessionManager()
        .addSessionManagerListener(sessionManagerListener, CastSession.class);
     sessionTransferCallback = new MySessionTransferCallback();
     castContext.addSessionTransferCallback(sessionTransferCallback);
  }

  @Override
  protected void onDestroy() {
    if (castContext != null) {
       castContext
          .getSessionManager()
          .removeSessionManagerListener(sessionManagerListener, CastSession.class);
      if (sessionTransferCallback != null) {
         castContext.removeSessionTransferCallback(sessionTransferCallback);
      }
    }
  }

  public static class MySessionTransferCallback extends SessionTransferCallback {
    public MySessionTransferCallback() {}

    @Override
    public void onTransferring(@SessionTransferCallback.TransferType int transferType) {
        // Perform necessary steps prior to onTransferred
    }

    @Override
    public void onTransferred(@SessionTransferCallback.TransferType int transferType,
                               SessionState sessionState) {
      if (transferType==SessionTransferCallback.TRANSFER_TYPE_FROM_REMOTE_TO_LOCAL) {
        // Remote stream is transferred to the local device.

        // Retrieve information from the SessionState to continue playback on the local player.
      }
    }

    @Override
    public void onTransferFailed(@SessionTransferCallback.TransferType int transferType,
                                 @SessionTransferCallback.TransferFailedReason int transferFailedReason) {
      // Handle transfer failure.
    }
  }
}

リモート間

出力切り替え機能は、ストリーミング拡張を使用して、オーディオ アプリの複数の Cast 対応スピーカー デバイスに拡張する機能をサポートしています。

オーディオ アプリとは、Google Cast SDK Developer Console の [Receiver App] 設定で Google Cast for Audio をサポートするアプリです。

スピーカーを使用したストリーミング拡張

出力切り替えスイッチを使用するオーディオ アプリは、ストリーミング拡張機能を使用して、キャスト セッション中に音声を複数の Cast 対応スピーカー デバイスに拡張できます。

この機能は Cast プラットフォームでサポートされており、アプリがデフォルトの UI を使用している場合は、追加の変更は必要ありません。カスタム UI を使用している場合は、アプリがグループにキャストしていることを反映するように UI を更新する必要があります。

ストリームの拡張中に新しい拡張グループ名を取得するには、CastSession#addCastListener を使用して Cast.Listener を登録します。次に、onDeviceNameChanged コールバック中に CastSession#getCastDevice() を呼び出します。

Kotlin
class MyActivity : Activity() {
    private var mCastSession: CastSession? = null
    private lateinit var mCastContext: CastContext
    private lateinit var mSessionManager: SessionManager
    private val mSessionManagerListener: SessionManagerListener<CastSession> =
        SessionManagerListenerImpl()
    private val mCastListener = CastListener()

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarting(session: CastSession?) {}

        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            addCastListener(session)
        }

        override fun onSessionStartFailed(session: CastSession?, error: Int) {}

        override fun onSessionSuspended(session: CastSession?, reason Int) {
            removeCastListener()
        }

        override fun onSessionResuming(session: CastSession?, sessionId: String) {}

        override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) {
            addCastListener(session)
        }

        override fun onSessionResumeFailed(session: CastSession?, error: Int) {}

        override fun onSessionEnding(session: CastSession?) {}

        override fun onSessionEnded(session: CastSession?, error: Int) {
            removeCastListener()
        }
    }

    private inner class CastListener : Cast.Listener() {
        override fun onDeviceNameChanged() {
            mCastSession?.let {
                val castDevice = it.castDevice
                val deviceName = castDevice.friendlyName
                // Update UIs with the new cast device name.
            }
        }
    }

    private fun addCastListener(castSession: CastSession) {
        mCastSession = castSession
        mCastSession?.addCastListener(mCastListener)
    }

    private fun removeCastListener() {
        mCastSession?.removeCastListener(mCastListener)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mCastContext = CastContext.getSharedInstance(this)
        mSessionManager = mCastContext.sessionManager
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onDestroy() {
        super.onDestroy()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }
}
Java
public class MyActivity extends Activity {
    private CastContext mCastContext;
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();
    private Cast.Listener mCastListener = new CastListener();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarting(CastSession session) {}
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            addCastListener(session);
        }
        @Override
        public void onSessionStartFailed(CastSession session, int error) {}
        @Override
        public void onSessionSuspended(CastSession session, int reason) {
            removeCastListener();
        }
        @Override
        public void onSessionResuming(CastSession session, String sessionId) {}
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            addCastListener(session);
        }
        @Override
        public void onSessionResumeFailed(CastSession session, int error) {}
        @Override
        public void onSessionEnding(CastSession session) {}
        @Override
        public void onSessionEnded(CastSession session, int error) {
            removeCastListener();
        }
    }

    private class CastListener extends Cast.Listener {
         @Override
         public void onDeviceNameChanged() {
             if (mCastSession == null) {
                 return;
             }
             CastDevice castDevice = mCastSession.getCastDevice();
             String deviceName = castDevice.getFriendlyName();
             // Update UIs with the new cast device name.
         }
    }

    private void addCastListener(CastSession castSession) {
        mCastSession = castSession;
        mCastSession.addCastListener(mCastListener);
    }

    private void removeCastListener() {
        if (mCastSession != null) {
            mCastSession.removeCastListener(mCastListener);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCastContext = CastContext.getSharedInstance(this);
        mSessionManager = mCastContext.getSessionManager();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
}

リモート間テスト

この機能をテストするには:

  1. 従来のキャスト機能またはローカルからリモートを使用して、コンテンツを Cast 対応デバイスにキャストします。
  2. エントリ ポイントのいずれかを使用して、出力切り替えツールを開きます。
  3. 別の Cast 対応デバイスをタップすると、オーディオアプリがコンテンツを追加デバイスに拡張し、動的グループを作成します。
  4. Cast 対応デバイスをもう一度タップすると、そのデバイスは動的グループから削除されます。