הגשת תוכן באמצעות 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"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- For targetSdk 34+ -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

שימוש ב-MediaLibrarySession

ממשק ה-API של MediaLibraryService מצפה שספריית המדיה תהיה מובנית בפורמט עץ, עם צומת root יחיד וצמתים צאצאים שאפשר להפעיל או לעיין בהם.

MediaLibrarySession הוא תוסף ל-MediaSession API שמאפשר להוסיף ממשקי API לגלישה בתוכן. בהשוואה ל-callback של MediaSession, ל-callback של 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))
      .setDisplayName("Add to playlist")
      .setIconResId(R.drawable.playlist_add)
      .setSessionCommand(SessionCommand(COMMAND_PLAYLIST_ADD, Bundle.EMPTY))
      .setExtras(playlistAddExtras)
      .build(),
    CommandButton.Builder(CommandButton.ICON_RADIO)
      .setDisplayName(context.getString(R.string.radio_station))
      .setIconResId(R.drawable.radio)
      .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("Add to playlist")
            .setIconUri(Uri.parse("http://www.example.com/icon/playlist_add"))
            .setSessionCommand(new SessionCommand(COMMAND_PLAYLIST_ADD, Bundle.EMPTY))
            .setExtras(playlistAddExtras)
            .build(),
        new CommandButton.Builder(CommandButton.ICON_RADIO)
            .setDisplayName("Radio station")
            .setIconUri(Uri.parse("http://www.example.com/icon/radio"))
            .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 שמוענק ל-callback כדי לקבל את המספר המקסימלי של לחצני הפקודה שאפשר להציג בבקר או בדפדפן. הערך 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);