פקדי מדיה

בקרי המדיה ב-Android נמצאים ליד ההגדרות המהירות. הסשנים ממספר אפליקציות מסודרים בקרוסלה שאפשר להחליק. הסשנים מופיעים בקרוסלה בסדר הזה:

  • שידורים חיים שמופעלים באופן מקומי בטלפון
  • שידורים מרחוק, כמו אלה שזוהו במכשירים חיצוניים או בסשנים של העברה (cast)
  • סשנים קודמים שניתן להמשיך מהם, לפי הסדר שבו הם הופעלו בפעם האחרונה

החל מגרסה Android 13 (רמת API ‏33), כדי להבטיח שלמשתמשים תהיה גישה לקבוצה עשירה של פקדי מדיה באפליקציות שמפעילות מדיה, לחצני הפעולה בפקדי המדיה נגזרים מהמצב Player.

כך תוכלו להציג קבוצה עקבית של אמצעי בקרה על מדיה וחוויית בקרה משופרת על המדיה במכשירים השונים.

איור 1 מראה דוגמה לאופן שבו זה נראה בטלפון ובטאבלט, בהתאמה.

פקדי המדיה מבחינת המראה שלהם במכשירי טלפון וטאבלט, באמצעות דוגמה לטראק לדוגמה שבו מוצגים הלחצנים
איור 1: אמצעי הבקרה על המדיה בטלפונים ובטאבלטים

המערכת מציגה עד חמישה לחצני פעולה על סמך המצב של Player, כפי שמתואר בטבלה הבאה. במצב קומפקטי מוצגים רק שלושת חריצי הפעולה הראשונים. האופן הזה תואם לאופן שבו לחצני המדיה מוצגים בפלטפורמות אחרות של Android, כמו Auto, ‏ Assistant ו-Wear OS.

משבצת קריטריונים פעולה
1 הערך של playWhenReady הוא שקר או שמצב ההפעלה הנוכחי הוא STATE_ENDED. Play
הערך של playWhenReady הוא true ומצב ההפעלה הנוכחי הוא STATE_BUFFERING. סימן גרפי של טעינת נתונים
הערך של playWhenReady הוא true ומצב ההפעלה הנוכחי הוא STATE_READY. השהיה
2 הפקודה לנגן COMMAND_SEEK_TO_PREVIOUS או COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM זמינה. הקודם
פקודת השחקן COMMAND_SEEK_TO_PREVIOUS או COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM לא זמינות, ופקודת התאמה אישית מהפריסה בהתאמה אישית שעדיין לא הוצבה זמינה כדי למלא את המקום. בהתאמה אישית
פרטים נוספים על הסשן כוללים ערך בוליאני true למפתח EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV. ריק
3 הפקודה לנגן COMMAND_SEEK_TO_NEXT או COMMAND_SEEK_TO_NEXT_MEDIA_ITEM זמינה. הבא
פקודת השחקן COMMAND_SEEK_TO_NEXT או COMMAND_SEEK_TO_NEXT_MEDIA_ITEM לא זמינות, ופקודת התאמה אישית מהפריסה בהתאמה אישית שעדיין לא הוצבה זמינה כדי למלא את המקום. בהתאמה אישית
פרטים נוספים על הסשן כוללים ערך בוליאני true למפתח EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT. ריק
4 כדי למלא את המקום הזה, אפשר להשתמש בפקודה מותאמת אישית מהפריסה בהתאמה אישית שעדיין לא הוצבה. בהתאמה אישית
5 כדי למלא את המקום הזה, אפשר להשתמש בפקודה מותאמת אישית מהפריסה בהתאמה אישית שעדיין לא הוצבה. בהתאמה אישית

הפקודות בהתאמה אישית ממוקמות לפי הסדר שבו הן נוספו לפריסת ההתאמה האישית.

התאמה אישית של לחצני הפקודות

כדי להתאים אישית את אמצעי הבקרה על המדיה במערכת באמצעות Jetpack Media3, תוכלו להגדיר את הפריסה בהתאמה אישית של הסשן ואת הפקודות הזמינות של אמצעי הבקרה בהתאם, כשמטמיעים MediaSessionService:

  1. ב-onCreate(), יוצרים MediaSession ומגדירים את הפריסה בהתאמה אישית של לחצני הפקודה.

  2. ב-MediaSession.Callback.onConnect(), מגדירים את הפקודות הזמינות של בקרי ה-ConnectionResult, כולל פקודות בהתאמה אישית, כדי להעניק להם הרשאה.

  3. ב-MediaSession.Callback.onCustomCommand(), משיבים לפקודה בהתאמה אישית שהמשתמש בחר.

