ฟีเจอร์สลับเอาต์พุตเป็นฟีเจอร์หนึ่งของ Cast SDK ที่ช่วยให้โอนเนื้อหาในเครื่องและเล่นจากระยะไกลได้อย่างราบรื่นโดยเริ่มจาก Android 13 โดยมีเป้าหมายเพื่อช่วยให้แอปของผู้ส่งควบคุมตําแหน่งที่เนื้อหาจะเล่นได้อย่างรวดเร็วและง่ายดาย
ตัวสลับเอาต์พุตใช้ไลบรารี MediaRouter
เพื่อเปลี่ยนการเล่นเนื้อหาระหว่างลําโพงโทรศัพท์ อุปกรณ์บลูทูธที่จับคู่ และอุปกรณ์ที่พร้อมใช้งาน Cast ระยะไกล ลักษณะการใช้งานสามารถแบ่งออกเป็นสถานการณ์ต่างๆ ต่อไปนี้
ดาวน์โหลดและใช้ตัวอย่างด้านล่างเพื่ออ้างอิงวิธีใช้ Output Switcher ในแอปเสียง โปรดดู README.md ที่ให้ไว้สําหรับคําแนะนําเกี่ยวกับวิธีเรียกใช้ตัวอย่าง
ตัวสลับเอาต์พุตควรเปิดใช้อยู่เพื่อให้รองรับการทํางานแบบจากระยะไกลสู่ระยะไกล รวมถึงจากระยะไกลเป็นภายในโดยใช้ขั้นตอนที่ระบุไว้ในคู่มือนี้ คุณไม่จําเป็นต้องดําเนินการใดๆ เพิ่มเติมเพื่อสนับสนุนการโอนระหว่างลําโพงในอุปกรณ์กับอุปกรณ์บลูทูธที่จับคู่ไว้
แอปเสียงคือแอปที่รองรับ Google Cast for Audio ในการตั้งค่าแอปของผู้รับใน Google Developers SDK Console
UI ตัวสลับเอาต์พุต
สวิตช์สลับเอาต์พุตจะแสดงอุปกรณ์ระยะไกลและระยะไกลที่มีให้ใช้งาน รวมถึงสถานะอุปกรณ์ปัจจุบัน เช่น การเลือกอุปกรณ์ เป็นระดับเสียงปัจจุบัน หากมีอุปกรณ์อื่นนอกจากอุปกรณ์ปัจจุบัน การคลิกอุปกรณ์อื่นจะช่วยให้คุณโอนการเล่นสื่อไปยังอุปกรณ์ที่เลือกได้
ปัญหาที่ทราบ
- เซสชันสื่อที่สร้างขึ้นสําหรับการเล่นในเครื่องจะปิดและสร้างขึ้นใหม่เมื่อเปลี่ยนไปใช้การแจ้งเตือน Cast SDK
จุดแรกเข้า
การแจ้งเตือนสื่อ
หากแอปโพสต์การแจ้งเตือนสื่อด้วย MediaSession
สําหรับการเล่นภายในเครื่อง (กําลังเล่นในเครื่อง) มุมขวาบนของการแจ้งเตือนสื่อจะแสดงชิปการแจ้งเตือนพร้อมชื่ออุปกรณ์ (เช่น ลําโพงโทรศัพท์) ที่เนื้อหากําลังเล่นอยู่ การแตะที่ชิปการแจ้งเตือนจะเปิด
UI ของระบบกล่องโต้ตอบ Switch Switcher
การตั้งค่าระดับเสียง
นอกจากนี้ 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
คือตัวรับการแพร่ภาพที่ช่วยให้โอนสื่อระหว่างอุปกรณ์ได้ด้วย UI ของระบบ ดูข้อมูลอ้างอิง MediaTransferReceiver สําหรับข้อมูลเพิ่มเติม
จากทางไกลสู่รีโมต
เมื่อผู้ใช้เปลี่ยนการเล่นจากในเครื่องเป็นระยะไกล SDK ของ Cast จะเริ่มเซสชันการแคสต์โดยอัตโนมัติ อย่างไรก็ตาม แอปต้องจัดการกับการเปลี่ยนจากในเครื่องเป็นระยะไกล เช่น หยุดการเล่นในเครื่องและโหลดสื่อในอุปกรณ์แคสต์ แอปควรฟังการแคสต์
SessionManagerListener
โดยใช้โค้ดเรียกกลับ onSessionStarted()
และ
onSessionEnded()
รวมถึงจัดการการทํางานเมื่อได้รับโค้ดเรียกกลับ
SessionManager
แอปควรแน่ใจว่าการเรียกกลับเหล่านี้ยังมีชีวิตอยู่เมื่อเปิดกล่องโต้ตอบตัวสลับเอาต์พุตและแอปไม่ได้อยู่ในพื้นหน้า
อัปเดต SessionManagerListener สําหรับการแคสต์พื้นหลัง
ประสบการณ์แคสต์แบบเดิมรองรับ Local-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. } } }