שליטה בהפעלה ופרסום באמצעות MediaSession

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

הפקודות יכולות להגיע מלחצנים פיזיים, כמו לחצן ההפעלה באוזניות או בשלט רחוק של טלוויזיה. יכול להיות שהפקודות יגיעו גם מאפליקציות לקוח שיש להן אמצעי בקרה למדיה, כמו פקודת 'השהיה' ל-Google Assistant. הפקודות האלה מועברות מה-mediaSession אל הנגן של אפליקציית המדיה.

מתי כדאי לבחור סשן מדיה

כשמטמיעים את MediaSession, מאפשרים למשתמשים לשלוט בהפעלה:

  • באמצעות האוזניות שלהם. לרוב יש באוזניות לחצנים או אפשרויות מגע שמאפשרות למשתמש להפעיל או להשהות את המדיה, או לעבור לטראק הבא או הקודם.
  • לדבר אל Google Assistant. דוגמה נפוצה היא לומר "Ok Google, pause" כדי להשהות מדיה שמופעלת כרגע במכשיר.
  • באמצעות שעון Wear OS. כך קל יותר לגשת לאמצעי הבקרה הנפוצים ביותר להפעלה בזמן שמשחקים בטלפון.
  • דרך אמצעי הבקרה למדיה. בקרוסלה הזו מוצגים אמצעי בקרה לכל סשן מדיה שפועל.
  • בטלוויזיה. מאפשר פעולות באמצעות לחצני הפעלה פיזיים, שליטה בהפעלה בפלטפורמה וניהול צריכת חשמל (לדוגמה, אם הטלוויזיה, מקרן הקול או הרסיבר של האודיו והווידאו נכבים או שהקלט משתנה, ההפעלה צריכה להיפסק באפליקציה).
  • דרך כפתורי המדיה של Android Auto. כך אפשר לשלוט בהפעלה בצורה בטוחה בזמן הנהיגה.
  • וכל תהליך חיצוני אחר שצריך להשפיע על ההפעלה.

זה מצוין להרבה תרחישי שימוש. במיוחד, מומלץ מאוד להשתמש ב-MediaSession במקרים הבאים:

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

עם זאת, לא כל תרחישי השימוש מתאימים ל-MediaSession. כדאי להשתמש רק ב-Player במקרים הבאים:

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

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

יצירת סשן מדיה

סשן מדיה מתקיים לצד הנגן שהוא מנהל. אפשר ליצור סשן מדיה עם אובייקט Context ואובייקט Player. צריך ליצור ולהפעיל סשן מדיה כשצריך, למשל בשיטת מחזור החיים onStart() או onResume() של Activity או Fragment, או בשיטה onCreate() של Service שבבעלותה סשן המדיה והנגן המשויך.

כדי ליצור סשן מדיה, מאתחלים Player ומספקים אותו ל-MediaSession.Builder כך:

Kotlin

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

טיפול אוטומטי במצב

ספריית Media3 מעדכנת אוטומטית את סשן המדיה באמצעות מצב הנגן. לכן לא צריך לטפל באופן ידני במיפוי מהנגן לסשן.

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

מזהה סשן ייחודי

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

אם אפליקציה רוצה לנהל כמה מופעים של סשנים בו-זמנית, היא צריכה לוודא שמזהה הסשן של כל סשן הוא ייחודי. אפשר להגדיר את מזהה הסשן כשיוצרים את הסשן באמצעות MediaSession.Builder.setId(String id).

אם אתם רואים ש-IllegalStateException גורם לקריסת האפליקציה עם הודעת השגיאה IllegalStateException: Session ID must be unique. ID=, סביר להניח שהופעלה סשן באופן לא צפוי לפני שחרור מופע שנוצר קודם עם אותו מזהה. כדי למנוע דליפה של סשנים בגלל שגיאת תכנות, המערכת מזהה מקרים כאלה ושולחת התראה על ידי הפעלת חריגה.

מתן שליטה ללקוחות אחרים

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

תרשים שממחיש את האינטראקציה בין MediaSession לבין MediaController.
תמונה 1: בקר המדיה מאפשר להעביר פקודות ממקורות חיצוניים לסשן המדיה.
.

כשבקר עומד להתחבר להפעלת המדיה, מתבצעת קריאה לשיטה onConnect(). אפשר להשתמש בControllerInfo שמופיע כדי להחליט אם לאשר או לדחות את הבקשה. דוגמה לאישור בקשת חיבור מופיעה בקטע הצהרה על פקודות מותאמות אישית.

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

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

