Phân phát nội dung bằng MediaLibraryService

Ứng dụng đa phương tiện thường chứa các bộ sưu tập mục nội dung nghe nhìn, được sắp xếp theo hệ phân cấp. Ví dụ: các bài hát trong một đĩa nhạc hoặc các tập phim truyền hình trong một danh sách phát. Hệ phân cấp này của mục nội dung nghe nhìn được gọi là thư viện nội dung nghe nhìn.

Ví dụ về nội dung nghe nhìn được sắp xếp theo hệ phân cấp
Hình 1: Ví dụ về hệ phân cấp mục nội dung nghe nhìn tạo thành một thư viện nội dung nghe nhìn.

MediaLibraryService cung cấp một API được chuẩn hoá để phân phát và truy cập vào thư viện nội dung nghe nhìn của bạn. Điều này có thể hữu ích, chẳng hạn như khi thêm tính năng hỗ trợ cho Android Auto vào ứng dụng đa phương tiện, cung cấp giao diện người dùng an toàn cho người lái xe cho thư viện nội dung nghe nhìn của bạn.

Tạo MediaLibraryService

Việc triển khai MediaLibraryService tương tự như triển khai MediaSessionService, ngoại trừ việc trong phương thức onGetSession(), bạn nên trả về MediaLibrarySession thay vì MediaSession.

Kotlin

class PlaybackService : MediaLibraryService() {
  private var mediaLibrarySession: MediaLibrarySession? = null
  private val callback: MediaLibrarySession.Callback =
    object : MediaLibrarySession.Callback {
      /* ... */
    }

  override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? {
    // If desired, validate the controller before returning the media library session
    return mediaLibrarySession
  }

  // Create your player and media library session in the onCreate lifecycle event
  override fun onCreate() {
    super.onCreate()
    val player = ExoPlayer.Builder(this).build()
    mediaLibrarySession = MediaLibrarySession.Builder(this, player, callback).build()
  }

  // Remember to release the player and media library session in onDestroy
  override fun onDestroy() {
    mediaLibrarySession?.run {
      player.release()
      release()
      mediaLibrarySession = null
    }
    super.onDestroy()
  }
}

Java

class PlaybackService extends MediaLibraryService {
  MediaLibrarySession mediaLibrarySession = null;
  MediaLibrarySession.Callback callback = new MediaLibrarySession.Callback() {
        /* ... */
      };

  @Override
  public MediaLibrarySession onGetSession(MediaSession.ControllerInfo controllerInfo) {
    // If desired, validate the controller before returning the media library session
    return mediaLibrarySession;
  }

  // Create your player and media library session in the onCreate lifecycle event
  @Override
  public void onCreate() {
    super.onCreate();
    ExoPlayer player = new ExoPlayer.Builder(this).build();
    mediaLibrarySession = new MediaLibrarySession.Builder(this, player, callback).build();
  }

  // Remember to release the player and media library session in onDestroy
  @Override
  public void onDestroy() {
    if (mediaLibrarySession != null) {
      mediaLibrarySession.getPlayer().release();
      mediaLibrarySession.release();
      mediaLibrarySession = null;
    }
    super.onDestroy();
  }
}
Đừng quên khai báo Service và các quyền cần thiết trong tệp kê khai:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

Bạn nên đăng ký dịch vụ của mình dưới dạng cả nền tảng và giao diện dịch vụ Media3:

  • Để tương thích với các ứng dụng sử dụng API phiên phát nội dung nghe nhìn của nền tảng, bạn nên đưa <action android:name="android.media.browse.MediaBrowserService"/> vào phần tử intent-filter. Media3 tự động cung cấp khả năng tương thích ngược với giao diện dịch vụ này.

  • Để đảm bảo khả năng tương thích trong tương lai và đảm bảo các ứng dụng sử dụng Media3 có thể giao tiếp với dịch vụ của bạn bằng API Media3, bạn nên cung cấp <action android:name="androidx.media3.session.MediaLibraryService"/> cùng với tuỳ chọn nền tảng.

Việc này cho phép các ứng dụng tìm thấy dịch vụ của bạn thông qua PackageManager và kết nối với MediaBrowser thông qua một trong hai giao diện này.

Sử dụng MediaLibrarySession

