Kiểm soát và quảng cáo tính năng phát bằng MediaSession

Phiên phát nội dung nghe nhìn là cách thức tương tác phổ biến với trình phát âm thanh hoặc trình phát video. Trong Media3, trình phát mặc định là lớp ExoPlayer, triển khai giao diện Player. Việc kết nối phiên phát nội dung đa phương tiện với trình phát cho phép ứng dụng quảng cáo việc phát nội dung đa phương tiện ra bên ngoài và nhận lệnh phát từ các nguồn bên ngoài.

Lệnh có thể bắt nguồn từ các nút vật lý, chẳng hạn như nút phát trên tai nghe hoặc điều khiển TV. Các lệnh này cũng có thể đến từ những ứng dụng khách có bộ điều khiển nội dung nghe nhìn, chẳng hạn như lệnh "tạm dừng" cho các trợ lý AI, bao gồm cả Gemini hoặc Trợ lý Google. Phiên phát nội dung đa phương tiện sẽ uỷ quyền các lệnh này cho trình phát của ứng dụng đa phương tiện.

Trường hợp nên chọn phiên đa phương tiện

Khi triển khai MediaSession, bạn cho phép người dùng điều khiển chế độ phát:

  • Qua tai nghe. Thường có các nút hoặc thao tác chạm mà người dùng có thể thực hiện trên tai nghe để phát hoặc tạm dừng nội dung nghe nhìn hoặc chuyển đến bản nhạc tiếp theo hoặc trước đó.
  • Bằng cách trò chuyện với các trợ lý AI, bao gồm cả Gemini hoặc Trợ lý Google. Một mẫu phổ biến là nói "Ok Google, tạm dừng" để tạm dừng mọi nội dung nghe nhìn đang phát trên thiết bị.
  • Thông qua đồng hồ Wear OS. Nhờ đó, người dùng có thể dễ dàng sử dụng các bộ điều khiển chế độ phát phổ biến nhất khi phát nội dung trên điện thoại.
  • Thông qua Các nút điều khiển nội dung nghe nhìn. Băng chuyền này hiển thị các nút điều khiển cho từng phiên phát nội dung nghe nhìn đang chạy.
  • Trên TV. Cho phép các thao tác bằng nút phát vật lý, điều khiển phát trên nền tảng và quản lý nguồn (ví dụ: nếu TV, loa thanh hoặc bộ thu A/V tắt hoặc đầu vào được chuyển đổi, thì quá trình phát sẽ dừng trong ứng dụng).
  • Thông qua các nút điều khiển nội dung nghe nhìn của Android Auto. Nhờ đó, bạn có thể điều khiển chế độ phát một cách an toàn khi lái xe.
  • Và mọi quy trình bên ngoài khác cần ảnh hưởng đến quá trình phát.

Điều này rất phù hợp với nhiều trường hợp sử dụng. Cụ thể, bạn nên cân nhắc sử dụng MediaSession khi:

  • Bạn đang phát trực tuyến nội dung video dài, chẳng hạn như phim hoặc truyền hình trực tuyến.
  • Bạn đang phát trực tuyến nội dung âm thanh dài, chẳng hạn như podcast hoặc danh sách phát nhạc.
  • Bạn đang tạo một ứng dụng truyền hình.

Tuy nhiên, không phải trường hợp sử dụng nào cũng phù hợp với MediaSession. Bạn có thể chỉ muốn sử dụng Player trong các trường hợp sau:

  • Bạn đang chiếu nội dung dạng ngắn, không cần điều khiển bên ngoài hoặc phát trong nền.
  • Không có video nào đang hoạt động, chẳng hạn như người dùng đang cuộn qua một danh sách và nhiều video xuất hiện trên màn hình cùng một lúc.
  • Bạn đang phát video giới thiệu hoặc giải thích một lần, video mà bạn muốn người dùng chủ động xem mà không cần các bộ điều khiển chế độ phát bên ngoài.
  • Nội dung của bạn nhạy cảm về quyền riêng tư và bạn không muốn các quy trình bên ngoài truy cập vào siêu dữ liệu của nội dung nghe nhìn (ví dụ: chế độ ẩn danh trong trình duyệt).

