Nút chuyển đầu ra

Nút chuyển đầu ra là một tính năng của SDK Cast, cho phép chuyển liền mạch giữa việc phát nội dung cục bộ và từ xa, bắt đầu từ Android 13. Mục tiêu là giúp các ứng dụng gửi dễ dàng và nhanh chóng kiểm soát vị trí phát nội dung. Nút chuyển đầu ra sử dụng thư viện MediaRouter để chuyển đổi việc phát nội dung giữa loa điện thoại, các thiết bị Bluetooth đã ghép nối và các thiết bị từ xa hỗ trợ Cast. Bạn có thể chia các trường hợp sử dụng thành các tình huống sau:

Tải xuống và sử dụng ứng dụng mẫu CastVideos-android để tham khảo cách triển khai Nút chuyển đầu ra trong ứng dụng của bạn.

Bạn nên bật Nút chuyển đầu ra để hỗ trợ chuyển đổi từ cục bộ sang từ xa, từ từ xa sang cục bộ và từ từ xa sang từ xa bằng các bước được trình bày trong hướng dẫn này. Bạn không cần thực hiện thêm bước nào để hỗ trợ việc chuyển đổi giữa loa của thiết bị cục bộ và các thiết bị Bluetooth đã ghép nối.

Giao diện người dùng của Nút chuyển đầu ra

Nút chuyển đầu ra hiển thị các thiết bị cục bộ và thiết bị từ xa có sẵn cũng như trạng thái hiện tại của thiết bị, bao gồm cả việc thiết bị có được chọn hay không, có đang kết nối hay không và mức âm lượng hiện tại. Nếu có các thiết bị khác ngoài thiết bị hiện tại, thì khi nhấp vào thiết bị khác, bạn có thể chuyển việc phát nội dung nghe nhìn sang thiết bị đã chọn.

Vấn đề đã biết

  • Các Phiên đa phương tiện được tạo để phát cục bộ sẽ bị đóng và tạo lại khi chuyển sang thông báo của SDK Cast.

Điểm truy cập

Thông báo về nội dung nghe nhìn

Nếu một ứng dụng đăng thông báo về nội dung nghe nhìn bằng MediaSession để phát cục bộ (phát trên thiết bị), thì góc trên cùng bên phải của thông báo về nội dung nghe nhìn sẽ hiển thị một khối thông báo có tên thiết bị (chẳng hạn như loa điện thoại) mà nội dung hiện đang phát. Khi bạn nhấn vào chip thông báo, giao diện người dùng hệ thống của hộp thoại Nút chuyển đầu ra sẽ mở ra.

Cài đặt âm lượng

Bạn cũng có thể kích hoạt giao diện người dùng hệ thống của hộp thoại Nút chuyển đầu ra bằng cách nhấp vào các nút âm lượng vật lý trên thiết bị, nhấn vào biểu tượng cài đặt ở dưới cùng và nhấn vào văn bản "Phát <Tên ứng dụng> trên <Thiết bị truyền>".

Tóm tắt các bước

Điều kiện tiên quyết

  1. Di chuyển ứng dụng Android hiện có sang AndroidX.
  2. Cập nhật build.gradle của ứng dụng để sử dụng phiên bản tối thiểu bắt buộc của SDK Android Sender cho Nút chuyển đầu ra:
    dependencies {
      ...
      implementation 'com.google.android.gms:play-services-cast-framework:21.2.0'
      ...
    }
  3. Ứng dụng hỗ trợ thông báo về nội dung nghe nhìn.
  4. Thiết bị chạy Android 13.

Thiết lập thông báo về nội dung nghe nhìn

Để sử dụng Nút chuyển đầu ra, các ứng dụng âm thanhvideo phải tạo một thông báo về nội dung nghe nhìn để hiển thị trạng thái phát và các nút điều khiển cho nội dung nghe nhìn của chúng để phát cục bộ. Việc này yêu cầu bạn phải tạo MediaSession, đặt MediaStyle bằng mã thông báo của MediaSession's và đặt các nút điều khiển nội dung nghe nhìn trên thông báo.

Nếu bạn hiện không sử dụng MediaStyleMediaSession, thì đoạn mã bên dưới sẽ cho biết cách thiết lập các mã này và có hướng dẫn thiết lập các lệnh gọi lại phiên đa phương tiện cho ứng dụng âm thanhvideo:

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();
}

Ngoài ra, để điền thông tin cho nội dung nghe nhìn vào thông báo, bạn cần thêm siêu dữ liệu và trạng thái phát của nội dung nghe nhìn vào MediaSession.

Để thêm siêu dữ liệu vào MediaSession, hãy sử dụng setMetaData() và cung cấp tất cả các hằng số MediaMetadata có liên quan cho nội dung nghe nhìn của bạn trong 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()
    );
}

Để thêm trạng thái phát vào MediaSession, hãy sử dụng setPlaybackState() và cung cấp tất cả các hằng số PlaybackStateCompat có liên quan cho nội dung nghe nhìn của bạn trong 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()
    );
}

