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

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

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

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

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

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

זה שימושי בהרבה תרחישי שימוש. במיוחד מומלץ להשתמש ב-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: בקר המדיה מאפשר להעביר פקודות ממקורות חיצוניים לסשן המדיה.

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

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

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

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

  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.
              }
            });