API MediaLibraryService yêu cầu thư viện nội dung nghe nhìn của bạn phải được cấu trúc theo định dạng cây, với một nút gốc và các nút con có thể phát được hoặc duyệt xem được.

MediaLibrarySession mở rộng API MediaSession để thêm API duyệt nội dung. So với MediaSession callback, MediaLibrarySession callback thêm các phương thức như:

  • onGetLibraryRoot() khi ứng dụng yêu cầu MediaItem gốc của cây nội dung
  • onGetChildren() khi ứng dụng yêu cầu các phần tử con của một MediaItem trong cây nội dung
  • onGetSearchResult() khi ứng dụng yêu cầu kết quả tìm kiếm từ cây nội dung cho một truy vấn nhất định

Các phương thức gọi lại có liên quan sẽ bao gồm một đối tượng LibraryParams có các tín hiệu bổ sung về loại cây nội dung mà ứng dụng khách quan tâm.

Nút lệnh cho mục nội dung nghe nhìn

Ứng dụng phiên có thể khai báo các nút lệnh được MediaItem hỗ trợ trong MediaMetadata. Điều này cho phép chỉ định một hoặc nhiều mục CommandButton cho một mục nội dung nghe nhìn mà trình điều khiển có thể hiển thị và sử dụng để gửi lệnh tuỳ chỉnh cho mục đó đến phiên theo cách thuận tiện.

Thiết lập các nút lệnh ở phía phiên

Khi tạo phiên, ứng dụng phiên sẽ khai báo tập hợp các nút lệnh mà phiên có thể xử lý dưới dạng lệnh tuỳ chỉnh:

Kotlin

val allCommandButtons =
  listOf(
    CommandButton.Builder(CommandButton.ICON_PLAYLIST_ADD)
      .setDisplayName(context.getString(R.string.add_to_playlist))
      .setSessionCommand(SessionCommand(COMMAND_PLAYLIST_ADD, Bundle.EMPTY))
      .setExtras(playlistAddExtras)
      .build(),
    CommandButton.Builder(CommandButton.ICON_RADIO)
      .setDisplayName(context.getString(R.string.radio_station))
      .setSessionCommand(SessionCommand(COMMAND_RADIO, Bundle.EMPTY))
      .setExtras(radioExtras)
      .build(),
  )
// Add all command buttons for media items supported by the session.
val session =
  MediaSession.Builder(context, player)
    .setCommandButtonsForMediaItems(allCommandButtons)
    .build()

Java

ImmutableList<CommandButton> allCommandButtons =
    ImmutableList.of(
        new CommandButton.Builder(CommandButton.ICON_PLAYLIST_ADD)
            .setDisplayName(context.getString(R.string.add_to_playlist))
            .setSessionCommand(new SessionCommand(COMMAND_PLAYLIST_ADD, Bundle.EMPTY))
            .setExtras(playlistAddExtras)
            .build(),
        new CommandButton.Builder(CommandButton.ICON_RADIO)
            .setDisplayName(context.getString(R.string.radio_station))
            .setSessionCommand(new SessionCommand(COMMAND_RADIO, Bundle.EMPTY))
            .setExtras(radioExtras)
            .build());
// Add all command buttons for media items supported by the session.
MediaSession session =
    new MediaSession.Builder(context, player)
        .setCommandButtonsForMediaItems(allCommandButtons)
        .build();

Khi tạo một mục nội dung nghe nhìn, ứng dụng phiên có thể thêm một tập hợp mã lệnh được hỗ trợ tham chiếu đến các lệnh phiên của nút lệnh đã được thiết lập khi tạo phiên:

Kotlin

val mediaItem =
  MediaItem.Builder()
    .setMediaMetadata(
      MediaMetadata.Builder()
        .setSupportedCommands(listOf(COMMAND_PLAYLIST_ADD, COMMAND_RADIO))
        .build()
    )
    .build()

Java

MediaItem mediaItem =
    new MediaItem.Builder()
        .setMediaMetadata(
            new MediaMetadata.Builder()
                .setSupportedCommands(ImmutableList.of(COMMAND_PLAYLIST_ADD, COMMAND_RADIO))
                .build())
        .build();

