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

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

สรุปขั้นตอน

ข้อกำหนดเบื้องต้น

  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 ข้อมูลโค้ดด้านล่างจะแสดงวิธีตั้งค่าและมีคำแนะนำให้ตั้งค่าโค้ดเรียกกลับของเซสชันสื่อสำหรับแอปเสียงและวิดีโอ

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 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 ในแอปดังกล่าว

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

สำหรับกรณีที่อุปกรณ์ของผู้ส่งปัจจุบันเข้าร่วมเซสชันที่มีอยู่กับผู้ส่งหลายราย และแอปต้องตรวจสอบว่าอนุญาตให้โอนสื่อปัจจุบันในเครื่องหรือไม่ แอปควรใช้โค้ดเรียกกลับ 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 เป็นส่วนขยายของโค้ดเรียกกลับ SessionManagerListener ที่มีอยู่และจะทำงานหลังจากทริกเกอร์ onSessionEnded ดังนั้น ลำดับของการเรียกกลับ จากทางไกลสู่ท้องถิ่นคือ

  1. onTransferring
  2. onSessionEnding
  3. onSessionEnded
  4. onTransferred

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

แอปที่รองรับการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น

สำหรับแอปที่รองรับการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น (โดยทั่วไปคือแอปเสียง) เราขอแนะนำให้ใช้ Service (เช่น MediaBrowserService) บริการต่างๆ ควรฟังการเรียกกลับของ onTransferred และกลับมาเล่นในเครื่องต่อทั้ง 2 รายการเมื่อแอปอยู่เบื้องหน้าหรือเบื้องหลัง

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