عرض المحتوى باستخدام MediaLibraryService

غالبًا ما تحتوي تطبيقات الوسائط على مجموعات من ملفات الوسائط منظَّمة في تسلسل هرمي. على سبيل المثال، الأغاني في ألبوم أو حلقات تلفزيونية في قائمة تشغيل. يُعرف هذا التسلسل الهرمي لملفات الوسائط باسم مكتبة الوسائط.

أمثلة على محتوى الوسائط المرتبة في تسلسل هرمي
الشكل 1: أمثلة على التسلسلات الهرمية لملفات الوسائط التي تشكّل مكتبة وسائط.

توفّر MediaLibraryService واجهة برمجة تطبيقات موحّدة لعرض مكتبة الوسائط والوصول إليها. يمكن أن يكون ذلك مفيدًا، على سبيل المثال، عند إضافة دعم لـ Android Auto إلى تطبيق الوسائط، الذي يوفّر واجهة مستخدِم آمنة للسائق لـ مكتبة الوسائط.

إنشاء 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 والأذونات المطلوبة في ملف البيان أيضًا:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
        <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" />

استخدام MediaLibrarySession

تتوقّع واجهة برمجة التطبيقات MediaLibraryService أن تكون مكتبة الوسائط منظَّمة بتنسيق شجري، مع عقدة جذر واحدة وعُقد فرعية يمكن تشغيلها أو تصفّحها بشكل أكبر.

توسّع MediaLibrarySession واجهة برمجة التطبيقات MediaSession لإضافة واجهات برمجة تطبيقات لتصفّح المحتوى. مقارنةً بطلب معاودة الاتصال MediaSession، يضيف طلب معاودة الاتصال MediaLibrarySession طرقًا مثل:

  • onGetLibraryRoot() عندما يطلب عميل العُقدة الجذر MediaItem لشجرة محتوى
  • onGetChildren() عندما يطلب عميل العُقد الفرعية لـ MediaItem في شجرة المحتوى
  • onGetSearchResult() عندما يطلب عميل نتائج البحث من شجرة المحتوى لاستعلام معيّن

ستتضمّن طرق معاودة الاتصال ذات الصلة كائن LibraryParams يتضمّن إشارات إضافية حول نوع شجرة المحتوى التي يهتم بها تطبيق العميل.

أزرار الأوامر لملفات الوسائط

يمكن لتطبيق الجلسة تعريف أزرار الأوامر التي يتيحها MediaItem في MediaMetadata. يتيح ذلك تعيين إدخال واحد أو أكثر من CommandButton لملف وسائط يمكن لوحدة التحكّم عرضه واستخدامه لإرسال الأمر المخصّص للملف إلى الجلسة بطريقة ملائمة.

إعداد أزرار الأوامر على جانب الجلسة

عند إنشاء الجلسة، يعرِّف تطبيق الجلسة مجموعة أزرار الأوامر التي يمكن للجلسة التعامل معها كأوامر مخصّصة:

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 الذي تم تمريره إلى طريقة رد الاتصال أداة إرجاع القيمة للوصول إلى هذه القيمة بسهولة. يتم ضبط القيمة تلقائيًا على 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);