Trình chuyển đổi đầu ra

Nút chuyển đầu ra là một tính năng của Cast SDK cho phép chuyển dữ liệu liền mạch giữa chế độ phát nội dung trên máy và từ xa, kể từ Android 13. Mục tiêu giúp các ứng dụng của người 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 sang chuyển đổi chế độ 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ị hỗ trợ Cast từ xa. Các trường hợp sử dụng có thể được chia thành những trường hợp sau trường hợp:

Bạn phải bật Output Switcher để hỗ trợ từ xa cục bộ, từ xa đến cục bộ và điều khiển từ xa bằng cách làm theo các bước trong tài liệu hướng dẫn này. Không có các bước bổ sung cần thực hiện để hỗ trợ quá trình chuyển giữa các thiết bị cục bộ loa và thiết bị Bluetooth được ghép nối.

Giao diện người dùng của Trình chuyển đổi đầu ra

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

Vấn đề đã biết

  • Các phiên phát nội dung đa phương tiện được tạo để phát trên thiết bị sẽ bị đóng và tạo lại khi chuyển sang thông báo về SDK truyền.

Đ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 cho phát trên thiết bị (phát trên thiết bị), góc trên cùng bên phải của thông báo về nội dung nghe nhìn 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) nội dung hiện đang được phát trên đó. Thao tác nhấn vào khối thông báo sẽ mở ra giao diện người dùng hệ thống của hộp thoại Output Switcher (Trình chuyển đổi đầu 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 Trình chuyển đổi đầu ra bằng cách nhấp vào 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 "Play <App Name> (Phát <Tên ứng dụng>) trên <Cast Device>" .

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. Hãy cập nhật build.gradle của ứng dụng để dùng phiên bản tối thiểu bắt buộc của SDK người gửi Android 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, audio và ứng dụng video cần phải tạo 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 nội dung đa phương tiện để phát trên thiết bị. Để làm được điều này, bạn phải tạo một MediaSession! thiết lập MediaStyle bằng mã thông báo của MediaSession và đặt chế độ điều khiển nội dung nghe nhìn trên .

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

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 về nội dung nghe nhìn vào thông báo, bạn sẽ cần thêm siêu dữ liệu và trạng thái phát đến MediaSession.

Để thêm siêu dữ liệu vào MediaSession, hãy sử dụng setMetaData() và cung cấp mọi thông tin Hằng số MediaMetadata cho nội dung đa phương tiệ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 mọi thông tin PlaybackStateCompat hằng số cho nội dung đa phương tiệ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

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

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

Bật Trình chuyển đổi đầu ra trong AndroidManifest.xml

Để bật Trình chuyển đổi đầu ra, MediaTransferReceiver cần được thêm 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 từ xa đến cục bộ cũng sẽ không hợp lệ.

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

Chiến lược phát hành đĩa đơn MediaTransferReceiver là broadcast receiver cho phép truyền nội dung nghe nhìn giữa các thiết bị có hệ thống Giao diện người dùng. Xem lớp MediaTransferReceiver tham khảo để biết thêm thông tin.

Từ cục bộ đến từ xa

Khi người dùng chuyển chế độ phát từ thiết bị cục bộ sang điều khiển từ xa, SDK Truyền sẽ khởi động phiên Truyền. Tuy nhiên, các ứng dụng cần phải xử lý việc chuyển đổi từ từ cục bộ sang điều khiển từ xa, ví dụ: dừng phát trên thiết bị và tải nội dung nghe nhìn trên Thiết bị truyền. Các ứng dụng cần theo dõi Cast SessionManagerListener! sử dụng onSessionStarted()onSessionEnded() các lệnh gọi lại và xử lý tác vụ khi nhận được dữ liệu Truyền SessionManager lệnh gọi lại. Ứ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 Output Switcher (Trình chuyển đổi đầu ra) đang mở và ứng dụng không chạy ở nền trước.

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

Phiên bản Cast cũ đã hỗ trợ tính năng từ xa cục bộ đến từ xa khi ứng dụng ở nền trước. Trải nghiệm Truyền thông thường bắt đầu khi người dùng nhấp vào biểu tượng Truyền trong ứng dụng rồi chọn một thiết bị để phát trực tuyến 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 trong ứng dụng.

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

Nếu ứng dụng sử dụng MediaBrowserService! bạn nên đăng ký 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);
    }
  }
}

Với bản cập nhật này, điều khiển từ xa cục bộ đến từ xa hoạt động giống như tính năng truyền thông thường khi ứng dụng chạy ở chế độ nền và bạn không cần làm gì thêm để chuyển đổi từ Thiết bị Bluetooth để truyền thiết bị.

Từ xa đến cục bộ

Nút chuyển đầu ra hỗ trợ khả năng truyền từ phát từ xa sang loa điện thoại hoặc thiết bị Bluetooth trong nhà. Bạn có thể bật tính năng này bằng cách đặt setRemoteToLocalEnabled gắn cờ true trên CastOptions.

Đối với trường hợp thiết bị hiện tại của người gửi tham gia một phiên hiện có bằng nhiều người 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 được chuyển cục bộ, các ứng dụng sẽ dùng onTransferred lệnh gọi lại của SessionTransferCallback để kiểm tra SessionState.

Đặt cờ setRemoteToLocalEnabled

CastOptions.Builder cung cấp setRemoteToLocalEnabled để hiện hoặc ẩn loa điện thoại và các thiết bị Bluetooth cục bộ dưới dạng thiết bị chuyển dữ liệu sang trong hộp thoại Output Switcher (Trình chuyển đổi đầu ra) khi có một phiên Truyền đ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 trên thiết bị

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

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

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

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

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

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

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

Đối với các ứng dụng hỗ trợ tính năng phát trong nền (thường là ứng dụng âm thanh), nên sử dụng Service (ví dụ: MediaBrowserService). Dịch vụ nên nghe onTransferred gọi lại và tiếp tục phát trên thiết bị ngay cả khi ứng dụng đang chạy trên 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.
        }
    }
}

Ứng dụng không hỗ trợ tính năng phát trong nền

Đối với những ứng dụng không hỗ trợ tính năng phát trong nền (thường là ứng dụng video), nên nghe onTransferred gọi lại và tiếp tục phát trên thiết bị nếu ứng dụng đang chạy trên nền trước.

Nếu chạy trong nền thì ứ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 chạy ở chế độ nền trước từ nền trước, quá trình phát cục bộ sẽ tiếp tục với thông tin đã lưu.

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

Điều khiển từ xa

Nút chuyển đầu ra hỗ trợ khả năng mở rộng sang nhiều thiết bị hỗ trợ Cast thiết bị loa cho ứng dụng âm thanh sử dụng tính năng Mở rộng luồng.

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

Mở rộng nội dung phát trực tuyến bằng loa

Các ứng dụng âm thanh sử dụng Nút chuyển đầu ra có thể mở rộng âm thanh đến nhiều thiết bị loa hỗ trợ tính năng Truyền trong một phiên Truyền bằng Luồng Mở rộng.

Tính năng này được nền tảng Truyền hỗ trợ và bạn không cần phải làm gì thêm sẽ thay đổi nếu ứng dụng đang dùng giao diện người dùng mặc định. Nếu được 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.

Cách lấy tên nhóm mở rộng mới trong quá trình mở rộng luồng: đăng ký một Cast.Listener sử dụ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ử tính năng từ xa đến từ xa

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

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