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 đa phương tiện là một cách thức tương tác phổ biến với trình phát âm thanh hoặc video. Trong Media3, trình phát mặc định là lớp ExoPlayer, lớp này sẽ triển khai giao diện Player. Khi kết nối phiên phát nội dung đa phương tiện với trình phát, một ứng dụng có thể quảng cáo việc phát nội dung đa phương tiện bên ngoài và nhận lệnh phát từ các nguồn bên ngoài.

Các 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 từ xa của TV. Các lệnh này cũng có thể đến từ các ứng dụng khách có trình điều khiển nội dung đa phương tiện, chẳng hạn như hướng dẫn "tạm dừng" cho 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.

Thời điểm chọn phiên phát nội dung nghe nhìn

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

  • Thông qua tai nghe. Thường thì người dùng có thể thực hiện các nút hoặc thao tác chạm 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 nói chuyện với Trợ lý Google. Một mẫu hình 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ờ vậy, họ có thể dễ dàng truy cập vào các bộ điều khiển chế độ phát phổ biến nhất khi phát trên điện thoại.
  • Thông qua Chế độ điều khiển nội dung nghe nhìn. Băng chuyền này cho thấy các chế độ điều khiển cho mỗi phiên phát nội dung đa phương tiện đang chạy.
  • Trên TV. Cho phép các thao tác bằng nút phát vật lý, chức năng điều khiển chế độ phát trên nền tảng và quản lý nguồn điện (ví dụ: nếu TV, loa thanh hoặc bộ thu A/V bị tắt hoặc đầu vào được chuyển, thì quá trình phát sẽ dừng trong ứng dụng).
  • Cũng như mọi quy trình bên ngoài khác cần ảnh hưởng đến việc phát.

Điều này rất hữu ích cho 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 tiếp nội dung video dài, chẳng hạn như phim hoặc chương trình truyền hình trực tuyến.
  • Bạn đang phát trực tiếp nội dung âm thanh có thời lượng 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 chỉ nên sử dụng Player trong các trường hợp sau:

  • Bạn đang hiển thị nội dung dạng ngắn, trong đó mức độ tương tác và tương tác của người dùng đóng vai trò quan trọng.
  • Không có một video hoạt động duy nhất nào, chẳng hạn như người dùng đang cuộn qua danh sách và nhiều video hiển thị cùng lúc trên màn hình.
  • Bạn đang phát một video giới thiệu hoặc giải thích một lần mà bạn mong muốn người dùng sẽ tích cực xem.
  • Nội dung của bạn phụ thuộc vào 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 đa phương tiệ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ó muốn ứng dụng tiếp tục phát khi người dùng không tương tác tích cực với nội dung hay không. Nếu câu trả lời là có, có thể bạn sẽ muốn chọn MediaSession. Nếu câu trả lời là không, thì bạn có thể cần sử dụng Player.

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

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

Để tạo một phiên phát nội dung đa phương tiện, hãy khởi chạy Player và cung cấp mã đó 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 sẽ tự động cập nhật phiên phát nội dung đa phương tiện bằng cách sử dụng trạng thái của trình phát. Do đó, bạn không cần phải xử lý việc ánh xạ từ người chơi sang phiên theo cách thủ công.

Đây là điểm khác biệt so với phương pháp cũ, trong đó bạn cần tạo và duy trì PlaybackStateCompat một cách độc lập với chính trình phát, chẳng hạn như để chỉ báo lỗi.

Mã phiên duy nhất

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

Nếu một ứng dụng muốn quản lý nhiều thực thể phiên cùng một lúc, thì ứ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 gây ra sự cố cho ứng dụng kèm theo thông báo lỗi IllegalStateException: Session ID must be unique. ID= thì có khả năng một phiên đã được tạo đột ngột 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 các phiên bị rò rỉ 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 trường hợp 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 đa phương tiện là chìa khoá để điều khiển quá trình phát. Thư viện này cho phép bạn định tuyến các lệnh từ các nguồn bên ngoài đến trình phát thực hiện công việc phát nội dung đa phương tiện của bạn. Các nguồn này có thể là 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 từ xa của TV, hoặc các lệnh gián tiếp như hướng dẫn "tạm dừng" đối với Trợ lý Google. Tương tự, bạn nên cấp quyền truy cập vào hệ thống Android để hỗ trợ các chế độ điều khiển thông báo và màn hình khoá, hoặc cho đồng hồ Wear OS để bạn có thể điều khiển chế độ phát trên mặt đồng hồ. Các ứng dụng bên ngoài có thể sử dụng trình điều khiển nội dung đa phương tiện để đưa ra lệnh phát nội dung đa phương tiện cho ứng dụng đa phương tiện. Phiên phát nội dung đa phương tiện sẽ nhận được lệnh này và phiên phát nội dung đa phương tiện sẽ uỷ quyền các lệnh cho trình phát nội dung đa phương tiện.

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