Nếu trường hợp sử dụng của bạn không phù hợp với bất kỳ trường hợp nào nêu trên, hãy cân nhắc xem bạn có đồng ý cho ứng dụng tiếp tục phát khi người dùng không chủ động tương tác với nội dung hay không. Nếu câu trả lời là có, thì có thể bạn nên chọn MediaSession. Nếu câu trả lời là không, bạn nên sử dụng Player.

Tạo phiên phát nội dung nghe nhìn

Phiên phát nội dung nghe nhìn hoạt động cùng với trình phát mà phiên đó quản lý. Bạn có thể tạo một phiên phát nội dung nghe nhìn bằng đối tượng Context và Player. Bạn nên tạo và khởi động một phiên phát nội dung nghe nhìn khi cần, chẳng hạn như phương thức vòng đời onStart() hoặc onResume() của Activity hoặc Fragment, hoặc phương thức onCreate() của Service sở hữu phiên phát nội dung nghe nhìn và trình phát liên kết.

Để tạo một phiên đa phương tiện, hãy khởi động Player và cung cấp phiên đó cho MediaSession.Builder như sau:

Kotlin

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

Xử lý trạng thái tự động

Thư viện Media3 tự động cập nhật phiên phát nội dung nghe nhìn bằng trạng thái của trình phát. Do đó, bạn không cần phải xử lý việc liên kết từ người chơi đến phiên theo cách thủ công.

Điều này khác với phiên phát nội dung nghe nhìn của nền tảng, nơi bạn cần tạo và duy trì PlaybackState độc lập với chính trình phát, chẳng hạn như để cho biết mọi lỗi.

Mã phiên riêng biệt

Theo mặc định, MediaSession.Builder sẽ tạo một phiên có chuỗi trống làm mã phiên. Điều này là đủ nếu ứng dụng chỉ có ý định tạo một phiên bản phiên duy nhất, đây là trường hợp phổ biến nhất.

Nếu muốn quản lý nhiều phiên bản phiên cùng một lúc, ứng dụng phải đảm bảo rằng mã phiên của mỗi phiên là duy nhất. Bạn có thể đặt mã phiên khi tạo phiên bằng MediaSession.Builder.setId(String id).

Nếu bạn thấy IllegalStateException làm ứng dụng gặp sự cố kèm theo thông báo lỗi IllegalStateException: Session ID must be unique. ID=, thì có thể một phiên đã được tạo ngoài dự kiến trước khi một thực thể được tạo trước đó có cùng mã nhận dạng được phát hành. Để tránh rò rỉ phiên do lỗi lập trình, những trường hợp như vậy sẽ được phát hiện và thông báo bằng cách gửi một ngoại lệ.

Cấp quyền kiểm soát cho các ứng dụng khác

Phiên phát nội dung nghe nhìn là yếu tố then chốt để điều khiển việc phát. Lớp này cho phép bạn định tuyến các lệnh từ nguồn bên ngoài đến trình phát thực hiện công việc phát nội dung nghe nhìn. Những nguồn này có thể là các nút vật lý như nút phát trên tai nghe hoặc điều khiển TV, hoặc các lệnh gián tiếp như hướng dẫn "tạm dừng" cho Gemini hoặc Trợ lý Google. Tương tự, bạn có thể muốn cấp quyền truy cập vào hệ thống Android để điều khiển thông báo và màn hình khoá, hoặc cấp quyền truy cập vào đồng hồ Wear OS để có thể điều khiển chế độ phát từ mặt đồng hồ. Ứng dụng bên ngoài có thể sử dụng trình điều khiển nội dung nghe nhìn để phát lệnh phát cho ứng dụng đa phương tiện của bạn. Phiên đa phương tiện của bạn sẽ nhận các lệnh này và uỷ quyền cho trình phát nội dung nghe nhìn.