Kotlin

class PlaybackService : MediaSessionService() {
  private val customCommandFavorites = SessionCommand(ACTION_FAVORITES, Bundle.EMPTY)
  private var mediaSession: MediaSession? = null

  override fun onCreate() {
    super.onCreate()
    val favoriteButton =
      CommandButton.Builder()
        .setDisplayName("Save to favorites")
        .setIconResId(R.drawable.favorite_icon)
        .setSessionCommand(customCommandFavorites)
        .build()
    val player = ExoPlayer.Builder(this).build()
    // Build the session with a custom layout.
    mediaSession =
      MediaSession.Builder(this, player)
        .setCallback(MyCallback())
        .setCustomLayout(ImmutableList.of(favoriteButton))
        .build()
  }

  private inner class MyCallback : MediaSession.Callback {
    override fun onConnect(
      session: MediaSession,
      controller: MediaSession.ControllerInfo
    ): ConnectionResult {
    // Set available player and session commands.
    return AcceptedResultBuilder(session)
      .setAvailablePlayerCommands(
        ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
          .remove(COMMAND_SEEK_TO_NEXT)
          .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
          .remove(COMMAND_SEEK_TO_PREVIOUS)
          .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
          .build()
      )
      .setAvailableSessionCommands(
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
          .add(customCommandFavorites)
          .build()
      )
      .build()
    }

    override fun onCustomCommand(
      session: MediaSession,
      controller: MediaSession.ControllerInfo,
      customCommand: SessionCommand,
      args: Bundle
    ): ListenableFuture {
      if (customCommand.customAction == ACTION_FAVORITES) {
        // Do custom logic here
        saveToFavorites(session.player.currentMediaItem)
        return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
      }
      return super.onCustomCommand(session, controller, customCommand, args)
    }
  }
}

Java

public class PlaybackService extends MediaSessionService {
  private static final SessionCommand CUSTOM_COMMAND_FAVORITES =
      new SessionCommand("ACTION_FAVORITES", Bundle.EMPTY);
  @Nullable private MediaSession mediaSession;

  public void onCreate() {
    super.onCreate();
    CommandButton favoriteButton =
        new CommandButton.Builder()
            .setDisplayName("Save to favorites")
            .setIconResId(R.drawable.favorite_icon)
            .setSessionCommand(CUSTOM_COMMAND_FAVORITES)
            .build();
    Player player = new ExoPlayer.Builder(this).build();
    // Build the session with a custom layout.
    mediaSession =
        new MediaSession.Builder(this, player)
            .setCallback(new MyCallback())
            .setCustomLayout(ImmutableList.of(favoriteButton))
            .build();
  }

  private static class MyCallback implements MediaSession.Callback {
    @Override
    public ConnectionResult onConnect(
        MediaSession session, MediaSession.ControllerInfo controller) {
      // Set available player and session commands.
      return new AcceptedResultBuilder(session)
          .setAvailablePlayerCommands(
              ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
                .remove(COMMAND_SEEK_TO_NEXT)
                .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
                .remove(COMMAND_SEEK_TO_PREVIOUS)
                .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
                .build())
          .setAvailableSessionCommands(
              ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
                .add(CUSTOM_COMMAND_FAVORITES)
                .build())
          .build();
    }

    public ListenableFuture onCustomCommand(
        MediaSession session,
        MediaSession.ControllerInfo controller,
        SessionCommand customCommand,
        Bundle args) {
      if (customCommand.customAction.equals(CUSTOM_COMMAND_FAVORITES.customAction)) {
        // Do custom logic here
        saveToFavorites(session.getPlayer().getCurrentMediaItem());
        return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
      }
      return MediaSession.Callback.super.onCustomCommand(
          session, controller, customCommand, args);
    }
  }
}

מידע נוסף על הגדרת MediaSession כך שלקוחות כמו המערכת יוכלו להתחבר לאפליקציית המדיה מופיע במאמר הענקת שליטה ללקוחות אחרים.

כשמשתמשים ב-Jetpack Media3, כשמטמיעים MediaSession, ה-PlaybackState מתעדכן באופן אוטומטי עם נגן המדיה. באופן דומה, כשמטמיעים MediaSessionService, הספרייה מפרסמת בשבילכם הודעה MediaStyle באופן אוטומטי ומעדכנת אותה.

תגובה ללחצני פעולה

כשמשתמש מקשיב על לחצן פעולה באמצעי הבקרה של המדיה במערכת, ה-MediaController של המערכת שולח פקודה להפעלה ל-MediaSession. לאחר מכן, ה-MediaSession מעביר את הפקודות האלה לנגן. הפקודות שמוגדרות בממשק Player של Media3 מטופלות באופן אוטומטי על ידי סשן המדיה.

