ตัวสลับเอาต์พุตเป็นฟีเจอร์ของ Cast SDK ที่ช่วยให้การโอนระหว่างการเล่นเนื้อหาในเครื่องและการเล่นเนื้อหาระยะไกลเป็นไปอย่างราบรื่นตั้งแต่ Android 13 เป็นต้นไป เป้าหมายคือการช่วยให้แอปผู้ส่งควบคุมตำแหน่งที่เล่นเนื้อหาได้อย่างง่ายดายและรวดเร็ว
ตัวสลับเอาต์พุตใช้ไลบรารี MediaRouter เพื่อสลับการเล่นเนื้อหาระหว่างลำโพงโทรศัพท์ อุปกรณ์บลูทูธที่จับคู่ และอุปกรณ์ระยะไกลที่พร้อมใช้งาน Cast กรณีการใช้งานแบ่งออกเป็นสถานการณ์ต่อไปนี้
ดาวน์โหลดและใช้แอปตัวอย่าง CastVideos-android เพื่อดูวิธีใช้ตัวสลับเอาต์พุตในแอป
ควรเปิดใช้ตัวสลับเอาต์พุตเพื่อรองรับการสลับจากอุปกรณ์ในเครื่องไปยังอุปกรณ์ระยะไกล จากอุปกรณ์ระยะไกลไปยังอุปกรณ์ในเครื่อง และจากอุปกรณ์ระยะไกลไปยังอุปกรณ์ระยะไกล โดยใช้ขั้นตอนที่ระบุไว้ในคู่มือนี้ คุณไม่จำเป็นต้องดำเนินการขั้นตอนเพิ่มเติมเพื่อรองรับการโอนระหว่างลำโพงของอุปกรณ์ในเครื่องและอุปกรณ์บลูทูธที่จับคู่ไว้
UI ของตัวสลับเอาต์พุต
ตัวสลับเอาต์พุตจะแสดงอุปกรณ์ในเครื่องและอุปกรณ์ระยะไกลที่พร้อมใช้งาน รวมถึงสถานะปัจจุบันของอุปกรณ์ เช่น อุปกรณ์ที่เลือก กำลังเชื่อมต่อ ระดับเสียงปัจจุบัน หากมีอุปกรณ์อื่นๆ นอกเหนือจากอุปกรณ์ปัจจุบัน การคลิกอุปกรณ์อื่นจะช่วยให้คุณโอนการเล่นสื่อไปยังอุปกรณ์ที่เลือกได้

ปัญหาที่ทราบ
- เซสชันสื่อที่สร้างขึ้นสำหรับการเล่นในเครื่องจะถูกปิดและสร้างขึ้นใหม่เมื่อเปลี่ยนไปใช้การแจ้งเตือน Cast SDK
จุดแรกเข้า
การแจ้งเตือนสื่อ
หากแอปโพสต์การแจ้งเตือนเกี่ยวกับสื่อที่มี MediaSession สำหรับการเล่นในเครื่อง (เล่นในเครื่อง) มุมขวาบนของการแจ้งเตือนเกี่ยวกับสื่อจะแสดงชิปการแจ้งเตือนพร้อมชื่ออุปกรณ์ (เช่น ลำโพงโทรศัพท์) ที่กำลังเล่นเนื้อหาอยู่ การแตะชิปการแจ้งเตือนจะเปิด UI ระบบของกล่องโต้ตอบตัวสลับเอาต์พุต

การตั้งค่าระดับเสียง
นอกจากนี้ คุณยังเรียกใช้ UI ของระบบกล่องโต้ตอบตัวสลับเอาต์พุตได้โดยคลิกปุ่มปรับระดับเสียงบนอุปกรณ์ แตะไอคอนการตั้งค่าที่ด้านล่าง แล้วแตะข้อความ "เล่น <ชื่อแอป> ใน <อุปกรณ์แคสต์>"