Hoạt động tương tác giữa MediaSession và MediaController.
Hình 1: Trình điều khiển nội dung nghe nhìn hỗ trợ việc truyền lệnh từ các nguồn bên ngoài đến phiên phát nội dung nghe nhìn.

Khi một bộ điều khiển sắp kết nối với phiên phát nội dung nghe nhìn của bạn, phiên đó sẽ gọi onConnectAsync(). Bạn có thể sử dụng ControllerInfo được cung cấp để quyết định xem nên chấp nhận hay từ chối yêu cầu. Để xem ví dụ về cách chấp nhận yêu cầu kết nối, hãy xem phần Khai báo và xử lý lệnh tuỳ chỉnh.

Sau khi kết nối, bộ điều khiển có thể gửi lệnh phát đến phiên. Sau đó, phiên sẽ uỷ quyền các lệnh đó cho trình phát. Các lệnh phát và danh sách phát được xác định trong giao diện Player sẽ tự động được phiên xử lý.

Các phương thức gọi lại khác cho phép bạn xử lý, chẳng hạn như yêu cầu về lệnh tuỳ chỉnh và sửa đổi danh sách phát. Tương tự, các lệnh gọi lại này bao gồm một đối tượng ControllerInfo để bạn có thể sửa đổi cách phản hồi từng yêu cầu trên cơ sở từng bộ điều khiển.

Sửa đổi danh sách phát

Phiên phát nội dung nghe nhìn có thể trực tiếp sửa đổi danh sách phát của trình phát như giải thích trong hướng dẫn về danh sách phát của ExoPlayer. Bộ điều khiển cũng có thể sửa đổi danh sách phát nếu bộ điều khiển có COMMAND_SET_MEDIA_ITEM hoặc COMMAND_CHANGE_MEDIA_ITEMS.

Khi thêm các mục mới vào danh sách phát, trình phát thường yêu cầu các thực thể MediaItem có URI đã xác định để có thể phát các mục đó. Theo mặc định, các mục mới thêm sẽ tự động được chuyển tiếp đến các phương thức của trình phát như player.addMediaItem nếu các mục đó có URI được xác định.

Nếu muốn tuỳ chỉnh các thực thể MediaItem được thêm vào trình phát, bạn có thể ghi đè onAddMediaItems(). Bạn cần thực hiện bước này khi muốn hỗ trợ các bộ điều khiển yêu cầu nội dung nghe nhìn mà không có URI xác định. Thay vào đó, MediaItem thường có một hoặc nhiều trường sau đây được đặt để mô tả nội dung nghe nhìn được yêu cầu:

  • MediaItem.mediaId: Mã nhận dạng chung xác định nội dung nghe nhìn.
  • MediaItem.RequestMetadata.mediaUri: URI yêu cầu có thể sử dụng giản đồ tuỳ chỉnh và không nhất thiết phải phát trực tiếp được bằng trình phát.
  • MediaItem.RequestMetadata.searchQuery: Cụm từ tìm kiếm dạng văn bản, ví dụ: từ Gemini hoặc Trợ lý Google.
  • MediaItem.mediaMetadata: Siêu dữ liệu có cấu trúc như "tiêu đề" hoặc "nghệ sĩ".

Để có thêm các lựa chọn tuỳ chỉnh cho danh sách phát hoàn toàn mới, bạn có thể ghi đè thêm onSetMediaItems(), cho phép bạn xác định mục bắt đầu và vị trí trong danh sách phát. Ví dụ: bạn có thể mở rộng một mục được yêu cầu thành toàn bộ danh sách phát và hướng dẫn trình phát bắt đầu tại chỉ mục của mục được yêu cầu ban đầu. Để xem mẫu triển khai onSetMediaItems() có tính năng này, hãy xem ứng dụng minh hoạ phiên.

Quản lý lựa chọn ưu tiên về nút nội dung nghe nhìn