שינוי הפלייליסט

במהלך הפעלת מדיה אפשר לשנות ישירות את הפלייליסט של הנגן, כמו שמוסבר במדריך ExoPlayer לפלייליסטים. לבעלי הרשאת ניהול יש גם אפשרות לשנות את הפלייליסט אם ההרשאות COMMAND_SET_MEDIA_ITEM או COMMAND_CHANGE_MEDIA_ITEMS זמינות להם.

כשמוסיפים פריטים חדשים לפלייליסט, בדרך כלל נדרשים MediaItem מופעים עם URI מוגדר כדי שהפריטים יוכלו לפעול. כברירת מחדל, פריטים חדשים שנוספים מועברים אוטומטית לשיטות של נגן כמו player.addMediaItem אם מוגדר להם URI.

אם רוצים להתאים אישית את המופעים של MediaItem שנוספו לנגן, אפשר לבטל את ההגדרה של onAddMediaItems(). השלב הזה נדרש כשרוצים לתמוך בבקשות של בקרים למדיה ללא URI מוגדר. במקום זאת, בדרך כלל MediaItem כולל אחד או יותר מהשדות הבאים שמוגדרים לתיאור המדיה המבוקשת:

  • MediaItem.id: מזהה כללי שמציין את המדיה.
  • MediaItem.RequestMetadata.mediaUri: ‏URI של בקשה שעשוי להשתמש בסכימה מותאמת אישית, ולא בהכרח ניתן להפעלה ישירה על ידי הנגן.
  • MediaItem.RequestMetadata.searchQuery: שאילתת חיפוש טקסטואלית, למשל מ-Google Assistant.
  • MediaItem.MediaMetadata: מטא-נתונים מובְנים כמו 'שם' או 'אומן'.

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

ניהול ההעדפות של כפתורי המדיה

כל בקר, למשל System UI,‏ Android Auto או Wear OS, יכול לקבל החלטות משלו לגבי הלחצנים שיוצגו למשתמש. כדי לציין אילו אמצעי בקרה להפעלה רוצים להציג למשתמש, אפשר להגדיר העדפות של לחצני מדיה ב-MediaSession. ההעדפות האלה כוללות רשימה מסודרת של מופעי CommandButton, שכל אחד מהם מגדיר העדפה לכפתור בממשק המשתמש.

הגדרת כפתורי הפקודות

מופעי CommandButton משמשים להגדרת ההעדפות של כפתורי המדיה. כל לחצן מגדיר שלושה היבטים של רכיב ממשק המשתמש הרצוי:

  1. הסמל, שמגדיר את המראה החזותי. כשיוצרים CommandButton.Builder, צריך להגדיר את הסמל לאחת מהקבועים המוגדרים מראש. חשוב לשים לב שזה לא משאב תמונה או Bitmap בפועל. קבוע גנרי עוזר לבקרי משאבים לבחור משאב מתאים כדי ליצור מראה ותחושה עקביים בממשק המשתמש שלהם. אם אף אחת מהקבועים של הסמלים המוגדרים מראש לא מתאימה לתרחיש השימוש שלכם, אתם יכולים להשתמש ב-setCustomIconResId.
  2. הפקודה, שמגדירה את הפעולה שמופעלת כשהמשתמש מבצע אינטראקציה עם הלחצן. אפשר להשתמש ב-setPlayerCommand בשביל Player.Command, או ב-setSessionCommand בשביל SessionCommand מוגדר מראש או בהתאמה אישית.
  3. העמודה Slot, שבה מגדירים את המיקום של הכפתור בממשק המשתמש של בקר המשחק. השדה הזה הוא אופציונלי והוא מוגדר באופן אוטומטי על סמך הסמל והפקודה. לדוגמה, אפשר לציין שכפתור יוצג באזור הניווט 'קדימה' בממשק המשתמש במקום באזור ברירת המחדל 'הצגה של עוד אפשרויות'.

Kotlin

val button =
  CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
    .setSessionCommand(SessionCommand(CUSTOM_ACTION_ID, Bundle.EMPTY))
    .setSlots(CommandButton.SLOT_FORWARD)
    .build()

Java

CommandButton button =
    new CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
        .setSessionCommand(new SessionCommand(CUSTOM_ACTION_ID, Bundle.EMPTY))
        .setSlots(CommandButton.SLOT_FORWARD)
        .build();

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

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

אפשר להשתמש ב-CommandButton.DisplayConstraints כדי ליצור תצוגה מקדימה של האופן שבו יוכרעו ההעדפות של לחצן המדיה, בהתאם למגבלות התצוגה של ממשק המשתמש.