สรุปขั้นตอน
- ตรวจสอบว่าเป็นไปตามข้อกำหนดเบื้องต้น
- เปิดใช้ตัวสลับเอาต์พุตใน AndroidManifest.xml
- อัปเดต SessionManagerListener สำหรับการแคสต์ในเบื้องหลัง
- เพิ่มการรองรับการเชื่อมต่อระยะไกลกับระยะไกล
- ตั้งค่าแฟล็ก setRemoteToLocalEnabled
- เล่นต่อในอุปกรณ์
ข้อกำหนดเบื้องต้น
- ย้ายข้อมูลแอป Android ที่มีอยู่ไปยัง AndroidX
- อัปเดต
build.gradleของแอปเพื่อใช้ Android Sender SDK เวอร์ชันขั้นต่ำที่จำเป็นสำหรับตัวสลับเอาต์พุต ดังนี้dependencies { ... implementation 'com.google.android.gms:play-services-cast-framework:21.2.0' ... }
- แอปนี้รองรับการแจ้งเตือนเกี่ยวกับสื่อ
- อุปกรณ์ที่ใช้ Android 13
ตั้งค่าการแจ้งเตือนสื่อ
หากต้องการใช้ตัวสลับเอาต์พุต แอปเสียงและวิดีโอจะต้องสร้างการแจ้งเตือนสื่อเพื่อแสดงสถานะการเล่นและการควบคุมสื่อสำหรับการเล่นในเครื่อง ซึ่งต้องสร้าง MediaSession, ตั้งค่า MediaStyle ด้วยโทเค็นของ MediaSession และตั้งค่าตัวควบคุมสื่อในการแจ้งเตือน
หากคุณไม่ได้ใช้ MediaStyle และ MediaSession อยู่ ข้อมูลโค้ดด้านล่างจะแสดงวิธีตั้งค่า และมีคำแนะนำในการตั้งค่า Callback ของเซสชันสื่อสำหรับแอปเสียงและวิดีโอ ดังนี้
// 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)
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()
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() )
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()
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() )
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
เป็น Broadcast Receiver ที่ช่วยให้สามารถโอนสื่อระหว่างอุปกรณ์ที่มี UI ของระบบได้ ดูข้อมูลเพิ่มเติมได้ที่ข้อมูลอ้างอิงเกี่ยวกับ MediaTransferReceiver
จากในเครื่องไปยังระยะไกล
เมื่อผู้ใช้เปลี่ยนการเล่นจากในเครื่องเป็นระยะไกล Cast SDK จะเริ่มเซสชัน Cast โดยอัตโนมัติ อย่างไรก็ตาม แอปต้องจัดการการเปลี่ยนจากอุปกรณ์ในเครื่องเป็นอุปกรณ์ระยะไกล เช่น หยุดการเล่นในเครื่องและโหลดสื่อในอุปกรณ์แคสต์ แอปควรฟัง Cast
SessionManagerListener
โดยใช้ Callback
onSessionStarted()
และ
onSessionEnded()
และจัดการการดำเนินการเมื่อได้รับ Callback ของ Cast
SessionManager แอปควรตรวจสอบว่า Callback เหล่านี้ยังคงทำงานอยู่เมื่อเปิดกล่องโต้ตอบตัวสลับเอาต์พุตและแอปไม่ได้ทำงานอยู่เบื้องหน้า
อัปเดต SessionManagerListener สำหรับการแคสต์ในเบื้องหลัง
ประสบการณ์การใช้งาน Cast แบบเดิมรองรับการเปลี่ยนจากอุปกรณ์ในเครื่องเป็นอุปกรณ์ระยะไกลอยู่แล้วเมื่อแอปทำงานอยู่เบื้องหน้า ประสบการณ์การใช้งาน Cast โดยทั่วไปจะเริ่มขึ้นเมื่อผู้ใช้คลิกไอคอน Cast ในแอปและเลือกอุปกรณ์ที่จะสตรีมสื่อ ในกรณีนี้ แอปต้องลงทะเบียนกับ SessionManagerListener ใน onCreate() หรือ onStart() และยกเลิกการลงทะเบียน Listener ใน onStop() หรือ onDestroy() ของกิจกรรมของแอป
ประสบการณ์ใหม่ในการแคสต์โดยใช้ตัวสลับเอาต์พุตช่วยให้แอปเริ่มแคสต์ได้เมื่ออยู่ในเบื้องหลัง ซึ่งจะมีประโยชน์อย่างยิ่งสำหรับแอปเสียงที่โพสต์การแจ้งเตือนเมื่อเล่นอยู่เบื้องหลัง แอปสามารถลงทะเบียน Listener SessionManager ใน onCreate() ของบริการ และยกเลิกการลงทะเบียนใน onDestroy() ของบริการ แอปควรได้รับ Callback จากอุปกรณ์ในเครื่องไปยังอุปกรณ์ระยะไกล (เช่น onSessionStarted) เสมอเมื่อแอปทำงานอยู่เบื้องหลัง
หากแอปใช้ MediaBrowserService เราขอแนะนำให้ลงทะเบียน SessionManagerListener ที่นั่น
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) } } }
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 เป็น true ใน CastOptions
ในกรณีที่อุปกรณ์ของผู้ส่งปัจจุบันเข้าร่วมเซสชันที่มีอยู่ซึ่งมีผู้ส่งหลายราย และแอปต้องตรวจสอบว่าสื่อปัจจุบันได้รับอนุญาตให้โอนในเครื่องหรือไม่ แอปควรใช้ Callback onTransferred ของ SessionTransferCallback เพื่อตรวจสอบ SessionState
ตั้งค่าแฟล็ก setRemoteToLocalEnabled
CastOptions.Builder มี setRemoteToLocalEnabled เพื่อแสดงหรือซ่อนลำโพงโทรศัพท์และอุปกรณ์บลูทูธในพื้นที่เป็นเป้าหมายการโอนในกล่องโต้ตอบตัวสลับเอาต์พุตเมื่อมีเซสชันการแคสต์ที่ใช้งานอยู่
class CastOptionsProvider : OptionsProvider { fun getCastOptions(context: Context?): CastOptions { ... return Builder() ... .setRemoteToLocalEnabled(true) .build() } }
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { ... return new CastOptions.Builder() ... .setRemoteToLocalEnabled(true) .build() } }
เล่นต่อในเครื่อง
แอปที่รองรับการโอนจากระยะไกลไปยังอุปกรณ์ควรลงทะเบียน SessionTransferCallback เพื่อรับการแจ้งเตือนเมื่อเกิดเหตุการณ์ขึ้น เพื่อให้แอปตรวจสอบได้ว่าควรอนุญาตให้โอนสื่อและเล่นต่อในอุปกรณ์หรือไม่
CastContext#addSessionTransferCallback(SessionTransferCallback)
อนุญาตให้แอปบันทึก SessionTransferCallback
และฟัง Callback ของ onTransferred และ onTransferFailed เมื่อมีการโอนผู้ส่ง
ไปยังการเล่นในเครื่อง
หลังจากแอปยกเลิกการลงทะเบียน SessionTransferCallback แล้ว แอปจะไม่ได้รับ SessionTransferCallback อีกต่อไป
SessionTransferCallback เป็นส่วนขยายของ Callback SessionManagerListener ที่มีอยู่ และจะทริกเกอร์หลังจากทริกเกอร์ onSessionEnded ลำดับของ Callback จากระยะไกลไปยังในเครื่องมีดังนี้
onTransferringonSessionEndingonSessionEndedonTransferred
เนื่องจากชิปการแจ้งเตือนของสื่อสามารถเปิดตัวสลับเอาต์พุตได้เมื่อแอปอยู่เบื้องหลังและกำลังแคสต์ แอปจึงต้องจัดการการโอนไปยังอุปกรณ์ในเครื่องแตกต่างกันไป โดยขึ้นอยู่กับว่าแอปนั้นรองรับการเล่นขณะล็อกหน้าจอหรือไม่ ในกรณีที่การโอนไม่สำเร็จ onTransferFailed จะเริ่มทำงานเมื่อเกิดข้อผิดพลาด
แอปที่รองรับการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น
สำหรับแอปที่รองรับการเล่นในเบื้องหลัง (โดยทั่วไปคือแอปเสียง) เราขอแนะนำให้ใช้ Service (เช่น MediaBrowserService) บริการควรฟัง Callback onTransferred และเล่นต่อในเครื่องทั้งเมื่อแอปทำงานอยู่เบื้องหน้าหรือเบื้องหลัง
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. } } }
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. } } }
แอปที่ไม่รองรับการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น
สำหรับแอปที่ไม่รองรับการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น (โดยทั่วไปคือแอปวิดีโอ) เราขอแนะนำให้ฟัง Callback onTransferred และกลับมาเล่นในเครื่องอีกครั้งหากแอปอยู่เบื้องหน้า
หากแอปทำงานอยู่เบื้องหลัง แอปควรหยุดเล่นชั่วคราวและควรจัดเก็บข้อมูลที่จำเป็นจาก SessionState (เช่น ข้อมูลเมตาของสื่อและตำแหน่งการเล่น) เมื่อแอปเปลี่ยนจากเบื้องหลังมาทำงานในเบื้องหน้า การเล่นในเครื่องควรดำเนินต่อไปด้วยข้อมูลที่จัดเก็บไว้
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. } } }
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 ในการตั้งค่าแอป Receiver ในคอนโซลนักพัฒนาซอฟต์แวร์ Google Cast SDK

