הגשת תוכן באמצעות MediaLibraryService

אפליקציות מדיה מכילות לרוב אוספים של פריטי מדיה, שמסודרים בהיררכיה. לדוגמה, שירים באלבום או פרקים של תוכנית טלוויזיה בפלייליסט. ההיררכיה הזו של פריטי מדיה נקראת ספריית מדיה.

דוגמאות לתוכן מדיה שמאורגן בהיררכיה
איור 1: דוגמאות להיררכיות של פריטי מדיה שיוצרות ספריית מדיה.

MediaLibraryService מספק API סטנדרטי להצגת ספריית המדיה ולגישה אליה. לדוגמה, זה יכול להיות שימושי כשמוסיפים תמיכה ב-Android Auto לאפליקציית המדיה, שמספקת ממשק משתמש משלה שמאפשר לנהג להשתמש בספריית המדיה בצורה בטוחה.

הרכבת MediaLibraryService

הטמעה של MediaLibraryService דומה להטמעה של MediaSessionService, אבל בשיטה onGetSession() צריך להחזיר MediaLibrarySession במקום MediaSession.

Kotlin

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

  // If desired, validate the controller before returning the media library session
  override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? =
    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 API מצפה שספריית המדיה תהיה מובנית בפורמט של עץ, עם צומת בסיס יחיד וצמתים צאצאים שיכולים להיות ניתנים להפעלה או ניתנים לעיון.

MediaLibrarySession מרחיב את MediaSession API כדי להוסיף ממשקי API לעיון בתוכן. בהשוואה לMediaSession callback, ב-MediaLibrarySession callback יש שיטות נוספות כמו:

  • 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(),
    // possibly more here
  )

// 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
  scope.launch {
    loadMediaItem(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: List<CommandButton> =
  controller.getCommandButtonsForMediaItem(mediaItem)

Java

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

לנוחותכם, MediaController יכול לשלוח פקודות מותאמות אישית ספציפיות לפריט מדיה באמצעות MediaController.sendCustomCommand(SessionCommand, MediaItem, Bundle):

Kotlin

controller.sendCustomCommand(addToPlaylistButton.sessionCommand!!, mediaItem, Bundle.EMPTY)

Java

controller.sendCustomCommand(
    checkNotNull(addToPlaylistButton.sessionCommand), mediaItem, Bundle.EMPTY);