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

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

สรุปขั้นตอน

สิ่งที่ต้องดำเนินการก่อน

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

โคตลิน
// 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()

โคตลิน
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()

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

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

โคตลิน
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.
    }
  }
}