Mỗi bộ điều khiển, ví dụ: Giao diện người dùng hệ thống, Android Auto hoặc Wear OS, có thể đưa ra quyết định riêng về việc hiển thị nút nào cho người dùng. Để cho biết những bộ điều khiển chế độ phát nào bạn muốn hiển thị cho người dùng, bạn có thể chỉ định lựa chọn ưu tiên về nút nội dung nghe nhìn trên MediaSession. Các lựa chọn ưu tiên này bao gồm một danh sách theo thứ tự gồm các thực thể CommandButton, mỗi thực thể xác định một lựa chọn ưu tiên cho một nút trong giao diện người dùng.

Xác định nút lệnh

Các thực thể CommandButton được dùng để xác định lựa chọn ưu tiên về nút nội dung nghe nhìn. Mỗi nút xác định 3 khía cạnh của phần tử trên giao diện người dùng mong muốn:

  1. Biểu tượng, xác định giao diện hình ảnh. Biểu tượng phải được đặt thành một trong các hằng số được xác định trước khi tạo CommandButton.Builder. Xin lưu ý rằng đây không phải là tài nguyên Bitmap hoặc hình ảnh thực tế. Hằng số chung giúp bộ điều khiển chọn tài nguyên phù hợp để có giao diện nhất quán trong giao diện người dùng của riêng chúng. Nếu không có hằng số biểu tượng nào được xác định trước phù hợp với trường hợp sử dụng của bạn, thì bạn có thể sử dụng setCustomIconResId.
  2. Lệnh, xác định hành động được kích hoạt khi người dùng tương tác với nút. Bạn có thể sử dụng setPlayerCommand cho Player.Command hoặc setSessionCommand cho SessionCommand được xác định trước hoặc tuỳ chỉnh.
  3. Vị trí, xác định vị trí đặt nút trong giao diện người dùng của bộ điều khiển. Trường này không bắt buộc và được đặt tự động dựa trên Biểu tượng và Lệnh. Ví dụ: thuộc tính này cho phép chỉ định rằng một nút sẽ xuất hiện trong khu vực điều hướng "tiếp theo" của giao diện người dùng thay vì khu vực "mục bổ sung" mặc định.

Kotlin

val button =
  CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
    .setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
    .setSlots(CommandButton.SLOT_FORWARD)
    .build()

Java

CommandButton button =
    new CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
        .setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
        .setSlots(CommandButton.SLOT_FORWARD)
        .build();

Khi các lựa chọn ưu tiên về nút nội dung nghe nhìn được giải quyết, thuật toán sau sẽ được áp dụng:

  1. Đối với mỗi CommandButton trong lựa chọn ưu tiên về nút điều khiển nội dung nghe nhìn, hãy đặt nút đó vào vị trí đầu tiên được cho phép và còn trống.
  2. Nếu bất kỳ vị trí trung tâm, tiến và lùi nào không được lấp đầy bằng một nút, hãy thêm các nút mặc định cho vị trí này.

Bạn có thể sử dụng CommandButton.DisplayConstraints để tạo bản xem trước về cách giải quyết các lựa chọn ưu tiên về nút nội dung nghe nhìn, tuỳ thuộc vào các quy tắc ràng buộc về việc hiển thị giao diện người dùng.

Đặt lựa chọn ưu tiên cho nút nội dung nghe nhìn

Cách dễ nhất để đặt lựa chọn ưu tiên cho nút điều khiển nội dung nghe nhìn là xác định danh sách khi tạo MediaSession. Ngoài ra, bạn có thể ghi đè MediaSession.Callback.onConnectAsync() để tuỳ chỉnh lựa chọn ưu tiên về nút nội dung nghe nhìn cho từng bộ điều khiển đã kết nối.

Kotlin

val mediaSession =
  MediaSession.Builder(context, player)
    .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton))
    .build()

Java

MediaSession mediaSession =
    new MediaSession.Builder(context, player)
        .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton))
        .build();

Cập nhật lựa chọn ưu tiên về nút nội dung nghe nhìn sau khi người dùng tương tác