การขยายสตรีมด้วยลำโพง
แอปเสียงที่ใช้ตัวสลับเอาต์พุตจะขยายเสียงไปยังอุปกรณ์ลำโพงที่พร้อมใช้งาน Cast หลายเครื่องได้ในระหว่างเซสชันการแคสต์โดยใช้การขยายสตรีม
แพลตฟอร์ม Cast รองรับฟีเจอร์นี้และไม่จำเป็นต้องทำการเปลี่ยนแปลงเพิ่มเติมหากแอปใช้ UI เริ่มต้น หากใช้ UI ที่กำหนดเอง แอปควรอัปเดต UI เพื่อแสดงว่าแอปกำลังแคสต์ไปยังกลุ่ม

หากต้องการรับชื่อกลุ่มที่ขยายใหม่ในระหว่างการขยายสตรีม ให้ลงทะเบียน Cast.Listener โดยใช้ CastSession#addCastListener
จากนั้นโทรหา
CastSession#getCastDevice()
ระหว่างการโทรกลับ onDeviceNameChanged
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) } }
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); } }
กำลังทดสอบการเชื่อมต่อระหว่างรีโมตกับรีโมต
วิธีทดสอบฟีเจอร์
- แคสต์เนื้อหาไปยังอุปกรณ์ที่พร้อมใช้งาน Cast โดยใช้การแคสต์แบบเดิมหรือใช้การแคสต์จากอุปกรณ์เคลื่อนที่ไปยังอุปกรณ์ระยะไกล
- เปิดตัวสลับเอาต์พุตโดยใช้จุดเข้าใช้งานจุดใดจุดหนึ่ง
- แตะอุปกรณ์ที่พร้อมใช้งาน Cast อีกเครื่องหนึ่ง แล้วแอปเสียงจะขยายเนื้อหาไปยังอุปกรณ์เพิ่มเติมเพื่อสร้างกลุ่มแบบไดนามิก
- แตะอุปกรณ์ที่พร้อมใช้งาน Cast อีกครั้ง ระบบจะนำอุปกรณ์ออกจากกลุ่มแบบไดนามิก