ตัวสลับเอาต์พุตเป็นฟีเจอร์ของ Cast SDK ที่ช่วยให้โอนเนื้อหาระหว่างเครื่องและการเล่นระยะไกลได้อย่างราบรื่นตั้งแต่ Android 13 เป็นต้นไป โดยมีเป้าหมายเพื่อช่วยให้แอปของผู้ส่งควบคุมตำแหน่งที่จะเปิดเนื้อหาได้อย่างง่ายดายและรวดเร็ว
ตัวสลับเอาต์พุตใช้ไลบรารี MediaRouter
เพื่อเปลี่ยนการเล่นเนื้อหาระหว่างลำโพงโทรศัพท์ อุปกรณ์บลูทูธที่จับคู่ และอุปกรณ์ที่พร้อมใช้งาน Cast ระยะไกล กรณีการใช้งานอาจแบ่งออกเป็นสถานการณ์ต่างๆ ต่อไปนี้
ดาวน์โหลดและใช้ตัวอย่างด้านล่างเพื่อการอ้างอิงเกี่ยวกับวิธีใช้ตัวสลับเอาต์พุตในแอป Audio ดู README.md ที่รวมอยู่เพื่อดูวิธีเรียกใช้ตัวอย่าง
ควรเปิดใช้ตัวสลับเอาต์พุตเพื่อให้รองรับ Local-to-remote และ Remote-to-local โดยทำตามขั้นตอนในคู่มือนี้ ไม่มีขั้นตอนเพิ่มเติมใดๆ ที่จำเป็นต่อการรองรับการโอนระหว่างลำโพงของอุปกรณ์ในเครือข่ายเดียวกันและอุปกรณ์บลูทูธที่จับคู่ไว้
แอปเสียงคือแอปที่รองรับ Google Cast for Audio ในการตั้งค่าแอปตัวรับ ในคอนโซลของนักพัฒนาซอฟต์แวร์ Google Cast SDK
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
ข้อมูลโค้ดด้านล่างจะแสดงวิธีตั้งค่าและมีคำแนะนำให้ตั้งค่าโค้ดเรียกกลับของเซสชันสื่อสำหรับแอปเสียงและวิดีโอ
// 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 Getr ที่เปิดใช้การโอนสื่อระหว่างอุปกรณ์ด้วย UI ของระบบ ดูข้อมูลเพิ่มเติมในข้อมูลอ้างอิง MediaTransferReceiver
จากเครื่องสู่ระยะไกล
เมื่อผู้ใช้เปลี่ยนการเล่นจากในเครื่องเป็นระยะไกล Cast SDK จะเริ่มเซสชันแคสต์โดยอัตโนมัติ อย่างไรก็ตาม แอปต้องจัดการกับการเปลี่ยนจากในเครื่องเป็นจากระยะไกล เช่น หยุดการเล่นในเครื่องและโหลดสื่อในอุปกรณ์แคสต์ แอปควรฟังการแคสต์
SessionManagerListener
โดยใช้
onSessionStarted()
และ
onSessionEnded()
การเรียกกลับ และจัดการการดำเนินการเมื่อได้รับการติดต่อกลับ
SessionManager
แอปควรตรวจสอบว่าโค้ดเรียกกลับเหล่านี้ยังคงทำงานอยู่เมื่อกล่องโต้ตอบตัวสลับเอาต์พุตเปิดอยู่และแอปไม่ได้อยู่ในส่วนหน้า
อัปเดต SessionManagerListener สำหรับการแคสต์ในเบื้องหลัง
ประสบการณ์การแคสต์แบบเดิมรองรับ "Local-to- Remote" อยู่แล้วเมื่อแอปทำงานอยู่เบื้องหน้า ประสบการณ์การแคสต์โดยทั่วไปจะเริ่มขึ้นเมื่อผู้ใช้คลิกไอคอน "แคสต์" ในแอปและเลือกอุปกรณ์ที่จะสตรีมสื่อ ในกรณีนี้ แอปต้องลงทะเบียน
SessionManagerListener
ใน onCreate()
หรือ
onStart()
และยกเลิกการลงทะเบียน Listener ใน
onStop()
หรือ
onDestroy()
กิจกรรมของแอป
ประสบการณ์การแคสต์ใหม่โดยใช้ตัวสลับเอาต์พุต แอปจะเริ่มแคสต์ได้เมื่ออยู่ในเบื้องหลัง ซึ่งมีประโยชน์มากโดยเฉพาะสำหรับแอปเสียง
ที่โพสต์การแจ้งเตือนเมื่อเล่นอยู่เบื้องหลัง แอปสามารถลงทะเบียน Listener SessionManager
ใน onCreate()
ของบริการและยกเลิกการลงทะเบียนใน onDestroy()
ของบริการได้ วิธีนี้จะทำให้แอปได้รับโค้ดเรียกกลับภายในเครื่องไปยังระยะไกลเสมอ (เช่น 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
สำหรับกรณีที่อุปกรณ์ของผู้ส่งปัจจุบันเข้าร่วมเซสชันที่มีอยู่กับผู้ส่งหลายราย และแอปต้องตรวจสอบว่าอนุญาตให้โอนสื่อปัจจุบันในเครื่องหรือไม่ แอปควรใช้โค้ดเรียกกลับ onTransferred
ของ SessionTransferCallback
เพื่อตรวจสอบ SessionState
ตั้งค่าแฟล็ก setRemoteToLocalEnabled
CastOptions
จะมี 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
และคอยฟังการติดต่อกลับ onTransferred
และ onTransferFailed
เมื่อโอนผู้ส่งไปยังการเล่นในเครื่อง
หลังจากที่แอปยกเลิกการลงทะเบียน SessionTransferCallback
แล้ว แอปจะไม่ได้รับ SessionTransferCallback
อีกต่อไป
SessionTransferCallback
เป็นส่วนขยายของโค้ดเรียกกลับ SessionManagerListener
ที่มีอยู่และจะทำงานหลังจากทริกเกอร์ onSessionEnded
ดังนั้น ลำดับของการเรียกกลับ
จากทางไกลสู่ท้องถิ่นคือ
onTransferring
onSessionEnding
onSessionEnded
onTransferred
เนื่องจากตัวสลับเอาต์พุตจะเปิดได้ด้วยชิปการแจ้งเตือนสื่อเมื่อแอปทำงานอยู่เบื้องหลังและแคสต์อยู่ แอปจึงต้องจัดการการโอนไปยัง
ในเครื่องแตกต่างออกไป โดยขึ้นอยู่กับว่ารองรับการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่นหรือไม่ ในกรณีการโอนไม่สำเร็จ onTransferFailed
จะเริ่มทำงานทุกครั้งที่เกิดข้อผิดพลาด
แอปที่รองรับการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น
สำหรับแอปที่รองรับการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น (โดยทั่วไปคือแอปเสียง) เราขอแนะนำให้ใช้ Service
(เช่น MediaBrowserService
) บริการต่างๆ ควรฟังการเรียกกลับของ onTransferred
และกลับมาเล่นในเครื่องต่อทั้ง 2 รายการเมื่อแอปอยู่เบื้องหน้าหรือเบื้องหลัง
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. } } }
แอปที่ไม่รองรับการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น
สำหรับแอปที่ไม่รองรับการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น (โดยทั่วไปคือแอปวิดีโอ) เราขอแนะนำให้ฟังโค้ดเรียกกลับของ 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. } } }