Sau khi xử lý một lượt tương tác với trình phát, bạn nên cập nhật các nút hiển thị trong giao diện người dùng của bộ điều khiển. Ví dụ điển hình là nút bật/tắt thay đổi biểu tượng và hành động sau khi kích hoạt hành động liên kết với nút này. Để cập nhật lựa chọn ưu tiên về nút điều khiển nội dung nghe nhìn, bạn có thể sử dụng MediaSession.setMediaButtonPreferences để cập nhật lựa chọn ưu tiên cho tất cả bộ điều khiển hoặc một bộ điều khiển cụ thể:

Kotlin

// Handle "favoritesButton" action, replace by opposite button
mediaSession.setMediaButtonPreferences(ImmutableList.of(likeButton, removeFromFavoritesButton))

Java

// Handle "favoritesButton" action, replace by opposite button
mediaSession.setMediaButtonPreferences(ImmutableList.of(likeButton, removeFromFavoritesButton));

Thêm lệnh tuỳ chỉnh và tuỳ chỉnh hành vi mặc định

Bạn có thể mở rộng các lệnh hiện có của trình phát bằng các lệnh tuỳ chỉnh, đồng thời có thể chặn các lệnh đến của trình phát và các nút nội dung nghe nhìn để thay đổi hành vi mặc định.

Khai báo và xử lý các lệnh tuỳ chỉnh

Ứng dụng đa phương tiện có thể xác định các lệnh tuỳ chỉnh, chẳng hạn như có thể dùng trong lựa chọn ưu tiên về nút đa phương tiện. Ví dụ: bạn có thể muốn triển khai các nút cho phép người dùng lưu một mục nội dung nghe nhìn vào danh sách các mục yêu thích. MediaController gửi lệnh tuỳ chỉnh và MediaSession.Callback nhận lệnh.

Để xác định lệnh tuỳ chỉnh, bạn cần ghi đè MediaSession.Callback.onConnectAsync() để đặt các lệnh tuỳ chỉnh có sẵn cho từng bộ điều khiển đã kết nối.

Kotlin

private class CustomMediaSessionCallback : MediaSession.Callback {

  // Configure commands available to the controller in onConnect()
  override fun onConnectAsync(
    session: MediaSession,
    controller: ControllerInfo,
  ): ListenableFuture<ConnectionResult> {
    val sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY))
        .build()
    return Futures.immediateFuture(
      AcceptedResultBuilder(session, controller)
        .setAvailableSessionCommands(sessionCommands)
        .build()
    )
  }
}

Java

private static class CustomMediaSessionCallback implements MediaSession.Callback {

  // Configure commands available to the controller in onConnect()
  @Override
  public ListenableFuture<ConnectionResult> onConnectAsync(
      MediaSession session, ControllerInfo controller) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS
            .buildUpon()
            .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
            .build();
    return Futures.immediateFuture(
        new AcceptedResultBuilder(session, controller)
            .setAvailableSessionCommands(sessionCommands)
            .build());
  }
}

Để nhận yêu cầu lệnh tuỳ chỉnh từ MediaController, hãy ghi đè phương thức onCustomCommand() trong Callback.

Kotlin

private class CustomCallback : MediaSession.Callback {
  // ...
  override fun onCustomCommand(
    session: MediaSession,
    controller: ControllerInfo,
    customCommand: SessionCommand,
    args: Bundle,
  ): ListenableFuture<SessionResult> {
    if (customCommand.customAction == SAVE_TO_FAVORITES) {
      // Do custom logic here
      saveToFavorites(session.player.currentMediaItem)
      return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
    }
    // ...
    return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
  }
}

Java

private static class CustomCallback implements MediaSession.Callback {
  // ...
  @Override
  public ListenableFuture<SessionResult> onCustomCommand(
      MediaSession session,
      ControllerInfo controller,
      SessionCommand customCommand,
      Bundle args) {
    if (customCommand.customAction.equals(SAVE_TO_FAVORITES)) {
      // Do custom logic here
      saveToFavorites(session.getPlayer().getCurrentMediaItem());
      return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
    }
    // ...
    return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
  }
}

