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

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

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

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

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

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

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

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

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

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

אם תרחיש השימוש שלכם לא מתאים לאף אחת מהאפשרויות שצוינו למעלה, כדאי לשקול אם אתם מסכימים שההפעלה באפליקציה תימשך גם כשהמשתמש לא מקיים אינטראקציה פעילה עם התוכן. אם התשובה היא כן, כדאי לבחור באפשרות 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() עם התכונה הזו מופיעה באפליקציית ההדגמה של הסשן.

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

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

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

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

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

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();

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

  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
  ): MediaSession.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 כדי לקבל מידע על הסיבה לשגיאה.

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

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

כדי לספק למשתמש מידע מותאם לשפה שלו ומשמעותי, אפשר להתאים אישית את קוד השגיאה, הודעת השגיאה ותוספות השגיאה של שגיאת הפעלה קריטית באמצעות 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.
              }
            });