Khi một bộ điều khiển hoặc trình duyệt kết nối hoặc gọi một phương thức khác của phiên Callback, ứng dụng phiên có thể kiểm tra ControllerInfo được truyền đến lệnh gọi lại để nhận số lượng nút lệnh tối đa mà bộ điều khiển hoặc trình duyệt có thể hiển thị. ControllerInfo được truyền vào phương thức gọi lại cung cấp một phương thức getter để truy cập vào giá trị này một cách thuận tiện. Theo mặc định, giá trị được đặt thành 0, cho biết trình duyệt hoặc trình điều khiển không hỗ trợ tính năng này:

Kotlin

override fun onGetItem(
  session: MediaLibrarySession,
  browser: MediaSession.ControllerInfo,
  mediaId: String,
): ListenableFuture<LibraryResult<MediaItem>> {
  val settableFuture = SettableFuture.create<LibraryResult<MediaItem>>()

  val maxCommandsForMediaItems = browser.maxCommandsForMediaItems
  loadMediaItemAsync(settableFuture, mediaId, maxCommandsForMediaItems)

  return settableFuture
}

Java

@Override
public ListenableFuture<LibraryResult<MediaItem>> onGetItem(
    MediaLibraryService.MediaLibrarySession session,
    ControllerInfo browser,
    String mediaId) {
  SettableFuture<LibraryResult<MediaItem>> settableFuture = SettableFuture.create();

  int maxCommandsForMediaItems = browser.getMaxCommandsForMediaItems();
  loadMediaItemAsync(settableFuture, mediaId, maxCommandsForMediaItems);

  return settableFuture;
}

Khi xử lý một thao tác tuỳ chỉnh đã được gửi cho một mục nội dung nghe nhìn, ứng dụng phiên có thể lấy mã mục nội dung nghe nhìn từ các đối số Bundle được truyền vào onCustomCommand:

Kotlin

override fun onCustomCommand(
  session: MediaSession,
  controller: MediaSession.ControllerInfo,
  customCommand: SessionCommand,
  args: Bundle,
): ListenableFuture<SessionResult> {
  val mediaItemId = args.getString(MediaConstants.EXTRA_KEY_MEDIA_ID)
  return if (mediaItemId != null)
    handleCustomCommandForMediaItem(controller, customCommand, mediaItemId, args)
  else handleCustomCommand(controller, customCommand, args)
}

Java

@Override
public ListenableFuture<SessionResult> onCustomCommand(
    MediaSession session,
    ControllerInfo controller,
    SessionCommand customCommand,
    Bundle args) {
  String mediaItemId = args.getString(MediaConstants.EXTRA_KEY_MEDIA_ID);
  return mediaItemId != null
      ? handleCustomCommandForMediaItem(controller, customCommand, mediaItemId, args)
      : handleCustomCommand(controller, customCommand, args);
}

Sử dụng các nút lệnh làm trình duyệt hoặc bộ điều khiển

Về phía MediaController, ứng dụng có thể khai báo số lượng nút lệnh tối đa mà ứng dụng hỗ trợ cho một mục nội dung nghe nhìn khi tạo MediaController hoặc MediaBrowser:

Kotlin

val browserFuture =
  MediaBrowser.Builder(context, sessionToken).setMaxCommandsForMediaItems(3).buildAsync()

Java

ListenableFuture<MediaBrowser> browserFuture =
    new MediaBrowser.Builder(context, sessionToken).setMaxCommandsForMediaItems(3).buildAsync();

Khi kết nối với phiên, ứng dụng trình điều khiển có thể nhận các nút lệnh mà mục nội dung nghe nhìn hỗ trợ và trình điều khiển có lệnh được ứng dụng phiên cấp:

Kotlin

val commandButtonsForMediaItem = controller.getCommandButtonsForMediaItem(mediaItem)

Java

ImmutableList<CommandButton> commandButtonsForMediaItem =
    controller.getCommandButtonsForMediaItem(mediaItem);

Kotlin

val future =
  controller.sendCustomCommand(
    requireNotNull(addToPlaylistButton.sessionCommand),
    mediaItem,
    Bundle.EMPTY,
  )

Java

ListenableFuture<SessionResult> future =
    controller.sendCustomCommand(
        checkNotNull(addToPlaylistButton.sessionCommand), mediaItem, Bundle.EMPTY);