ปุ่มสลับเอาต์พุต

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

ดาวน์โหลดและใช้ตัวอย่างด้านล่างสำหรับวิธีติดตั้ง Output Switcher ในแอปเสียง ดู README.md ที่มาพร้อมกับวิธีการเรียกใช้ตัวอย่าง

ดาวน์โหลดตัวอย่าง

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

แอปเสียงคือแอปที่รองรับ Google Cast for Audio ในการตั้งค่าแอปตัวรับในคอนโซลของนักพัฒนาซอฟต์แวร์ Google Cast SDK

UI ของตัวสลับเอาต์พุต

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

ปัญหาที่ทราบแล้ว

  • เซสชันสื่อที่สร้างขึ้นสำหรับการเล่นในเครื่องจะปิดและสร้างใหม่เมื่อเปลี่ยนไปใช้การแจ้งเตือน Cast SDK

จุดแรกเข้า

การแจ้งเตือนสื่อ

หากแอปโพสต์การแจ้งเตือนสื่อด้วย MediaSession สำหรับการเล่นในเครื่อง (เล่นในเครื่อง) ที่มุมขวาบนของการแจ้งเตือนสื่อจะแสดงชิปการแจ้งเตือนที่มีชื่ออุปกรณ์ (เช่น ลำโพงโทรศัพท์) ที่เนื้อหากำลังเล่นอยู่ การแตะที่ชิปการแจ้งเตือนจะเปิด 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 ตั้งค่า MediaStyle ด้วยโทเค็นของ MediaSession และตั้งค่าการควบคุมสื่อในการแจ้งเตือน

หากคุณไม่ได้ใช้ MediaStyle และ MediaSession อยู่ ข้อมูลโค้ดด้านล่างจะแสดงวิธีตั้งค่าและมีคำแนะนำสำหรับการตั้งค่า Callback ของเซสชันสื่อสำหรับแอปเสียงและวิดีโอ

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 เป็น Broadcast Receiver ที่เปิดใช้การโอนสื่อระหว่างอุปกรณ์ที่มี UI ของระบบ ดูข้อมูลเพิ่มเติมที่ข้อมูลอ้างอิง MediaTransferReceiver

จากในตัวไประยะไกล

เมื่อผู้ใช้เปลี่ยนการเล่นจาก "ในเครื่อง" ไปเป็น "ระยะไกล" Cast SDK จะเริ่มเซสชันการแคสต์โดยอัตโนมัติ อย่างไรก็ตาม แอปจะต้องจัดการการเปลี่ยนจากในตัวเครื่องไปเป็นระยะไกล เช่น หยุดการเล่นในเครื่องและโหลดสื่อบนอุปกรณ์แคสต์ แอปควรฟังการแคสต์ SessionManagerListener โดยใช้การเรียกกลับ onSessionStarted() และ onSessionEnded() และจัดการการทำงานเมื่อได้รับการเรียกกลับแคสต์ SessionManager แอปควรตรวจสอบว่า Callback เหล่านี้ยังคงทำงานอยู่เมื่อกล่องโต้ตอบตัวสลับเอาต์พุตเปิดอยู่และแอปไม่ได้อยู่ที่เบื้องหน้า

อัปเดต SessionManagerListener สำหรับการแคสต์ในเบื้องหลัง

ประสบการณ์การแคสต์แบบเดิมจะรองรับการส่งภายในไปยังรีโมตอยู่แล้วเมื่อแอปทำงานอยู่เบื้องหน้า ประสบการณ์การแคสต์โดยทั่วไปจะเริ่มต้นเมื่อผู้ใช้คลิก ไอคอน "แคสต์" ในแอปและเลือกอุปกรณ์ที่จะสตรีมสื่อ ในกรณีนี้ แอปจะต้องลงทะเบียน SessionManagerListener ใน onCreate() หรือ onStart() และยกเลิกการลงทะเบียน Listener ใน onStop() หรือ onDestroy() กิจกรรมของแอป

ประสบการณ์การแคสต์แบบใหม่โดยใช้ตัวสลับเอาต์พุตทำให้แอปเริ่มแคสต์ได้เมื่ออยู่ในเบื้องหลัง ความสามารถนี้มีประโยชน์อย่างยิ่งสำหรับแอปเสียง ที่โพสต์การแจ้งเตือนเมื่อเล่นอยู่เบื้องหลัง แอปจะลงทะเบียน Listener SessionManager ใน onCreate() ของบริการและยกเลิกการลงทะเบียนใน onDestroy() ของบริการได้ เมื่อใช้วิธีนี้ แอปควรได้รับ Callback จากต้นทางถึงระยะไกล (เช่น 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 เป็น true ใน CastOptions

สำหรับกรณีที่อุปกรณ์ผู้ส่งปัจจุบันเข้าร่วมเซสชันที่มีอยู่กับผู้ส่งหลายรายและแอปต้องตรวจสอบว่าระบบอนุญาตให้โอนสื่อปัจจุบันในเครื่องได้หรือไม่ แอปควรใช้ Callback onTransferred ของ SessionTransferCallback เพื่อตรวจสอบ SessionState

ตั้งค่าแฟล็ก setRemoteToLocalEnabled

CastOptions จะกำหนด 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 และรอฟังการเรียกกลับของ onTransferred และ onTransferFailed เมื่อโอนผู้ส่งไปยังการเล่นในเครื่อง

หลังจากที่แอปยกเลิกการลงทะเบียน SessionTransferCallback แล้ว แอปจะไม่ได้รับ SessionTransferCallback อีกต่อไป

SessionTransferCallback เป็นส่วนขยายของ Callback SessionManagerListener ที่มีอยู่และจะทำงานหลังจากทริกเกอร์ onSessionEnded ดังนั้น ลำดับของ Callback ระยะไกลไปยังท้องถิ่นคือ

  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.
    }
  }
}