Bạn có thể theo dõi trình điều khiển nội dung nghe nhìn nào đang đưa ra yêu cầu bằng cách sử dụng thuộc tính packageName của đối tượng MediaSession.ControllerInfo được truyền vào các phương thức Callback. Nhờ vậy, bạn có thể điều chỉnh hành vi của ứng dụng để phản hồi một lệnh nhất định nếu lệnh đó bắt nguồn từ hệ thống, ứng dụng của riêng bạn hoặc các ứng dụng khách khác.

Tuỳ chỉnh các lệnh mặc định của trình phát

Tất cả các lệnh mặc định và việc xử lý trạng thái đều được uỷ quyền cho Player trên MediaSession. Để tuỳ chỉnh hành vi của một lệnh được xác định trong giao diện Player, chẳng hạn như play() hoặc seekToNext(), hãy gói Player của bạn trong ForwardingSimpleBasePlayer trước khi truyền lệnh đó đến MediaSession:

Kotlin

val forwardingPlayer =
  object : ForwardingSimpleBasePlayer(player) {
    // Customizations
  }

val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()

Java

ForwardingSimpleBasePlayer forwardingPlayer = new ForwardingSimpleBasePlayer(player) {
      // Customizations
    };

MediaSession mediaSession = new MediaSession.Builder(context, forwardingPlayer).build();

Để biết thêm thông tin về ForwardingSimpleBasePlayer, hãy xem hướng dẫn về ExoPlayer về Tuỳ chỉnh.

Xác định bộ điều khiển yêu cầu của lệnh trình phát

Khi lệnh gọi đến phương thức Player bắt nguồn từ MediaController, bạn có thể xác định nguồn gốc bằng MediaSession.controllerForCurrentRequest và lấy ControllerInfo cho yêu cầu hiện tại:

Kotlin

private class CallerAwarePlayer(player: Player) : ForwardingSimpleBasePlayer(player) {
  private lateinit var session: MediaSession

  override fun handleSeek(
    mediaItemIndex: Int,
    positionMs: Long,
    seekCommand: Int,
  ): ListenableFuture<*> {
    Log.d(
      "caller",
      "seek operation from package ${session.controllerForCurrentRequest?.packageName}",
    )
    return super.handleSeek(mediaItemIndex, positionMs, seekCommand)
  }
}

Java

private static final class CallerAwarePlayer extends ForwardingSimpleBasePlayer {
  private MediaSession session;

  public CallerAwarePlayer(Player player) {
    super(player);
  }

  @Override
  protected ListenableFuture<?> handleSeek(int mediaItemIndex, long positionMs, int seekCommand) {
    Log.d(
        "caller",
        "seek operation from package: "
            + session.getControllerForCurrentRequest().getPackageName());
    return super.handleSeek(mediaItemIndex, positionMs, seekCommand);
  }
}

Tuỳ chỉnh cách xử lý nút nội dung nghe nhìn

Nút nội dung nghe nhìn là các nút phần cứng có trên thiết bị Android và các thiết bị ngoại vi khác, chẳng hạn như nút phát/tạm dừng trên tai nghe Bluetooth. Media3 xử lý các sự kiện nút nội dung nghe nhìn cho bạn khi các sự kiện đó đến phiên và gọi phương thức Player thích hợp trên trình phát phiên.

Bạn nên xử lý tất cả sự kiện nút nội dung nghe nhìn sắp tới trong phương thức Player tương ứng. Đối với các trường hợp sử dụng nâng cao hơn, bạn có thể chặn các sự kiện nút nội dung nghe nhìn trong MediaSession.Callback.onMediaButtonEvent().

Xử lý và báo cáo lỗi

Có hai loại lỗi mà một phiên phát ra và báo cáo cho bộ điều khiển. Lỗi nghiêm trọng báo cáo lỗi phát lại kỹ thuật của trình phát phiên làm gián đoạn quá trình phát. Lỗi nghiêm trọng được báo cáo tự động cho bộ điều khiển khi xảy ra. Lỗi không nghiêm trọng là lỗi không liên quan đến kỹ thuật hoặc chính sách, không làm gián đoạn quá trình phát và được ứng dụng gửi đến bộ điều khiển theo cách thủ công.

