輸出切換器

輸出端切換器是 Cast SDK 的一項功能,可順暢傳輸 從 Android 13 開始,本機和遠端播放內容間取得的平衡目標 可以讓傳送者應用程式輕鬆快速地控製播放內容的位置 輸出端切換器會使用 將 MediaRouter 程式庫 切換手機喇叭、配對的藍牙裝置、 和支援 Cast 的遠端裝置用途可細分為 情境:

下載並使用 CastVideos-android 範例應用程式 ,用於瞭解如何在應用程式中實作輸出切換器。

應啟用輸出端切換器,以便支援本機對本機和遠端之間的本機連線 ,瞭解如何以遠端方式進行遠端連線,並使用本指南執行的步驟。沒有任何 進行額外步驟以支援本機裝置之間的轉移作業 喇叭和配對的藍牙裝置。

輸出端切換器 UI

輸出端切換器會顯示可用的本機和遠端裝置 以及目前裝置狀態,包括如果選取裝置 正在連線,目前音量等級。如果還有其他裝置 到目前的裝置,點選其他裝置即可傳輸媒體 。

已知問題

  • 系統會關閉並重新建立為本機播放建立的媒體工作階段 請在切換至 Cast SDK 通知後 看到投影片內容

進入點

媒體通知

如果應用程式會發布含有媒體通知的應用程式 MediaSession: 本機播放 (在本機播放):媒體通知的右上角 會顯示通知方塊,內含裝置名稱 (例如手機喇叭) 以及目前播放的內容輕觸通知方塊即可開啟 輸出端切換器對話方塊系統 UI

音量設定

您也可透過按一下 實體音量按鈕、輕觸畫面底部的設定圖示、 並輕觸「播放 <應用程式名稱>」在 <Cast Device> 上文字。

步驟摘要

必要條件

  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 的裝置。

設定媒體通知

使用輸出端切換器的方法如下: audio影片應用程式 ,以便建立媒體通知來顯示播放狀態, 以便控製本機播放的媒體。因此您需要建立 MediaSession、 設定 MediaStyleMediaSession 權杖相符,並在 通知。

如果您目前沒有使用 MediaStyleMediaSession,請使用以下程式碼片段 將說明設定方式以及媒體設定指南 工作階段回呼 「audio」影片 應用程式:

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 中啟用輸出切換器

如要啟用輸出端切換器, MediaTransferReceiver敬上 需要新增至應用程式的 AndroidManifest.xml。如果不支援 系統不會啟用,且遠端轉本機功能旗標也會失效。

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

MediaTransferReceiver敬上 是一種廣播接收器,可讓搭載系統的裝置在不同裝置間傳輸媒體 第一種是使用無代碼解決方案 AutoML 透過使用者介面建立機器學習模型請參閱 MediaTransferReceiver 參考資料 瞭解詳情

本機對遠端

當使用者從本機切換至遠端播放時,Cast SDK 就會啟動 投放工作階段。不過,應用程式需要處理 遠端播放,例如停止本機播放 並在投放裝置上載入媒體。應用程式應收聽投放內容 SessionManagerListener、 方法是使用 onSessionStarted()onSessionEnded() 回呼,並在收到 Cast 時處理動作 SessionManager 回呼函式。應用程式應確保這些回呼在 輸出切換器對話方塊已開啟,且應用程式不在前景執行。

更新 SessionManagerListener 用於背景投放

舊版 Cast 服務已可在應用程式符合下列條件的情況下,支援本機對遠端裝置 在前景執行當使用者點選「投放」圖示時,一般的投放體驗隨即啟動 ,並選擇要串流媒體的裝置。在此情況下,應用程式必須 註冊 SessionManagerListener、 在onCreate()onStart() ,然後從中取消註冊 onStop()onDestroy() 應用程式活動。

透過使用輸出切換器投放內容,應用程式可啟動 並投放內容這在播放音訊時特別有用 在背景播放時張貼通知的應用程式。應用程式可註冊 SessionManager 並在 onCreate() 的接聽程式中將其取消註冊,並取消註冊 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);
    }
  }
}

本次更新後,本機對遠端裝置的運作方式與傳統轉換 應用程式正在背景執行,因此使用者無須額外執行其他操作,就能從背景切換 支援投放裝置的藍牙裝置。

遠端對本機

輸出端切換器可將遠端播放內容轉移至 手機喇叭或本機藍牙裝置。透過設定 setRemoteToLocalEnabled敬上 標記給CastOptions上的true

針對目前的傳送者裝置加入現有工作階段, 多個寄件者,且應用程式必須檢查目前的媒體是否可 在本機轉移,應用程式應使用 onTransferred SessionTransferCallback 的回呼 即可查看 SessionState

設定 setRemoteToLocalEnabled 標記

CastOptions.Builder 提供 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 隨時會在錯誤發生時觸發

支援背景播放的應用程式

支援在背景播放的應用程式 (通常是音訊應用程式) 建議使用 Service (例如 MediaBrowserService)。Services (服務) 應該收聽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 for Audio 的應用程式 Google Cast SDK 開發人員設定 控制台

透過揚聲器增加串流裝置

使用輸出切換器的音訊應用程式可展開音訊 透過串流功能,在多部支援 Cast 的喇叭裝置上串流播放 展開。

這項功能受到 Cast 平台支援,無須採取其他行動 來變更。如果使用自訂 UI,則應用程式 應更新 UI,以反映應用程式正在投放至群組。

如要在增加串流裝置期間使用新的展開群組名稱,請按照下列步驟操作: 註冊 Cast.Listener敬上 方法是使用 CastSession#addCastListener。 然後呼叫 CastSession#getCastDevice()敬上 會在 onDeviceNameChanged 回呼期間播放。

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
  2. 使用其中一個進入點開啟輸出切換器。
  3. 輕觸其他支援 Cast 的裝置後,音訊應用程式就會展開內容, 或其他裝置建立動態群組
  4. 再次輕觸支援 Cast 的裝置,該裝置就會從動態畫面中移除 群組。