הגדרת העדפות של כפתורי מדיה

הדרך הכי קלה להגדיר את ההעדפות של כפתורי המדיה היא להגדיר את הרשימה כשיוצרים את MediaSession. לחלופין, אפשר לבטל את ההגדרה של MediaSession.Callback.onConnect כדי להתאים אישית את ההעדפות של כפתורי המדיה לכל בקר מחובר.

Kotlin

val mediaSession =
  MediaSession.Builder(context, player)
    .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton))
    .build()

Java

MediaSession mediaSession =
  new MediaSession.Builder(context, player)
      .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton))
      .build();

עדכון ההעדפות של כפתורי המדיה אחרי אינטראקציה של משתמש

אחרי טיפול באינטראקציה עם הנגן, יכול להיות שתרצו לעדכן את הלחצנים שמוצגים בממשק המשתמש של בקר המשחקים. דוגמה טיפוסית היא כפתור מתג שמשנה את הסמל והפעולה שלו אחרי הפעלת הפעולה שמשויכת לכפתור הזה. כדי לעדכן את העדפות כפתורי המדיה, אפשר להשתמש ב-MediaSession.setMediaButtonPreferences כדי לעדכן את ההעדפות של כל הבקרים או של בקר ספציפי:

Kotlin

// Handle "favoritesButton" action, replace by opposite button
mediaSession.setMediaButtonPreferences(
  ImmutableList.of(likeButton, removeFromFavoritesButton))

Java

// Handle "favoritesButton" action, replace by opposite button
mediaSession.setMediaButtonPreferences(
    ImmutableList.of(likeButton, removeFromFavoritesButton));

הוספת פקודות מותאמות אישית והתאמה אישית של התנהגות ברירת המחדל

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

הצהרה על פקודות בהתאמה אישית וטיפול בהן

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

כדי להגדיר פקודות בהתאמה אישית, צריך לבטל את ההגדרה של MediaSession.Callback.onConnect() כדי להגדיר את הפקודות הזמינות בהתאמה אישית לכל בקר מחובר.

Kotlin

private class CustomMediaSessionCallback: MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  override fun onConnect(
    session: MediaSession,
    controller: MediaSession.ControllerInfo
  ): ConnectionResult {
    val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY))
        .build()
    return AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build()
  }
}

Java

class CustomMediaSessionCallback implements MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  @Override
  public ConnectionResult onConnect(
    MediaSession session,
    ControllerInfo controller) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
            .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
            .build();
    return new AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
}

כדי לקבל בקשות לפקודות מותאמות אישית מ-MediaController, צריך לבטל את השיטה onCustomCommand() ב-Callback.

Kotlin

private class CustomMediaSessionCallback: MediaSession.Callback {
  ...
  override fun onCustomCommand(
    session: MediaSession,
    controller: MediaSession.ControllerInfo,
    customCommand: SessionCommand,
    args: Bundle
  ): ListenableFuture<SessionResult> {
    if (customCommand.customAction == SAVE_TO_FAVORITES) {
      // Do custom logic here
      saveToFavorites(session.player.currentMediaItem)
      return Futures.immediateFuture(
        SessionResult(SessionResult.RESULT_SUCCESS)
      )
    }
    ...
  }
}

Java

class CustomMediaSessionCallback implements MediaSession.Callback {
  ...
  @Override
  public ListenableFuture<SessionResult> onCustomCommand(
    MediaSession session, 
    ControllerInfo controller,
    SessionCommand customCommand,
    Bundle args
  ) {
    if(customCommand.customAction.equals(SAVE_TO_FAVORITES)) {
      // Do custom logic here
      saveToFavorites(session.getPlayer().getCurrentMediaItem());
      return Futures.immediateFuture(
        new SessionResult(SessionResult.RESULT_SUCCESS)
      );
    }
    ...
  }
}

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

התאמה אישית של פקודות ברירת המחדל של הנגן

כל פקודות ברירת המחדל וניהול המצב מועברים אל Player שנמצא ב-MediaSession. כדי להתאים אישית את ההתנהגות של פקודה שמוגדרת בממשק Player, כמו play() או seekToNext(), צריך להוסיף את Player ל-ForwardingSimpleBasePlayer לפני שמעבירים אותה ל-MediaSession:

Kotlin

val player = (logic to build a Player instance)

val forwardingPlayer = object : ForwardingSimpleBasePlayer(player) {
  // Customizations
}

val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()

Java

