輸出端切換器

輸出切換器是 Cast SDK 的功能,可在 Android 13 以上版本中,在本機和遠端播放內容之間進行無縫轉移。目的是協助傳送端應用程式輕鬆快速地控制內容播放位置。Output Switcher 會使用 MediaRouter 程式庫,在手機喇叭、配對的藍牙裝置和遠端支援 Cast 的裝置之間切換內容播放。用途可細分為下列情境:

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

請按照本指南中的步驟啟用輸出切換器,以支援本機至遠端、遠端至本機和遠端至遠端的連線。不需要額外步驟,即可支援本機裝置喇叭和配對的藍牙裝置之間的轉移。

輸出端切換器 UI

輸出切換器會顯示可用的本機和遠端裝置,以及目前的裝置狀態,包括裝置是否已選取、是否正在連線,以及目前的音量。如果除了目前的裝置外,還有其他裝置,點選其他裝置即可將媒體播放內容傳輸至所選裝置。

已知問題

  • 當切換至 Cast SDK 通知時,系統會關閉並重新建立為本機播放而建立的媒體工作階段。

進入點

媒體通知

如果應用程式發布含有 MediaSession 的媒體通知,用於在裝置上播放內容,則媒體通知的右上角會顯示通知方塊,其中包含目前播放內容的裝置名稱 (例如手機喇叭)。輕觸通知方塊即可開啟「Output Switcher」對話方塊系統 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(),並在 MediaMetadataCompat.Builder() 中提供媒體的所有相關 MediaMetadata 常數。

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

如要啟用輸出端切換器,請將 MediaTransferReceiver 新增至應用程式的 AndroidManifest.xml。如果未啟用,則無法啟用這項功能,且遠端至本機功能旗標也會失效。

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

MediaTransferReceiver 是廣播接收器,可在裝置之間透過系統 UI 進行媒體傳輸。詳情請參閱 MediaTransferReceiver 參考資料

本機到遠端

當使用者將播放內容從本機切換至遠端時,Cast SDK 會自動啟動 Cast 工作階段。不過,應用程式需要處理從本機切換至遠端的情況,例如停止本機播放並載入投放裝置上的媒體。應用程式應使用 onSessionStarted()onSessionEnded() 回呼,監聽 Cast SessionManagerListener,並在收到 Cast SessionManager 回呼時處理動作。應用程式應確保在開啟「Output Switcher」對話方塊且應用程式不在前景執行時,這些回呼仍可正常運作。

更新背景投放的 SessionManagerListener

在應用程式處於前景時,舊版投放體驗已支援本機到遠端的功能。使用者在應用程式中點選「投放」圖示,並選取要串流媒體的裝置,即可開始一般投放體驗。在這種情況下,應用程式需要在 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);     }   } }

在這個更新中,當應用程式處於背景時,本地到遠端的行為與傳統投放相同,而且不需要額外工作就能從藍牙裝置切換到投放裝置。

遠端到本機

輸出切換器可讓您從遠端播放轉移至手機喇叭或本機藍牙裝置。如要啟用此功能,請將 CastOptions 上的 setRemoteToLocalEnabled 旗標設為 true

如果目前的傳送端裝置加入具有多個傳送端的現有工作階段,且應用程式需要檢查目前的媒體是否允許在本機傳輸,應用程式應使用 SessionTransferCallbackonTransferred 回呼來檢查 SessionState

設定 setRemoteToLocalEnabled 標記

CastOptions.Builder 提供 setRemoteToLocalEnabled,可在有 Cast 工作階段時,在輸出端切換器對話方塊中顯示或隱藏手機喇叭和本機藍牙裝置,做為轉移至目標。

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)。服務應監聽 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 開發人員控制台的接收器應用程式設定中支援 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. 再次輕觸支援投放的裝置,即可將該裝置從動態群組中移除。