Hành vi thông báo của ứng dụng video

Các ứng dụng video hoặc ứng dụng âm thanh không hỗ trợ phát cục bộ trong nền phải có hành vi cụ thể đối với thông báo về nội dung nghe nhìn để tránh gặp vấn đề khi gửi lệnh nội dung nghe nhìn trong những trường hợp không hỗ trợ phát:

  • Đăng thông báo về nội dung nghe nhìn khi phát nội dung nghe nhìn cục bộ và ứng dụng đang ở nền trước.
  • Tạm dừng phát cục bộ và đóng thông báo khi ứng dụng ở nền.
  • Khi ứng dụng chuyển về nền trước, quá trình phát cục bộ sẽ tiếp tục và thông báo sẽ được đăng lại.

Bật Nút chuyển đầu ra trong AndroidManifest.xml

Để bật Nút chuyển đầu ra, bạn cần thêm MediaTransferReceiver vào AndroidManifest.xml của ứng dụng. Nếu không, tính năng này sẽ không được bật và cờ tính năng chuyển đổi từ từ xa sang cục bộ cũng sẽ không hợp lệ.

<application>
    ...
    <receiver
         android:name="androidx.mediarouter.media.MediaTransferReceiver"
         android:exported="true">
    </receiver>
    ...
</application>

The MediaTransferReceiver là một bộ nhận tín hiệu truyền tin cho phép chuyển nội dung nghe nhìn giữa các thiết bị có giao diện người dùng hệ thống. Hãy xem tài liệu tham khảo về MediaTransferReceiver reference để biết thêm thông tin.

Từ cục bộ sang từ xa

Khi người dùng chuyển đổi việc phát từ cục bộ sang từ xa, SDK Cast sẽ tự động bắt đầu phiên truyền. Tuy nhiên, các ứng dụng cần xử lý việc chuyển đổi từ cục bộ sang từ xa, chẳng hạn như dừng phát cục bộ và tải nội dung nghe nhìn trên thiết bị truyền. Các ứng dụng nên theo dõi Cast SessionManagerListener, sử dụng các lệnh gọi lại onSessionStarted()onSessionEnded(), đồng thời xử lý hành động khi nhận được lệnh gọi lại SessionManager của Cast. Các ứng dụng phải đảm bảo rằng các lệnh gọi lại này vẫn hoạt động khi hộp thoại Nút chuyển đầu ra được mở và ứng dụng không ở nền trước.

Cập nhật SessionManagerListener để truyền trong nền

Trải nghiệm truyền cũ đã hỗ trợ chuyển đổi từ cục bộ sang từ xa khi ứng dụng ở nền trước. Trải nghiệm Cast điển hình bắt đầu khi người dùng nhấp vào biểu tượng Cast trong ứng dụng và chọn một thiết bị để truyền phát nội dung nghe nhìn. Trong trường hợp này, ứng dụng cần đăng ký SessionManagerListener, trong onCreate() hoặc onStart() và huỷ đăng ký trình nghe trong onStop() hoặc onDestroy() của hoạt động của ứng dụng.

Với trải nghiệm truyền mới bằng Nút chuyển đầu ra, các ứng dụng có thể bắt đầu truyền khi ở nền. Điều này đặc biệt hữu ích đối với các ứng dụng âm thanh đăng thông báo khi phát trong nền. Các ứng dụng có thể đăng ký trình nghe SessionManager trong onCreate() của dịch vụ và huỷ đăng ký trong onDestroy() của dịch vụ. Các ứng dụng phải luôn nhận được lệnh gọi lại chuyển đổi từ cục bộ sang từ xa (chẳng hạn như onSessionStarted) khi ứng dụng ở nền.

Nếu ứng dụng sử dụng MediaBrowserService, bạn nên đăng ký SessionManagerListener tại đó.

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);
    }
  }
}

Với bản cập nhật này, việc chuyển đổi từ cục bộ sang từ xa hoạt động giống như việc truyền thông thường khi ứng dụng ở nền và không cần thực hiện thêm thao tác để chuyển đổi từ thiết bị Bluetooth sang thiết bị truyền.

Từ từ xa sang cục bộ

Nút chuyển đầu ra cung cấp khả năng chuyển từ phát từ xa sang loa điện thoại hoặc thiết bị Bluetooth cục bộ. Bạn có thể bật tính năng này bằng cách đặt cờ setRemoteToLocalEnabled thành true trên CastOptions.

Đối với những trường hợp thiết bị gửi hiện tại tham gia một phiên hiện có có nhiều thiết bị gửi và ứng dụng cần kiểm tra xem nội dung nghe nhìn hiện tại có được phép chuyển cục bộ hay không, các ứng dụng nên sử dụng lệnh gọi lại onTransferred của SessionTransferCallback để kiểm tra SessionState.

Đặt cờ setRemoteToLocalEnabled