Khi tay điều khiển sắp kết nối với phiên phát nội dung đa phương tiện của bạn, phương thức onConnect() sẽ được gọi. 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 trong mục Khai báo các lệnh có sẵn.

Sau khi kết nối, tay đ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ẽ do phiên xử lý tự động.

Các phương thức gọi lại khác cho phép bạn xử lý các yêu cầu lệnh phát tuỳ chỉnhsửa đổi danh sách phát chẳng hạn). Tương tự, các lệnh gọi lại này bao gồm đố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 đơn vị kiểm soát.

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

Phiên phát nội dung đa phương tiệ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. Các tay điều khiển cũng có thể sửa đổi danh sách phát nếu COMMAND_SET_MEDIA_ITEM hoặc COMMAND_CHANGE_MEDIA_ITEMS có sẵn cho tay điều khiển.

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ể MediaItemURI được xác định để có thể phát các thực thể đó. Theo mặc định, các mục mới thêm sẽ được tự động chuyển tiếp đến các phương thức của trình phát như player.addMediaItem nếu chúng đã xác định URI.

Nếu muốn tuỳ chỉnh các thực thể MediaItem đã thêm vào trình phát, bạn có thể ghi đè onAddMediaItems(). Đây là bước cần thiết khi bạn muốn hỗ trợ các tay điều khiển yêu cầu nội dung nghe nhìn mà không cần 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.id: Mã nhận dạng chung xác định nội dung đa phương tiện.
  • MediaItem.RequestMetadata.mediaUri: URI yêu cầu có thể sử dụng một giản đồ tuỳ chỉnh và không nhất thiết là người chơi có thể phát trực tiếp.
  • MediaItem.RequestMetadata.searchQuery: Một cụm từ tìm kiếm bằng văn bản, chẳng hạn như từ 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 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() để 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 ra toàn bộ danh sách phát và hướng dẫn người chơi bắt đầu tại chỉ mục của mục được yêu cầu ban đầu. Bạn có thể xem mẫu cách triển khai onSetMediaItems() với tính năng này trong ứng dụng minh hoạ phiên.

Quản lý bố cục tuỳ chỉnh và các lệnh tuỳ chỉnh

Các phần sau đây mô tả cách quảng cáo bố cục tuỳ chỉnh của các nút lệnh tuỳ chỉnh cho các ứng dụng khách và uỷ quyền cho tay điều khiển gửi lệnh tuỳ chỉnh.

Xác định bố cục tuỳ chỉnh của phiên

Để cho các ứng dụng khách biết bộ điều khiển chế độ phát nào mà bạn muốn người dùng thấy, hãy đặt bố cục tuỳ chỉnh của phiên khi tạo MediaSession trong phương thức onCreate() của dịch vụ.

Kotlin

override fun onCreate() {
  super.onCreate()

  val likeButton = CommandButton.Builder()
    .setDisplayName("Like")
    .setIconResId(R.drawable.like_icon)
    .setSessionCommand(SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING))
    .build()
  val favoriteButton = CommandButton.Builder()
    .setDisplayName("Save to favorites")
    .setIconResId(R.drawable.favorite_icon)
    .setSessionCommand(SessionCommand(SAVE_TO_FAVORITES, Bundle()))
    .build()

  session =
    MediaSession.Builder(this, player)
      .setCallback(CustomMediaSessionCallback())
      .setCustomLayout(ImmutableList.of(likeButton, favoriteButton))
      .build()
}

Java

@Override
public void onCreate() {
  super.onCreate();

  CommandButton likeButton = new CommandButton.Builder()
    .setDisplayName("Like")
    .setIconResId(R.drawable.like_icon)
    .setSessionCommand(new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING))
    .build();
  CommandButton favoriteButton = new CommandButton.Builder()
    .setDisplayName("Save to favorites")
    .setIconResId(R.drawable.favorite_icon)
    .setSessionCommand(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
    .build();

  Player player = new ExoPlayer.Builder(this).build();
  mediaSession =
      new MediaSession.Builder(this, player)
          .setCallback(new CustomMediaSessionCallback())
          .setCustomLayout(ImmutableList.of(likeButton, favoriteButton))
          .build();
}

Khai báo trình phát có sẵn và 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 mà thực thể đó có thể sử dụng trong một bố cục tuỳ chỉnh. Ví dụ: có thể bạn 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 đa phương tiện vào danh sách các mục yêu thích. MediaController gửi các lệnh tuỳ chỉnh và MediaSession.Callback sẽ nhận các lệnh đó.