Lỗi phát nghiêm trọng

Trình phát sẽ báo cáo lỗi phát nghiêm trọng cho phiên, sau đó báo cáo cho bộ điều khiển để gọi thông qua Player.Listener.onPlayerError(PlaybackException) và Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException).

Trong trường hợp này, trạng thái phát sẽ chuyển sang STATE_IDLE và MediaController.getPlayerError() sẽ trả về PlaybackException gây ra quá trình chuyển đổi. Bộ điều khiển có thể kiểm tra PlaybackException.errorCode để lấy thông tin về lý do gây ra lỗi.

Đặt lỗi trình phát tuỳ chỉnh

Ngoài các lỗi nghiêm trọng do trình phát báo cáo, ứng dụng có thể đặt PlaybackException tuỳ chỉnh ở cấp MediaSession bằng MediaSession.setPlaybackException(PlaybackException). Điều này cho phép ứng dụng báo hiệu trạng thái lỗi cho các bộ điều khiển đã kết nối. Bạn có thể đặt ngoại lệ cho tất cả bộ điều khiển đã kết nối hoặc cho một ControllerInfo cụ thể.

Khi một ứng dụng đặt PlaybackException bằng API này:

  • Các phiên bản MediaController đã kết nối sẽ nhận được thông báo. Các lệnh gọi lại Listener.onPlayerError(PlaybackException) và Listener.onPlayerErrorChanged(@Nullable PlaybackException) trên trình điều khiển sẽ được gọi kèm theo ngoại lệ được cung cấp.

  • Phương thức MediaController.getPlayerError() sẽ trả về PlaybackException do ứng dụng đặt.

  • Trạng thái phát của các bộ điều khiển bị ảnh hưởng sẽ thay đổi thành Player.STATE_IDLE.

  • Các lệnh có sẵn sẽ bị xoá và chỉ còn lại các lệnh đọc như COMMAND_GET_TIMELINE trong trường hợp các lệnh này đã được cấp. Ví dụ: trạng thái của Timeline bị đóng băng ở trạng thái khi ngoại lệ được áp dụng cho trình điều khiển. Các lệnh cố gắng thay đổi trạng thái của trình phát, chẳng hạn như COMMAND_PLAY, sẽ bị xoá cho đến khi ứng dụng xoá ngoại lệ phát cho trình điều khiển đã cho.

Để xoá PlaybackException tuỳ chỉnh đã đặt trước đó và khôi phục báo cáo trạng thái trình phát thông thường, ứng dụng có thể gọi MediaSession.setPlaybackException(/* playbackException= */ null) hoặc MediaSession.setPlaybackException(ControllerInfo, /* playbackException= */ null).

Tuỳ chỉnh lỗi nghiêm trọng

Để cung cấp thông tin phù hợp và có ý nghĩa cho người dùng, bạn có thể tuỳ chỉnh mã lỗi, thông báo lỗi và các thông tin bổ sung về lỗi phát nghiêm trọng từ trình phát thực tế. Để thực hiện việc này, hãy sử dụng ForwardingSimpleBasePlayer khi tạo phiên:

Kotlin

val session = MediaSession.Builder(context, ErrorForwardingPlayer(context, player)).build()

Java

MediaSession session =
    new MediaSession.Builder(context, new ErrorForwardingPlayer(context, player)).build();

Lớp con ForwardingSimpleBasePlayer của bạn có thể ghi đè getState() để chặn lỗi và tuỳ chỉnh mã lỗi, thông báo hoặc các thông tin bổ sung. Tương tự, bạn cũng có thể tạo ra các lỗi mới không tồn tại trong trình phát ban đầu:

Kotlin