The CastOptions.Builder cung cấp một setRemoteToLocalEnabled để hiển thị hoặc ẩn loa điện thoại và các Thiết bị Bluetooth cục bộ dưới dạng mục tiêu chuyển đổi trong hộp thoại nút chuyển đầu ra khi có một phiên Cast đang hoạt động.

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()
  }
}

Tiếp tục phát cục bộ

Các ứng dụng hỗ trợ chuyển đổi từ từ xa sang cục bộ nên đăng ký SessionTransferCallback để nhận thông báo khi sự kiện xảy ra, nhờ đó, chúng có thể kiểm tra xem có nên cho phép chuyển nội dung nghe nhìn và tiếp tục phát cục bộ hay không.

CastContext#addSessionTransferCallback(SessionTransferCallback) cho phép một ứng dụng đăng ký SessionTransferCallback và theo dõi các lệnh gọi lại onTransferredonTransferFailed khi một thiết bị gửi được chuyển sang phát cục bộ.

Sau khi ứng dụng huỷ đăng ký SessionTransferCallback, ứng dụng sẽ không còn nhận được SessionTransferCallback s.

The SessionTransferCallback là một phần mở rộng của các lệnh gọi lại SessionManagerListener hiện có và được kích hoạt sau khi onSessionEnded được kích hoạt. Thứ tự của các lệnh gọi lại chuyển đổi từ từ xa sang cục bộ là:

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

Vì Nút chuyển đầu ra có thể được mở bằng khối thông báo về nội dung nghe nhìn khi ứng dụng ở nền và đang truyền, nên các ứng dụng cần xử lý việc chuyển sang cục bộ theo cách khác tuỳ thuộc vào việc chúng có hỗ trợ phát trong nền hay không. Trong trường hợp chuyển không thành công, onTransferFailed sẽ kích hoạt bất cứ lúc nào xảy ra lỗi.

Các ứng dụng hỗ trợ phát trong nền

Đối với các ứng dụng hỗ trợ phát trong nền (thường là ứng dụng âm thanh), bạn nên sử dụng Service (ví dụ: MediaBrowserService). Các dịch vụ nên theo dõi lệnh gọi lại onTransferred và tiếp tục phát cục bộ cả khi ứng dụng ở nền trước hoặc nền.

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

Các ứng dụng không hỗ trợ phát trong nền

Đối với các ứng dụng không hỗ trợ phát trong nền (thường là ứng dụng video), bạn nên theo dõi lệnh gọi lại onTransferred và tiếp tục phát cục bộ nếu ứng dụng ở nền trước.

Nếu ứng dụng ở nền, ứng dụng sẽ tạm dừng phát và lưu trữ thông tin cần thiết từ SessionState (ví dụ: siêu dữ liệu nội dung nghe nhìn và vị trí phát). Khi ứng dụng được đưa lên nền trước từ nền, quá trình phát cục bộ sẽ tiếp tục với thông tin đã lưu trữ.

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

Từ từ xa sang từ xa

Nút chuyển đầu ra hỗ trợ khả năng mở rộng sang nhiều thiết bị loa hỗ trợ Cast cho các ứng dụng âm thanh bằng tính năng mở rộng phiên phát trực tuyến.

Ứng dụng âm thanh là những ứng dụng hỗ trợ Google Cast cho âm thanh trong phần Cài đặt ứng dụng nhận trong Bảng điều khiển dành cho nhà phát triển SDK của Google Cast

Mở rộng luồng bằng loa

Các ứng dụng âm thanh sử dụng nút chuyển đầu ra có khả năng mở rộng âm thanh sang nhiều thiết bị loa hỗ trợ Cast trong một phiên Cast bằng tính năng mở rộng phiên phát trực tuyến.

Tính năng này được nền tảng Cast hỗ trợ và không yêu cầu thay đổi thêm nếu ứng dụng đang sử dụng giao diện người dùng mặc định. Nếu sử dụng giao diện người dùng tuỳ chỉnh, ứng dụng sẽ cập nhật giao diện người dùng để phản ánh rằng ứng dụng đang truyền đến một nhóm.

Để lấy tên nhóm mở rộng mới trong quá trình mở rộng phiên phát trực tuyến, hãy đăng ký Cast.Listener bằng CastSession#addCastListener. Sau đó, gọi CastSession#getCastDevice() trong lệnh gọi lại 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);
    }
}

Kiểm thử chuyển đổi từ từ xa sang từ xa

Cách kiểm thử tính năng này:

  1. Truyền nội dung của bạn đến một thiết bị hỗ trợ Cast bằng cách truyền thông thường hoặc với từ cục bộ sang từ xa.
  2. Mở Nút chuyển đầu ra bằng một trong các điểm truy cập.
  3. Nhấn vào một thiết bị hỗ trợ Cast khác, các ứng dụng âm thanh sẽ mở rộng nội dung sang thiết bị bổ sung, tạo một nhóm động.
  4. Nhấn lại vào thiết bị hỗ trợ Cast, thiết bị đó sẽ bị xoá khỏi nhóm động.