ExoPlayer player = (logic to build a Player instance)

ForwardingSimpleBasePlayer forwardingPlayer =
    new ForwardingSimpleBasePlayer(player) {
      // Customizations
    };

MediaSession mediaSession =
  new MediaSession.Builder(context, forwardingPlayer).build();

מידע נוסף על ForwardingSimpleBasePlayer אפשר למצוא במדריך ExoPlayer בנושא התאמה אישית.

זיהוי הבקר ששלח את הפקודה לנגן

כששיחה לשיטת Player מתחילה על ידי MediaController, אפשר לזהות את מקור השיחה באמצעות MediaSession.controllerForCurrentRequest ולקבל את ControllerInfo עבור הבקשה הנוכחית:

Kotlin

class CallerAwarePlayer(player: Player) :
  ForwardingSimpleBasePlayer(player) {

  override fun handleSeek(
    mediaItemIndex: Int,
    positionMs: Long,
    seekCommand: Int,
  ): ListenableFuture<*> {
    Log.d(
      "caller",
      "seek operation from package ${session.controllerForCurrentRequest?.packageName}",
    )
    return super.handleSeek(mediaItemIndex, positionMs, seekCommand)
  }
}

Java

public class CallerAwarePlayer extends ForwardingSimpleBasePlayer {
  public CallerAwarePlayer(Player player) {
    super(player);
  }

  @Override
  protected ListenableFuture<?> handleSeek(
        int mediaItemIndex, long positionMs, int seekCommand) {
    Log.d(
        "caller",
        "seek operation from package: "
            + session.getControllerForCurrentRequest().getPackageName());
    return super.handleSeek(mediaItemIndex, positionMs, seekCommand);
  }
}

התאמה אישית של הטיפול בכפתורי מדיה

לחצני מדיה הם לחצני חומרה שנמצאים במכשירי Android ובמכשירים היקפיים אחרים, כמו לחצן ההפעלה/ההשהיה באוזניות Bluetooth. ‫Media3 מטפל באירועים של לחצני מדיה כשהם מגיעים לסשן, וקורא לשיטה המתאימה Player בנגן הסשן.

מומלץ לטפל בכל האירועים של לחצני המדיה הנכנסים בשיטה המתאימה Player. לתרחישי שימוש מתקדמים יותר, אפשר ליירט את האירועים של לחצן המדיה ב-MediaSession.Callback.onMediaButtonEvent(Intent).

טיפול בשגיאות ודיווח עליהן

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

שגיאות חמורות בהפעלה

הנגן מדווח על שגיאת הפעלה קריטית לסשן, ואז מדווח על כך לבקרי המדיה כדי להפעיל את Player.Listener.onPlayerError(PlaybackException) ואת Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException).

במקרה כזה, מצב ההפעלה משתנה ל-STATE_IDLE ו-MediaController.getPlaybackError() מחזירה את PlaybackException שגרם למעבר. בקר יכול לבדוק את PlayerException.errorCode כדי לקבל מידע על הסיבה לשגיאה.

הגדרת שגיאה מותאמת אישית בנגן

בנוסף לשגיאות קריטיות שמדווחות על ידי הנגן, אפליקציה יכולה להגדיר PlaybackException מותאם אישית ברמה של MediaSession באמצעות MediaSession.setPlaybackException(PlaybackException). כך האפליקציה יכולה לסמן מצב שגיאה לבקרי המשחקים המחוברים. אפשר להגדיר את ההחרגה לכל הבקרים המחוברים או ל-ControllerInfo ספציפי.

כשבאפליקציה מוגדרת PlaybackException באמצעות ה-API הזה:

  • יישלחו התראות למופעים מחוברים של MediaController. הקריאות החוזרות (callback) ‏Listener.onPlayerError(PlaybackException) ו-Listener.onPlayerErrorChanged(@Nullable PlaybackException) בבקר יופעלו עם החריגה שסופקה.

  • השיטה MediaController.getPlayerError() תחזיר את הערך PlaybackException שהוגדר על ידי האפליקציה.

  • מצב ההפעלה של הבקרים המושפעים ישתנה לPlayer.STATE_IDLE.

  • הפקודות הזמינות יוסרו ויישארו רק פקודות קריאה כמו COMMAND_GET_TIMELINE, אם הן כבר אושרו. לדוגמה, המצב של Timeline קפוא למצב שבו החריגה הוחלה על בקר. פקודות שמנסות לשנות את מצב הנגן, כמו COMMAND_PLAY, מוסרות עד שהאפליקציה מסירה את חריגת ההפעלה של הבקר הנתון.