במאמר הוספת פקודות בהתאמה אישית מוסבר איך להגיב לפקודה בהתאמה אישית.

התנהגות לפני Android 13

כדי לשמור על תאימות לאחור, ממשק המשתמש של המערכת ממשיך לספק פריסה חלופית שמשתמשת בפעולות התראות לאפליקציות שלא מתעדכנות כדי לטרגט את Android 13, או לאפליקציות שלא כוללות מידע על PlaybackState. לחצני הפעולה מגיעים מהרשימה Notification.Action שמצורפת להתראה MediaStyle. המערכת מציגה עד חמש פעולות בסדר שבו הן נוספו. במצב קומפקטי מוצגים עד שלושה לחצנים, בהתאם לערכים שמועברים אל setShowActionsInCompactView().

הפעולות בהתאמה אישית ממוקמות לפי הסדר שבו הן נוספו ל-PlaybackState.

דוגמת הקוד הבאה ממחישה איך מוסיפים פעולות להתראה בסגנון Media :

Kotlin

import androidx.core.app.NotificationCompat
import androidx.media3.session.MediaStyleNotificationHelper

var notification = NotificationCompat.Builder(context, CHANNEL_ID)
        // Show controls on lock screen even when user hides sensitive content.
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        // Add media control buttons that invoke intents in your media service
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
        // Apply the media style template
        .setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build()

Java

import androidx.core.app.NotificationCompat;
import androidx.media3.session.MediaStyleNotificationHelper;

NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_ID)
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent)
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent)
        .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build();

תמיכה בהמשך הפעלת המדיה

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

אפשר להפעיל או להשבית את התכונה 'המשך ההפעלה' באפליקציית ההגדרות, בקטע צליל > מדיה. המשתמש יכול לגשת להגדרות גם על ידי הקשה על סמל גלגל השיניים שמופיע אחרי החלקה על הקרוסלה המורחבת.

Media3 מציע ממשקי API שבעזרתם קל יותר לתמוך בהמשך הפעלת מדיה. במסמכים בנושא המשך ההפעלה באמצעות Media3 מוסבר איך להטמיע את התכונה הזו.

שימוש בממשקי ה-API הקודמים של מדיה

בקטע הזה מוסבר איך לשלב את הרכיב עם אמצעי הבקרה של המדיה במערכת באמצעות ממשקי ה-API הקודמים של MediaCompat.

המערכת מאחזרת את הפרטים הבאים מ-MediaMetadata של MediaSession ומציגה אותם כשהם זמינים:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (אם משך הזמן לא מוגדר, בסרגל החיפוש לא יוצג התקדמות)

כדי להבטיח שתופיע התראה תקינה ומדויקת על בקרת מדיה, צריך להגדיר את הערך של המטא-נתונים METADATA_KEY_TITLE או METADATA_KEY_DISPLAY_TITLE לשם של המדיה שמושמעת כרגע.

בנגן המדיה מוצג הזמן שחלף מההתחלה של המדיה שמושמעת כרגע, וגם סרגל סריקה שממופה למקש MediaSession PlaybackState.

בנגן המדיה מוצגת ההתקדמות של המדיה שמופעלת כרגע, לצד סרגל סריקה שממופה למקש MediaSession PlaybackState. באמצעות סרגל החיפוש המשתמשים יכולים לשנות את המיקום, והוא מציג את משך הזמן שחלף בפריט המדיה. כדי שסרגל החיפוש יופעל, צריך להטמיע את PlaybackState.Builder#setActions ולכלול את ACTION_SEEK_TO.

משבצת פעולה קריטריונים
1 Play המצב הנוכחי של PlaybackState הוא אחד מהמצבים הבאים:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
סימן גרפי של טעינת נתונים המצב הנוכחי של PlaybackState הוא אחד מהמצבים הבאים:
  • STATE_CONNECTING
  • STATE_BUFFERING
