出力の切り替え

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

アプリで出力スイッチャーを実装する方法については、CastVideos-android サンプルアプリをダウンロードして使用してください。

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

出力スイッチャーの UI

出力スイッチャーには、利用可能なローカル デバイスとリモート デバイス、およびデバイスが選択されているかどうか、接続中かどうか、現在の音量レベルなどの現在のデバイスの状態が表示されます。現在のデバイス以外にデバイスがある場合、別のデバイスをクリックすると、選択したデバイスにメディアの再生を転送できます。

既知の問題

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

エントリ ポイント

メディア通知

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

音量の設定

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

ステップの概要

前提条件

  1. 既存の Android アプリを AndroidX に移行します。
  2. アプリの build.gradle を更新して、出力スイッチャーに必要な最小バージョンの Android Sender SDK を使用します。
    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 定数を PlaybackStateCompat.Builder() に指定します。

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 を登録し、送信者がローカル再生に転送されたときに onTransferredonTransferFailed のコールバックをリッスンできます。

アプリが 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 のレシーバー アプリの設定で 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. 従来のキャストまたは local-to-remote を使用して、Cast 対応デバイスにコンテンツをキャストします。
  2. エントリ ポイントのいずれかを使用して出力スイッチャーを開きます。
  3. 別の Cast 対応デバイスをタップすると、オーディオ アプリがコンテンツをそのデバイスに拡張し、動的グループが作成されます。
  4. Cast 対応デバイスをもう一度タップすると、動的グループから削除されます。