כדי לנקות את ההגדרה הקודמת של PlaybackException בהתאמה אישית ולשחזר את הדיווח הרגיל על מצב הנגן, אפשר להפעיל את הפונקציה MediaSession.setPlaybackException(/* playbackException= */ null) או MediaSession.setPlaybackException(ControllerInfo, /* playbackException= */ null) באפליקציה.

התאמה אישית של שגיאות קריטיות

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

Kotlin

val forwardingPlayer = ErrorForwardingPlayer(player)
val session = MediaSession.Builder(context, forwardingPlayer).build()

Java

Player forwardingPlayer = new ErrorForwardingPlayer(player);
MediaSession session =
    new MediaSession.Builder(context, forwardingPlayer).build();

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

Kotlin

class ErrorForwardingPlayer (private val context: Context, player: Player) :
    ForwardingSimpleBasePlayer(player) {

  override fun getState(): State {
    var state = super.getState()
    if (state.playerError != null) {
      state =
        state.buildUpon()
          .setPlayerError(customizePlaybackException(state.playerError!!))
          .build()
    }
    return state
  }

  fun customizePlaybackException(error: PlaybackException): PlaybackException {
    val buttonLabel: String
    val errorMessage: String
    when (error.errorCode) {
      PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW -> {
        buttonLabel = context.getString(R.string.err_button_label_restart_stream)
        errorMessage = context.getString(R.string.err_msg_behind_live_window)
      }
      else -> {
        buttonLabel = context.getString(R.string.err_button_label_ok)
        errorMessage = context.getString(R.string.err_message_default)
      }
    }
    val extras = Bundle()
    extras.putString("button_label", buttonLabel)
    return PlaybackException(errorMessage, error.cause, error.errorCode, extras)
  }
}

Java

class ErrorForwardingPlayer extends ForwardingSimpleBasePlayer {

  private final Context context;

  public ErrorForwardingPlayer(Context context, Player player) {
    super(player);
    this.context = context;
  }

  @Override
  protected State getState() {
    State state = super.getState();
    if (state.playerError != null) {
      state =
          state.buildUpon()
              .setPlayerError(customizePlaybackException(state.playerError))
              .build();
    }
    return state;
  }

  private PlaybackException customizePlaybackException(PlaybackException error) {
    String buttonLabel;
    String errorMessage;
    switch (error.errorCode) {
      case PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW:
        buttonLabel = context.getString(R.string.err_button_label_restart_stream);
        errorMessage = context.getString(R.string.err_msg_behind_live_window);
        break;
      default:
        buttonLabel = context.getString(R.string.err_button_label_ok);
        errorMessage = context.getString(R.string.err_message_default);
        break;
    }
    Bundle extras = new Bundle();
    extras.putString("button_label", buttonLabel);
    return new PlaybackException(errorMessage, error.getCause(), error.errorCode, extras);
  }
}

שגיאות לא קריטיות

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

Kotlin

val sessionError = SessionError(
  SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
  context.getString(R.string.error_message_authentication_expired),
)

// Option 1: Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError)

// Option 2: Sending a nonfatal error to the media notification controller only
// to set the error code and error message in the playback state of the platform
// media session.
mediaSession.mediaNotificationControllerInfo?.let {
  mediaSession.sendError(it, sessionError)
}

Java

SessionError sessionError = new SessionError(
    SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
    context.getString(R.string.error_message_authentication_expired));

// Option 1: Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError);

// Option 2: Sending a nonfatal error to the media notification controller only
// to set the error code and error message in the playback state of the platform
// media session.
ControllerInfo mediaNotificationControllerInfo =
    mediaSession.getMediaNotificationControllerInfo();
if (mediaNotificationControllerInfo != null) {
  mediaSession.sendError(mediaNotificationControllerInfo, sessionError);
}

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

קבלת שגיאות לא קריטיות

אפליקציה MediaController מקבלת שגיאה לא קריטית באמצעות הטמעה של MediaController.Listener.onError:

Kotlin

val future = MediaController.Builder(context, sessionToken)
  .setListener(object : MediaController.Listener {
    override fun onError(controller: MediaController, sessionError: SessionError) {
      // Handle nonfatal error.
    }
  })
  .buildAsync()

Java

MediaController.Builder future =
    new MediaController.Builder(context, sessionToken)
        .setListener(
            new MediaController.Listener() {
              @Override
              public void onError(MediaController controller, SessionError sessionError) {
                // Handle nonfatal error.
              }
            });