השהיה המצב הנוכחי של PlaybackState הוא לא אף אחת מהאפשרויות האלה.
2 הקודם PlaybackState פעולות כוללות את ACTION_SKIP_TO_PREVIOUS.
בהתאמה אישית PlaybackState פעולות לא כוללות את ACTION_SKIP_TO_PREVIOUS ו-PlaybackState פעולות בהתאמה אישית כוללות פעולה בהתאמה אישית שעדיין לא הוצבה.
ריק PlaybackState extras כוללים ערך בוליאני true למפתח SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV.
3 הבא PlaybackState פעולות כוללות את ACTION_SKIP_TO_NEXT.
בהתאמה אישית PlaybackState פעולות לא כוללות את ACTION_SKIP_TO_NEXT ו-PlaybackState פעולות בהתאמה אישית כוללות פעולה בהתאמה אישית שעדיין לא הוצבה.
ריק PlaybackState extras כוללים ערך בוליאני true למפתח SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT.
4 בהתאמה אישית PlaybackState פעולות בהתאמה אישית כוללות פעולה מותאמת אישית שעדיין לא הוצבה.
5 בהתאמה אישית PlaybackState פעולות בהתאמה אישית כוללות פעולה מותאמת אישית שעדיין לא הוצבה.

הוספת פעולות רגילות

בדוגמאות הקוד הבאות מוסבר איך מוסיפים פעולות PlaybackState רגילות ומותאמות אישית.

כדי להגדיר את הפעולות 'הפעלה', 'השהיה', 'קודם' ו'הבא', צריך להגדיר את הפעולות האלה ב-PlaybackState של סשן המדיה.

Kotlin

val session = MediaSessionCompat(context, TAG)
val playbackStateBuilder = PlaybackStateCompat.Builder()
val style = NotificationCompat.MediaStyle()

// For this example, the media is currently paused:
val state = PlaybackStateCompat.STATE_PAUSED
val position = 0L
val playbackSpeed = 1f
playbackStateBuilder.setState(state, position, playbackSpeed)

// And the user can play, skip to next or previous, and seek
val stateActions = PlaybackStateCompat.ACTION_PLAY
    or PlaybackStateCompat.ACTION_PLAY_PAUSE
    or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    or PlaybackStateCompat.ACTION_SEEK_TO // adding the seek action enables seeking with the seekbar
playbackStateBuilder.setActions(stateActions)

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build())
style.setMediaSession(session.sessionToken)
notificationBuilder.setStyle(style)

Java

MediaSessionCompat session = new MediaSessionCompat(context, TAG);
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle();

// For this example, the media is currently paused:
int state = PlaybackStateCompat.STATE_PAUSED;
long position = 0L;
float playbackSpeed = 1f;
playbackStateBuilder.setState(state, position, playbackSpeed);

// And the user can play, skip to next or previous, and seek
long stateActions = PlaybackStateCompat.ACTION_PLAY
    | PlaybackStateCompat.ACTION_PLAY_PAUSE
    | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    | PlaybackStateCompat.ACTION_SEEK_TO; // adding this enables the seekbar thumb
playbackStateBuilder.setActions(stateActions);

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build());
style.setMediaSession(session.getSessionToken());
notificationBuilder.setStyle(style);

אם לא רוצים להוסיף לחצנים למיקומים 'הקודם' או 'הבא', לא מוסיפים את הערכים ACTION_SKIP_TO_PREVIOUS או ACTION_SKIP_TO_NEXT, ובמקום זאת מוסיפים פריטים נוספים לסשן:

Kotlin

session.setExtras(Bundle().apply {
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
})

Java

Bundle extras = new Bundle();
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true);
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true);
session.setExtras(extras);

הוספת פעולות בהתאמה אישית

כדי להציג פעולות אחרות באמצעי הבקרה של המדיה, אפשר ליצור PlaybackStateCompat.CustomAction ולהוסיף אותו ל-PlaybackState במקום זאת. הפעולות האלה מוצגות לפי הסדר שבו הן נוספו.

Kotlin

val customAction = PlaybackStateCompat.CustomAction.Builder(
    "com.example.MY_CUSTOM_ACTION", // action ID
    "Custom Action", // title - used as content description for the button
    R.drawable.ic_custom_action
).build()

playbackStateBuilder.addCustomAction(customAction)

Java

PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction.Builder(
        "com.example.MY_CUSTOM_ACTION", // action ID
        "Custom Action", // title - used as content description for the button
        R.drawable.ic_custom_action
).build();

playbackStateBuilder.addCustomAction(customAction);

תגובה לפעולות של PlaybackState

כשמשתמש מקשיב על לחצן, SystemUI משתמש ב-MediaController.TransportControls כדי לשלוח פקודה חזרה ל-MediaSession. צריך לרשום קריאה חוזרת (callback) שיכולה להגיב כראוי לאירועים האלה.

Kotlin

