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

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

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

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

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

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

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

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

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

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

כל אמצעי בקרה, למשל ממשק המשתמש של המערכת, 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)
    .setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
    .setSlots(CommandButton.SLOT_FORWARD)
    .build()

Java

CommandButton button =
    new CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
        .setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
        .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
  ): 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. הקריאות החוזרות (callbacks) של 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.
              }
            });