แสดงเนื้อหาด้วย MediaLibraryService

แอปสื่อมักมีคอลเล็กชันของรายการสื่อที่จัดระเบียบเป็นลำดับชั้น เช่น เพลงในอัลบั้มหรือตอนของรายการทีวีในเพลย์ลิสต์ ลำดับชั้นของ รายการสื่อนี้เรียกว่าคลังสื่อ

ตัวอย่างเนื้อหาสื่อที่จัดเรียงตามลำดับชั้น
รูปที่ 1: ตัวอย่างลำดับชั้นของรายการสื่อที่สร้างคลังสื่อ

MediaLibraryService มี API ที่ได้มาตรฐานสำหรับแสดงและเข้าถึงคลังสื่อ ซึ่งอาจเป็นประโยชน์ในกรณีต่างๆ เช่น เมื่อเพิ่มการรองรับ Android Auto ลงในแอปสื่อ ซึ่งมี UI ที่ปลอดภัยสำหรับผู้ขับขี่ของคลังสื่อ

สร้าง MediaLibraryService

การติดตั้งใช้งาน MediaLibraryService คล้ายกับการติดตั้งใช้งาน MediaSessionService ยกเว้นว่าในเมธอด onGetSession() คุณ ควรส่งคืน MediaLibrarySession แทน 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();
  }
}
อย่าลืมประกาศ Service และสิทธิ์ที่จำเป็นในไฟล์ Manifest ด้วย

<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" />

เราขอแนะนำให้ลงทะเบียนบริการเป็นทั้งแพลตฟอร์มและอินเทอร์เฟซบริการ Media3 ดังนี้

  • เพื่อความเข้ากันได้กับไคลเอ็นต์ที่ใช้ API เซสชันสื่อของแพลตฟอร์ม เราขอแนะนำให้ใส่ <action android:name="android.media.browse.MediaBrowserService"/> ในองค์ประกอบ intent-filter Media3 จะให้การรองรับการทำงานย้อนหลังกับอินเทอร์เฟซบริการนี้โดยอัตโนมัติ

  • เพื่อเป็นการตั้งค่าที่พร้อมรับอนาคตและเพื่อให้มั่นใจว่าแอปที่ใช้ Media3 จะสื่อสารกับบริการของคุณได้โดยใช้ Media3 API คุณควรระบุ <action android:name="androidx.media3.session.MediaLibraryService"/> ควบคู่ไปกับตัวเลือกแพลตฟอร์ม

ซึ่งจะช่วยให้แอปค้นหาบริการของคุณผ่าน PackageManager และเชื่อมต่อกับ MediaBrowser ผ่านอินเทอร์เฟซใดอินเทอร์เฟซหนึ่งได้

ใช้ MediaLibrarySession

MediaLibraryService API คาดหวังให้คุณจัดโครงสร้างคลังสื่อในรูปแบบโครงสร้างต้นไม้ โดยมีโหนดรูทเดียวและโหนดลูกที่อาจเล่นได้หรือเรียกดูเพิ่มเติม

MediaLibrarySession ขยาย MediaSession API เพื่อเพิ่ม API การเรียกดูเนื้อหา เมื่อเทียบกับ MediaSession Callback แล้ว MediaLibrarySession Callback จะเพิ่มเมธอดต่างๆ เช่น

  • onGetLibraryRoot() เมื่อไคลเอ็นต์ขอรูท MediaItem ของ โครงสร้างเนื้อหา
  • onGetChildren() เมื่อไคลเอ็นต์ขอรายการย่อยของ MediaItem ในโครงสร้างเนื้อหา
  • onGetSearchResult() เมื่อไคลเอ็นต์ขอผลการค้นหาจาก โครงสร้างเนื้อหาสำหรับคำค้นหาที่กำหนด

เมธอดเรียกกลับที่เกี่ยวข้องจะมีออบเจ็กต์ LibraryParams พร้อมสัญญาณเพิ่มเติมเกี่ยวกับประเภทของโครงสร้างเนื้อหาที่แอปไคลเอ็นต์สนใจ

ปุ่มคำสั่งสำหรับรายการสื่อ

แอปเซสชันสามารถประกาศปุ่มคำสั่งที่ MediaItem ในMediaMetadata รองรับได้ ซึ่งจะช่วยให้กำหนดรายการ CommandButton อย่างน้อย 1 รายการ ให้กับรายการสื่อที่ตัวควบคุมแสดงและใช้เพื่อส่งคำสั่งที่กำหนดเอง สำหรับรายการไปยังเซสชันได้อย่างสะดวก

ตั้งค่าปุ่มคำสั่งในฝั่งเซสชัน

เมื่อสร้างเซสชัน แอปเซสชันจะประกาศชุดปุ่มคำสั่ง ที่เซสชันสามารถจัดการเป็นคำสั่งที่กำหนดเองได้ ดังนี้

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

เมื่อสร้างรายการสื่อ แอปเซสชันจะเพิ่มชุดรหัสคำสั่งที่รองรับได้ ซึ่งอ้างอิงถึงคำสั่งเซสชันของปุ่มคำสั่งที่ตั้งค่าไว้เมื่อ สร้างเซสชัน

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

เมื่อตัวควบคุมหรือเบราว์เซอร์เชื่อมต่อหรือเรียกใช้เมธอดอื่นของเซสชัน Callback แอปเซสชันจะตรวจสอบ ControllerInfo ที่ส่งไปยัง การเรียกกลับเพื่อรับจำนวนปุ่มคำสั่งสูงสุดที่ตัวควบคุมหรือเบราว์เซอร์ แสดงได้ ControllerInfo ที่ส่งไปยังเมธอด Callback มี Getter เพื่อเข้าถึงค่านี้ได้อย่างสะดวก โดยค่าเริ่มต้น ระบบจะตั้งค่าเป็น 0 ซึ่ง บ่งชี้ว่าเบราว์เซอร์หรือตัวควบคุมไม่รองรับฟีเจอร์นี้

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

เมื่อจัดการการดำเนินการที่กำหนดเองซึ่งส่งมาสำหรับรายการสื่อ เซสชัน แอปจะรับรหัสรายการสื่อจากอาร์กิวเมนต์ Bundle ที่ส่งไปยัง 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);
}

ใช้ปุ่มคำสั่งเป็นเบราว์เซอร์หรือตัวควบคุม

ในฝั่ง MediaController แอปสามารถประกาศจำนวนปุ่มคำสั่งสูงสุดที่รองรับสำหรับรายการสื่อเมื่อสร้าง MediaController หรือ MediaBrowser ได้ดังนี้

Kotlin

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

Java

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

เมื่อเชื่อมต่อกับเซสชันแล้ว แอปตัวควบคุมจะรับปุ่มคำสั่ง ที่รายการสื่อรองรับและตัวควบคุมมี คำสั่งที่พร้อมใช้งานซึ่งแอปเซสชันให้สิทธิ์ได้

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