val callback = object: MediaSession.Callback() {
    override fun onPlay() {
        // start playback
    }

    override fun onPause() {
        // pause playback
    }

    override fun onSkipToPrevious() {
        // skip to previous
    }

    override fun onSkipToNext() {
        // skip to next
    }

    override fun onSeekTo(pos: Long) {
        // jump to position in track
    }

    override fun onCustomAction(action: String, extras: Bundle?) {
        when (action) {
            CUSTOM_ACTION_1 -> doCustomAction1(extras)
            CUSTOM_ACTION_2 -> doCustomAction2(extras)
            else -> {
                Log.w(TAG, "Unknown custom action $action")
            }
        }
    }

}

session.setCallback(callback)

Java

MediaSession.Callback callback = new MediaSession.Callback() {
    @Override
    public void onPlay() {
        // start playback
    }

    @Override
    public void onPause() {
        // pause playback
    }

    @Override
    public void onSkipToPrevious() {
        // skip to previous
    }

    @Override
    public void onSkipToNext() {
        // skip to next
    }

    @Override
    public void onSeekTo(long pos) {
        // jump to position in track
    }

    @Override
    public void onCustomAction(String action, Bundle extras) {
        if (action.equals(CUSTOM_ACTION_1)) {
            doCustomAction1(extras);
        } else if (action.equals(CUSTOM_ACTION_2)) {
            doCustomAction2(extras);
        } else {
            Log.w(TAG, "Unknown custom action " + action);
        }
    }
};

המשך הפעלה של המדיה

כדי שאפליקציית הנגן תופיע באזור ההגדרות המהירות, צריך ליצור התראה מסוג MediaStyle עם אסימון MediaSession תקף.

כדי להציג את השם של ההתראה בסגנון MediaStyle, משתמשים ב-NotificationBuilder.setContentTitle().

כדי להציג את סמל המותג של נגן המדיה, משתמשים ב-NotificationBuilder.setSmallIcon().

כדי לתמוך בהמשך ההפעלה, האפליקציות צריכות להטמיע MediaBrowserService ו-MediaSession. ב-MediaSession צריך להטמיע את פונקציית ה-callback onPlay().

הטמעה של MediaBrowserService

אחרי שהמכשיר מופעל, המערכת מחפשת את חמש אפליקציות המדיה שבהן השתמשתם לאחרונה ומספקת פקדים שאפשר להשתמש בהם כדי להפעיל מחדש את ההפעלה מכל אפליקציה.

המערכת מנסה ליצור קשר עם MediaBrowserService באמצעות חיבור מ-SystemUI. האפליקציה צריכה לאפשר חיבורים כאלה, אחרת לא תהיה לה תמיכה בהמשך ההפעלה.

אפשר לזהות ולאמת חיבורים מ-SystemUI באמצעות שם החבילה com.android.systemui והחתימה. SystemUI חתום על ידי חתימה של הפלטפורמה. דוגמה לאופן שבו בודקים את החתימה של הפלטפורמה מופיעה באפליקציית UAMP.

כדי לתמוך בהמשך ההפעלה, MediaBrowserService צריך להטמיע את ההתנהגויות הבאות:

  • onGetRoot() חייבת להחזיר שורש שאינו null במהירות. לוגיקה מורכבת אחרת צריך לטפל ב-onLoadChildren()

  • כשמפעילים את onLoadChildren() במזהה המדיה ברמה הבסיסית, התוצאה צריכה להכיל צאצא מסוג FLAG_PLAYABLE.

  • כשמקבלים שאילתה מסוג EXTRA_RECENT, הפונקציה MediaBrowserService אמורה להחזיר את פריט המדיה שהושמעו לאחרונה. הערך המוחזר צריך להיות פריט מדיה בפועל ולא פונקציה גנרית.

  • MediaBrowserService חייב לספק MediaDescription מתאים עם שם וכותרת משנה שאינם ריקים. צריך גם להגדיר URI של סמל או קובץ bitmap של סמל.

בדוגמאות הקוד הבאות מוסבר איך מטמיעים את onGetRoot().

Kotlin

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your 
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        rootHints?.let {
            if (it.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                val extras = Bundle().apply {
                    putBoolean(BrowserRoot.EXTRA_RECENT, true)
                }
                return BrowserRoot(MY_RECENTS_ROOT_ID, extras)
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return BrowserRoot(MY_MEDIA_ROOT_ID, null)
    }
    // Return an empty tree to disallow browsing.
    return BrowserRoot(MY_EMPTY_ROOT_ID, null)

Java

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        if (rootHints != null) {
            if (rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                Bundle extras = new Bundle();
                extras.putBoolean(BrowserRoot.EXTRA_RECENT, true);
                return new BrowserRoot(MY_RECENTS_ROOT_ID, extras);
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    }
    // Return an empty tree to disallow browsing.
    return new BrowserRoot(MY_EMPTY_ROOT_ID, null);
}