private class ErrorForwardingPlayer(private val context: Context, player: Player) :
  ForwardingSimpleBasePlayer(player) {

  override fun getState(): State {
    var state = super.getState()
    if (state.playerError != null) {
      state =
        state.buildUpon().setPlayerError(customizePlaybackException(state.playerError!!)).build()
    }
    return state
  }

  private fun customizePlaybackException(error: PlaybackException): PlaybackException {
    val buttonLabel: String
    val errorMessage: String
    when (error.errorCode) {
      PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW -> {
        buttonLabel = context.getString(R.string.err_button_label_restart_stream)
        errorMessage = context.getString(R.string.err_msg_behind_live_window)
      }
      else -> {
        buttonLabel = context.getString(R.string.err_button_label_ok)
        errorMessage = context.getString(R.string.err_message_default)
      }
    }
    val extras = Bundle()
    extras.putString("button_label", buttonLabel)
    return PlaybackException(errorMessage, error.cause, error.errorCode, extras)
  }
}

Java

private static class ErrorForwardingPlayer extends ForwardingSimpleBasePlayer {

  private final Context context;

  public ErrorForwardingPlayer(Context context, Player player) {
    super(player);
    this.context = context;
  }

  @Override
  protected State getState() {
    State state = super.getState();
    if (state.playerError != null) {
      state =
          state.buildUpon().setPlayerError(customizePlaybackException(state.playerError)).build();
    }
    return state;
  }

  private PlaybackException customizePlaybackException(PlaybackException error) {
    String buttonLabel;
    String errorMessage;
    switch (error.errorCode) {
      case PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW:
        buttonLabel = context.getString(R.string.err_button_label_restart_stream);
        errorMessage = context.getString(R.string.err_msg_behind_live_window);
        break;
      default:
        buttonLabel = context.getString(R.string.err_button_label_ok);
        errorMessage = context.getString(R.string.err_message_default);
        break;
    }
    Bundle extras = new Bundle();
    extras.putString("button_label", buttonLabel);
    return new PlaybackException(errorMessage, error.getCause(), error.errorCode, extras);
  }
}

Lỗi không nghiêm trọng

Ứng dụng có thể gửi lỗi không nghiêm trọng không bắt nguồn từ ngoại lệ kỹ thuật đến tất cả hoặc một bộ điều khiển cụ thể:

Kotlin

val sessionError =
  SessionError(
    SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
    context.getString(R.string.error_message_authentication_expired),
  )

// Option 1: Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError)

// Option 2: Sending a nonfatal error to the media notification controller only
// to set the error code and error message in the playback state of the platform
// media session.
mediaSession.mediaNotificationControllerInfo?.let { mediaSession.sendError(it, sessionError) }

Java

SessionError sessionError =
    new SessionError(
        SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
        context.getString(R.string.error_message_authentication_expired));

// Option 1: Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError);

// Option 2: Sending a nonfatal error to the media notification controller only
// to set the error code and error message in the playback state of the platform
// media session.
ControllerInfo mediaNotificationControllerInfo =
    mediaSession.getMediaNotificationControllerInfo();
if (mediaNotificationControllerInfo != null) {
  mediaSession.sendError(mediaNotificationControllerInfo, sessionError);
}

Khi một lỗi không nghiêm trọng được gửi đến trình điều khiển thông báo nội dung nghe nhìn, mã lỗi và thông báo lỗi sẽ được sao chép vào phiên nội dung nghe nhìn của nền tảng, trong khi PlaybackState.state không được thay đổi thành STATE_ERROR.

Nhận lỗi không nghiêm trọng

MediaController nhận được lỗi không nghiêm trọng bằng cách triển khai MediaController.Listener.onError:

Kotlin

val future =
  MediaController.Builder(context, sessionToken)
    .setListener(
      object : MediaController.Listener {
        override fun onError(controller: MediaController, sessionError: SessionError) {
          // Handle nonfatal error.
        }
      }
    )
    .buildAsync()

Java

MediaController.Builder future =
    new MediaController.Builder(context, sessionToken)
        .setListener(
            new MediaController.Listener() {
              @Override
              public void onError(MediaController controller, SessionError sessionError) {
                // Handle nonfatal error.
              }
            });