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

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

ดาวน์โหลดและใช้แอปตัวอย่าง CastVideos-android เพื่อดูข้อมูลอ้างอิงเกี่ยวกับวิธีใช้ตัวสลับเอาต์พุตในแอป

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

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

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

ปัญหาที่ทราบ

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

จุดแรกเข้า

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

หากแอปโพสต์การแจ้งเตือนสื่อด้วย MediaSession สำหรับ การเล่นในเครื่อง (เล่นในเครื่อง) มุมขวาบนของการแจ้งเตือนสื่อ จะแสดงชิปการแจ้งเตือนพร้อมชื่ออุปกรณ์ (เช่น ลำโพงของโทรศัพท์) ที่ กำลังเล่นเนื้อหาอยู่ การแตะชิปการแจ้งเตือนจะเปิด UI ของระบบกล่องโต้ตอบตัวสลับเอาต์พุต

การตั้งค่าระดับเสียง

คุณยังทริกเกอร์ UI ของระบบกล่องโต้ตอบตัวสลับเอาต์พุตได้ด้วยการคลิกปุ่มปรับระดับเสียงจริงบนอุปกรณ์ แตะไอคอนการตั้งค่าที่ด้านล่าง และแตะข้อความ "เล่น <ชื่อแอป> บน <อุปกรณ์ Cast>"

สรุปขั้นตอน

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

  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's และตั้งค่าตัวควบคุมสื่อในการแจ้งเต101}ือน

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

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>

The MediaTransferReceiver เป็น Broadcast Receiver ที่ช่วยให้การโอนสื่อระหว่างอุปกรณ์ที่มี UI ของระบบเป็นไปได้ ดูข้อมูลเพิ่มเติมได้ที่ข้อมูลอ้างอิง MediaTransferReceiver reference

ในเครื่องไประยะไกล

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

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

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

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

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

ระยะไกลไปในเครื่อง

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

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

ตั้งค่าสถานะ setRemoteToLocalEnabled

The CastOptions.Builder มี setRemoteToLocalEnabled เพื่อแสดงหรือซ่อนลำโพงของโทรศัพท์และอุปกรณ์บลูทูธในเครื่องเป็นเป้าหมายการโอน ในกล่องโต้ตอบตัวสลับเอาต์พุตเมื่อมีเซสชัน Cast ที่ใช้งานอยู่

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 และกลับมาเล่นต่อในเครื่องทั้งเมื่อแอปอยู่ในเบื้องหน้าหรือเบื้องหลัง

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

ระยะไกลไประยะไกล

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

แอปเสียงคือแอปที่รองรับ Google Cast for Audio ในการตั้งค่าแอปตัวรับสัญญาณ ในแผงควบคุมสำหรับนักพัฒนาซอฟต์แวร์ Google Cast SDK

การขยายสตรีมด้วยลำโพง

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

แพลตฟอร์ม Cast รองรับฟีเจอร์นี้และไม่จำเป็นต้องทำการเปลี่ยนแปลงเพิ่มเติมหากแอปใช้ UI เริ่มต้น หากใช้ UI ที่กำหนดเอง แอปควรจะอัปเดต UI เพื่อแสดงว่าแอปกำลังแคสต์ไปยังกลุ่ม

หากต้องการรับชื่อกลุ่มที่ขยายใหม่ระหว่างการขยายสตรีม ให้ลงทะเบียน Cast.Listener โดยใช้ CastSession#addCastListener จากนั้นเรียก CastSession#getCastDevice() ระหว่างการเรียกกลับ onDeviceNameChanged

Kotlin
class MyActivity : Activity() {
    private var mCastSession: CastSession? = null
    private lateinit var mCastContext: CastContext
    private lateinit var mSessionManager: SessionManager
    private val mSessionManagerListener: SessionManagerListener<CastSession> =
        SessionManagerListenerImpl()
    private val mCastListener = CastListener()

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarting(session: CastSession?) {}

        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            addCastListener(session)
        }

        override fun onSessionStartFailed(session: CastSession?, error: Int) {}

        override fun onSessionSuspended(session: CastSession?, reason Int) {
            removeCastListener()
        }

        override fun onSessionResuming(session: CastSession?, sessionId: String) {}

        override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) {
            addCastListener(session)
        }

        override fun onSessionResumeFailed(session: CastSession?, error: Int) {}

        override fun onSessionEnding(session: CastSession?) {}

        override fun onSessionEnded(session: CastSession?, error: Int) {
            removeCastListener()
        }
    }

    private inner class CastListener : Cast.Listener() {
        override fun onDeviceNameChanged() {
            mCastSession?.let {
                val castDevice = it.castDevice
                val deviceName = castDevice.friendlyName
                // Update UIs with the new cast device name.
            }
        }
    }

    private fun addCastListener(castSession: CastSession) {
        mCastSession = castSession
        mCastSession?.addCastListener(mCastListener)
    }

    private fun removeCastListener() {
        mCastSession?.removeCastListener(mCastListener)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mCastContext = CastContext.getSharedInstance(this)
        mSessionManager = mCastContext.sessionManager
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onDestroy() {
        super.onDestroy()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }
}
Java
public class MyActivity extends Activity {
    private CastContext mCastContext;
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();
    private Cast.Listener mCastListener = new CastListener();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarting(CastSession session) {}
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            addCastListener(session);
        }
        @Override
        public void onSessionStartFailed(CastSession session, int error) {}
        @Override
        public void onSessionSuspended(CastSession session, int reason) {
            removeCastListener();
        }
        @Override
        public void onSessionResuming(CastSession session, String sessionId) {}
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            addCastListener(session);
        }
        @Override
        public void onSessionResumeFailed(CastSession session, int error) {}
        @Override
        public void onSessionEnding(CastSession session) {}
        @Override
        public void onSessionEnded(CastSession session, int error) {
            removeCastListener();
        }
    }

    private class CastListener extends Cast.Listener {
         @Override
         public void onDeviceNameChanged() {
             if (mCastSession == null) {
                 return;
             }
             CastDevice castDevice = mCastSession.getCastDevice();
             String deviceName = castDevice.getFriendlyName();
             // Update UIs with the new cast device name.
         }
    }

    private void addCastListener(CastSession castSession) {
        mCastSession = castSession;
        mCastSession.addCastListener(mCastListener);
    }

    private void removeCastListener() {
        if (mCastSession != null) {
            mCastSession.removeCastListener(mCastListener);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCastContext = CastContext.getSharedInstance(this);
        mSessionManager = mCastContext.getSessionManager();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
}

การทดสอบการเล่นจากระยะไกลไประยะไกล

วิธีทดสอบฟีเจอร์

  1. แคสต์เนื้อหาไปยังอุปกรณ์ที่พร้อมใช้งาน Cast โดยใช้การแคสต์ทั่วไปหรือการเล่นจากในเครื่องไประยะไกล
  2. เปิดตัวสลับเอาต์พุตโดยใช้จุดแรกเข้าจุดใดจุดหนึ่ง
  3. แตะอุปกรณ์อื่นที่พร้อมใช้งาน Cast แอปเสียงจะขยายเนื้อหาไปยังอุปกรณ์เพิ่มเติม ซึ่งจะสร้างกลุ่มแบบไดนามิก
  4. แตะอุปกรณ์ที่พร้อมใช้งาน Cast อีกครั้ง ระบบจะนำอุปกรณ์ดังกล่าวออกจากกลุ่มแบบไดนามิก