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

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

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

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

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 reference

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

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

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

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

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

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

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

CastOptions.Builder ให้สิทธิ์ 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 ที่มีอยู่ Callback และจะทำงานหลังจากมีการเรียกให้แสดง 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.
    }
  }
}

รีโมตสู่รีโมต

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

แอปเสียงคือแอปที่รองรับ Google Cast สำหรับเสียงในการการตั้งค่าแอปรับใน Google Cast SDK Developer Console

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

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

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

หากต้องการรับชื่อกลุ่มแบบขยายใหม่ระหว่างการขยายสตรีม ให้ลงทะเบียน Cast.Listener โดยใช้ CastSession#addCastListener จากนั้นโทร CastSession#getCastDevice() ในระหว่าง Callback 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 อีกครั้ง อุปกรณ์ดังกล่าวจะถูกนำออกจากไดนามิก กลุ่ม