Bạn có thể xác định những lệnh phiên tuỳ chỉnh nào dành cho MediaController khi ứng dụng này kết nối với phiên phát nội dung nghe nhìn của bạn. Bạn có thể thực hiện việc này bằng cách ghi đè MediaSession.Callback.onConnect(). Định cấu hình và trả về tập hợp các lệnh có sẵn khi chấp nhận yêu cầu kết nối từ MediaController trong phương pháp gọi lại onConnect:

Kotlin

private inner class CustomMediaSessionCallback: MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  override fun onConnect(
    session: MediaSession,
    controller: MediaSession.ControllerInfo
  ): MediaSession.ConnectionResult {
    val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY))
        .build()
    return AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build()
  }
}

Java

class CustomMediaSessionCallback implements MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  @Override
  public ConnectionResult onConnect(
    MediaSession session,
    ControllerInfo controller) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
            .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
            .build();
    return new AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
}

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

Kotlin

private inner class CustomMediaSessionCallback: MediaSession.Callback {
  ...
  override fun onCustomCommand(
    session: MediaSession,
    controller: MediaSession.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)
      )
    }
    ...
  }
}

Java

class CustomMediaSessionCallback 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)
      );
    }
    ...
  }
}

Bạn có thể theo dõi trình điều khiển nội dung nghe nhìn nào đang gửi 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. Điều này cho phép bạn đ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.

Cập nhật bố cục tuỳ chỉnh sau khi người dùng tương tác

Sau khi xử lý lệnh tuỳ chỉnh hoặc bất kỳ hoạt động tương tác nào khác với trình phát, bạn nên cập nhật bố cục hiển thị trong giao diện người dùng của tay điều khiển. Ví dụ điển hình là nút bật tắt thay đổi biểu tượng sau khi kích hoạt hành động liên kết với nút này. Để cập nhật bố cục, bạn có thể sử dụng MediaSession.setCustomLayout:

Kotlin

val removeFromFavoritesButton = CommandButton.Builder()
  .setDisplayName("Remove from favorites")
  .setIconResId(R.drawable.favorite_remove_icon)
  .setSessionCommand(SessionCommand(REMOVE_FROM_FAVORITES, Bundle()))
  .build()
mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton))

Java

CommandButton removeFromFavoritesButton = new CommandButton.Builder()
  .setDisplayName("Remove from favorites")
  .setIconResId(R.drawable.favorite_remove_icon)
  .setSessionCommand(new SessionCommand(REMOVE_FROM_FAVORITES, new Bundle()))
  .build();
mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton));

Tuỳ chỉnh hành vi của lệnh phát

Để 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 trong một ForwardingPlayer.

Kotlin

val player = ExoPlayer.Builder(context).build()

val forwardingPlayer = object : ForwardingPlayer(player) {
  override fun play() {
    // Add custom logic
    super.play()
  }

  override fun setPlayWhenReady(playWhenReady: Boolean) {
    // Add custom logic
    super.setPlayWhenReady(playWhenReady)
  }
}

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

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player) {
  @Override
  public void play() {
    // Add custom logic
    super.play();
  }

  @Override
  public void setPlayWhenReady(boolean playWhenReady) {
    // Add custom logic
    super.setPlayWhenReady(playWhenReady);
  }
};

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

Để biết thêm thông tin về ForwardingPlayer, 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 cho một lệnh của người chơi

Khi lệnh gọi đến một phương thức Player được MediaController bắt nguồn, 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

class CallerAwareForwardingPlayer(player: Player) :
  ForwardingPlayer(player) {

  override fun seekToNext() {
    Log.d(
      "caller",
      "seekToNext called from package ${session.controllerForCurrentRequest?.packageName}"
    )
    super.seekToNext()
  }
}

Java

public class CallerAwareForwardingPlayer extends ForwardingPlayer {
  public CallerAwareForwardingPlayer(Player player) {
    super(player);
  }

  @Override
  public void seekToNext() {
    Log.d(
        "caller",
        "seekToNext called from package: "
            + session.getControllerForCurrentRequest().getPackageName());
    super.seekToNext();
  }
}

Phản hồi các nút đa phương tiện

Nút phát nội dung đa phương tiệ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 đa phương tiệ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.

Ứng dụng có thể ghi đè hành vi mặc định bằng cách ghi đè MediaSession.Callback.onMediaButtonEvent(Intent). Trong trường hợp như vậy, ứng dụng có thể/cần tự xử lý tất cả thông tin cụ thể về API.