סשנים של מדיה מספקים דרך אוניברסלית לאינטראקציה עם נגן אודיו או וידאו. ב-Media3, הנגן שמוגדר כברירת מחדל הוא המחלקה ExoPlayer, שמטמיעה את הממשק Player. חיבור סשן המדיה לנגן מאפשר לאפליקציה לפרסם את הפעלת המדיה באופן חיצוני ולקבל פקודות הפעלה ממקורות חיצוניים.
הפקודות יכולות להגיע מכפתורים פיזיים, כמו כפתור ההפעלה באוזניות או בשלט של הטלוויזיה. הם יכולים להגיע גם מאפליקציות לקוח שיש להן ממשק שליטה במדיה, כמו הוראה "להשהות" לעוזרים אישיים מבוססי-AI, כולל Gemini או Google Assistant. פעילות המדיה מעבירה את הפקודות האלה לנגן של אפליקציית המדיה.
מתי כדאי לבחור בסשן מדיה
כשמטמיעים את MediaSession, מאפשרים למשתמשים לשלוט בהפעלה:
- דרך האוזניות. לפעמים יש כפתורים או אינטראקציות במגע שהמשתמש יכול לבצע באוזניות כדי להפעיל או להשהות מדיה, או כדי לעבור לטראק הבא או הקודם.
- באמצעות דיבור אל עוזרים אישיים מבוססי-AI, כולל Gemini או Google Assistant. דפוס נפוץ הוא לומר "Ok Google, pause" כדי להשהות את המדיה שמופעלת כרגע במכשיר.
- דרך שעון Wear OS. כך קל יותר לגשת לרכיבי ה-UI הנפוצים ביותר להפעלה בזמן שהם משחקים בטלפון.
- דרך ממשק השליטה במדיה. בקרוסלה הזו מוצגים פקדים לכל סשן מדיה פעיל.
- בטלוויזיה. מאפשרת פעולות עם לחצני הפעלה פיזיים, שליטה בהפעלה בפלטפורמה וניהול צריכת החשמל (לדוגמה, אם הטלוויזיה, מקרן הקול או מקלט ה-A/V נכבים או שהקלט מוחלף, ההפעלה באפליקציה אמורה להיפסק).
- באמצעות ממשק השליטה במדיה של Android Auto. ככה אפשר לשלוט בהפעלה בצורה בטוחה בזמן הנהיגה.
- וגם כל תהליך חיצוני אחר שצריך להשפיע על ההפעלה.
זה מעולה להרבה תרחישי שימוש. מומלץ מאוד להשתמש ב-MediaSession במקרים הבאים:
- אתם משדרים סרטונים ארוכים (LFV), כמו סרטים או טלוויזיה בשידור חי.
- אתם מאזינים בסטרימינג לתוכן אודיו ארוך, כמו פודקאסטים או פלייליסטים של מוזיקה.
- אתם מפתחים אפליקציית טלוויזיה.
עם זאת, לא כל תרחישי השימוש מתאימים ל-MediaSession. כדאי להשתמש רק ב-Player במקרים הבאים:
- אתם מציגים תוכן קצר, שבו אין צורך בשליטה חיצונית או בהפעלה ברקע.
- אין סרטון פעיל אחד, למשל כשהמשתמש גולל ברשימה וכמה סרטונים מוצגים במסך בו-זמנית.
- אתם מפעילים סרטון הסבר או סרטון היכרות חד-פעמי, ואתם מצפים שהמשתמש יצפה בו באופן פעיל בלי צורך ברכיבי UI חיצוניים להפעלה.
- התוכן שלכם רגיש מבחינת פרטיות ואתם לא רוצים שתהליכים חיצוניים ייגשו למטא-נתונים של המדיה (לדוגמה, מצב פרטי בדפדפן).
אם תרחיש השימוש שלכם לא מתאים לאף אחד מהתרחישים שמפורטים למעלה, כדאי לשקול אם אתם רוצים שהאפליקציה תמשיך להפעיל את התוכן גם כשהמשתמש לא מקיים אינטראקציה פעילה עם התוכן. אם התשובה היא כן, כנראה שכדאי לבחור באפשרות MediaSession. אם התשובה היא לא, כנראה שעדיף להשתמש ב-Player.
יצירת סשן מדיה
סשן מדיה פועל לצד הנגן שהוא מנהל. אפשר ליצור סשן מדיה באמצעות אובייקט Context ואובייקט Player. צריך ליצור ולהפעיל סשן מדיה כשצריך, למשל ב-lifecycle method onStart() או onResume() של Activity או Fragment, או ב-method 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=, כנראה שנוצר סשן באופן בלתי צפוי לפני ששוחרר מופע שנוצר קודם עם אותו מזהה. כדי למנוע דליפה של סשנים בגלל שגיאה בתכנות, המערכת מזהה מקרים כאלה ומודיעה עליהם באמצעות חריגה.
לתת הרשאת שליטה ללקוחות אחרים
סשן המדיה הוא המפתח לשליטה בהפעלה. הוא מאפשר לנתב פקודות ממקורות חיצוניים לנגן שמפעיל את המדיה. המקורות האלה יכולים להיות כפתורים פיזיים, כמו כפתור ההפעלה באוזניות או בשלט של הטלוויזיה, או פקודות עקיפות, כמו הוראה ל-Gemini או ל-Google Assistant להשהות את ההפעלה. באופן דומה, יכול להיות שתרצו לתת גישה למערכת Android כדי לשלוט בהתראות ובמסך הנעילה, או לשעון Wear OS כדי שתוכלו לשלוט בהפעלה מתצוגת השעון. לקוחות חיצוניים יכולים להשתמש בממשק השליטה במדיה כדי לשלוח פקודות הפעלה לאפליקציית המדיה. סשן המדיה מקבל את הפקודות האלה ומעביר אותן לנגן המדיה.
כשבקר עומד להתחבר לסשן המדיה, הסשן קורא ל-onConnectAsync(). אתם יכולים להשתמש בControllerInfo שסופק כדי להחליט אם לאשר או לדחות את הבקשה. במאמר הצהרה על פקודות מותאמות אישית וטיפול בהן תוכלו לקרוא דוגמה לאישור של בקשת חיבור.
אחרי החיבור, אפשר לשלוח פקודות הפעלה לסשן באמצעות הבקר. הפקודות האלה מועברות מהסשן אל הנגן. פקודות הפעלה ופלייליסט שמוגדרות בממשק Player מטופלות באופן אוטומטי בסשן.
שיטות קריאה חוזרת אחרות מאפשרות לכם לטפל, למשל, בבקשות לפקודות מותאמות אישית ולשינוי הפלייליסט. הקריאות החוזרות האלה כוללות באופן דומה אובייקט ControllerInfo, כדי שתוכלו לשנות את האופן שבו אתם מגיבים לכל בקשה על בסיס כל בקר.
שינוי הפלייליסט
סשן מדיה יכול לשנות ישירות את הפלייליסט של הנגן שלו, כפי שמוסבר במדריך של ExoPlayer לפלייליסטים.
בקרים יכולים גם לשנות את הפלייליסט אם לבקר יש גישה אל COMMAND_SET_MEDIA_ITEM או אל COMMAND_CHANGE_MEDIA_ITEMS.
כשמוסיפים פריטים חדשים לפלייליסט, בדרך כלל הנגן דורש מופעים של MediaItem עם URI מוגדר כדי שיהיה אפשר להפעיל אותם. כברירת מחדל, פריטים חדשים שנוספים מועברים אוטומטית לשיטות של הנגן כמו player.addMediaItem אם מוגדר להם URI.
אם רוצים להתאים אישית את המופעים של MediaItem שנוספו לנגן, אפשר לשנות את onAddMediaItems(). השלב הזה נדרש כשרוצים לתמוך בבקרים שמבקשים מדיה בלי URI מוגדר. במקום זאת, בדרך כלל אחד או יותר מהשדות הבאים ב-MediaItem מוגדרים לתיאור המדיה המבוקשת:
-
MediaItem.mediaId: מזהה כללי של המדיה. -
MediaItem.RequestMetadata.mediaUri: URI של בקשה שיכול להשתמש בסכימה מותאמת אישית, ולא בהכרח ניתן להפעלה ישירות על ידי הנגן. MediaItem.RequestMetadata.searchQuery: שאילתת חיפוש טקסטואלית, למשל מ-Gemini או מ-Google Assistant.-
MediaItem.mediaMetadata: מטא-נתונים מובְנים כמו "שם" או "אומן".
כדי לקבל אפשרויות נוספות להתאמה אישית של פלייליסטים חדשים לגמרי, אתם יכולים גם לשנות את onSetMediaItems(), שמאפשר לכם להגדיר את הפריט הראשון ואת המיקום בפלייליסט. לדוגמה, אתם יכולים להרחיב פריט אחד שביקשתם לפלייליסט שלם ולהורות לנגן להתחיל באינדקס של הפריט שביקשתם במקור. הטמעה לדוגמה של onSetMediaItems() עם התכונה הזו מופיעה באפליקציית ההדגמה של הסשן.
ניהול ההעדפות של כפתורי המדיה
כל בקר, למשל ממשק המשתמש של המערכת, Android Auto או Wear OS, יכול לקבל החלטות משלו לגבי הלחצנים שיוצגו למשתמש. כדי לציין אילו רכיבי UI להפעלה אתם רוצים לחשוף למשתמש, אתם יכולים לציין העדפות של לחצני מדיה ב-MediaSession. ההעדפות האלה מורכבות מרשימה מסודרת של מופעי CommandButton, שכל אחד מהם מגדיר העדפה לכפתור בממשק המשתמש.
הגדרת כפתורי הפקודות
מופעים של CommandButton משמשים להגדרת ההעדפות של כפתורי המדיה. כל כפתור מגדיר שלושה היבטים של הרכיב הרצוי בממשק המשתמש:
- סמל, שמגדיר את המראה החזותי. כשיוצרים
CommandButton.Builder, צריך להגדיר את הסמל לאחד מהקבועים שהוגדרו מראש. חשוב לשים לב שזה לא משאב תמונה או מפת סיביות (bitmap) בפועל. קבוע גנרי עוזר לבקרים לבחור משאב מתאים למראה ולתחושה עקביים בממשק המשתמש שלהם. אם אף אחד מהקבועים המוגדרים מראש של הסמלים לא מתאים לתרחיש לדוגמה שלכם, אתם יכולים להשתמש במקום זאת ב-setCustomIconResId. - הפקודה, שמגדירה את הפעולה שמופעלת כשהמשתמש יוצר אינטראקציה עם הכפתור. אפשר להשתמש ב-
setPlayerCommandבשבילPlayer.Command, או ב-setSessionCommandבשבילSessionCommandמוגדר מראש או בהתאמה אישית. - Slot, שמגדיר איפה הכפתור צריך להיות ממוקם בממשק המשתמש של הבקר. השדה הזה הוא אופציונלי ומוגדר באופן אוטומטי על סמך הסמל והפקודה. לדוגמה, אפשר לציין שכפתור מסוים יוצג באזור הניווט "הבא" בממשק המשתמש, במקום באזור ברירת המחדל "עוד".
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();
כשההעדפות של כפתורי המדיה נפתרות, האלגוריתם הבא מופעל:
- לכל
CommandButtonבהעדפות של כפתורי המדיה, ממקמים את הכפתור במשבצת הראשונה שזמינה ומותרת. - אם אחת מהמשבצות המרכזית, הקודמת או הבאה לא מלאה בכפתור, מוסיפים כפתורי ברירת מחדל למשבצת הזו.
אפשר להשתמש ב-CommandButton.DisplayConstraints כדי ליצור תצוגה מקדימה של האופן שבו ההעדפות של לחצני המדיה יפורשו בהתאם למגבלות התצוגה של ממשק המשתמש.
הגדרת ההעדפות של כפתורי המדיה
הדרך הכי קלה להגדיר את ההעדפות של כפתורי המדיה היא להגדיר את הרשימה כשיוצרים את ה-MediaSession. לחלופין, אפשר לשנות את MediaSession.Callback.onConnectAsync() כדי להתאים אישית את ההעדפות של כפתורי המדיה לכל בקר מחובר.
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.onConnectAsync() כדי להגדיר את הפקודות המותאמות אישית שזמינות לכל בקר מחובר.
Kotlin
private class CustomMediaSessionCallback : MediaSession.Callback { // Configure commands available to the controller in onConnect() override fun onConnectAsync( session: MediaSession, controller: ControllerInfo, ): ListenableFuture<ConnectionResult> { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY)) .build() return Futures.immediateFuture( AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build() ) } }
Java
private static class CustomMediaSessionCallback implements MediaSession.Callback { // Configure commands available to the controller in onConnect() @Override public ListenableFuture<ConnectionResult> onConnectAsync( MediaSession session, ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); return Futures.immediateFuture( new AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build()); } }
כדי לקבל בקשות לפקודות בהתאמה אישית מ-MediaController, צריך לשנות את השיטה onCustomCommand() ב-Callback.
Kotlin
private class CustomCallback : MediaSession.Callback { // ... override fun onCustomCommand( session: MediaSession, controller: 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)) } // ... return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } }
Java
private static class CustomCallback 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)); } // ... return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS)); } }
כדי לעקוב אחרי ממשק השליטה במדיה ששולח בקשה, אפשר להשתמש במאפיין packageName של האובייקט MediaSession.ControllerInfo שמועבר ל-methods של Callback. כך אתם יכולים להתאים אישית את ההתנהגות של האפליקציה בתגובה לפקודה מסוימת, אם היא מגיעה מהמערכת, מהאפליקציה שלכם או מאפליקציות אחרות של לקוחות.
התאמה אישית של פקודות ברירת המחדל של הנגן
כל פקודות ברירת המחדל והטיפול במצבים מועברים אל Player שנמצא ב-MediaSession. כדי להתאים אישית את ההתנהגות של פקודה שמוגדרת בממשק Player, כמו play() או seekToNext(), צריך לעטוף את Player ב-ForwardingSimpleBasePlayer לפני שמעבירים אותו אל MediaSession:
Kotlin
val forwardingPlayer = object : ForwardingSimpleBasePlayer(player) { // Customizations } val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()
Java
ForwardingSimpleBasePlayer forwardingPlayer = new ForwardingSimpleBasePlayer(player) { // Customizations }; MediaSession mediaSession = new MediaSession.Builder(context, forwardingPlayer).build();
מידע נוסף על ForwardingSimpleBasePlayer זמין במדריך של ExoPlayer בנושא התאמה אישית.
זיהוי הבקר ששלח את הפקודה לנגן
כששיטה של Player מופעלת על ידי MediaController, אפשר לזהות את המקור באמצעות MediaSession.controllerForCurrentRequest ולקבל את ControllerInfo לבקשה הנוכחית:
Kotlin
private class CallerAwarePlayer(player: Player) : ForwardingSimpleBasePlayer(player) { private lateinit var session: MediaSession 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
private static final class CallerAwarePlayer extends ForwardingSimpleBasePlayer { private MediaSession session; 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 מטפל באירועים של לחצני מדיה כשהם מגיעים לסשן, וקורא ל-method המתאים של Player בנגן הסשן.
מומלץ לטפל בכל האירועים הנכנסים של לחצני המדיה ב-method המתאים של Player. בתרחישים מתקדמים יותר, אפשר ליירט את האירועים של לחצני המדיה ב-MediaSession.Callback.onMediaButtonEvent().
טיפול בשגיאות ודיווח עליהן
יש שני סוגים של שגיאות שסשן יכול להפיק ולדווח לבקרים. שגיאות חמורות מדווחות על כשל טכני בהפעלה של הנגן בסשן, שקוטע את ההפעלה. שגיאות חמורות מדווחות לבקר באופן אוטומטי כשהן מתרחשות. שגיאות לא קריטיות הן שגיאות לא טכניות או שגיאות שקשורות למדיניות, שלא קוטעות את ההפעלה ונשלחות לבקרים על ידי האפליקציה באופן ידני.
שגיאות הפעלה קריטיות
הנגן מדווח לסשן על שגיאת הפעלה קריטית, ואז הסשן מדווח על השגיאה לבקרים כדי להפעיל את Player.Listener.onPlayerError(PlaybackException) ו-Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException).
במקרה כזה, מצב ההפעלה משתנה ל-STATE_IDLE והפונקציה MediaController.getPlayerError() מחזירה את הערך PlaybackException שגרם לשינוי. בקר יכול לבדוק את PlaybackException.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).
התאמה אישית של שגיאות קריטיות
כדי לספק למשתמש מידע משמעותי ומותאם לשוק המקומי, אתם יכולים להתאים אישית את קוד השגיאה, את הודעת השגיאה ואת התוספות לשגיאה של שגיאת הפעלה חמורה שמגיעה מהנגן עצמו. כדי לעשות את זה, משתמשים ב-ForwardingSimpleBasePlayer כשיוצרים את הסשן:
Kotlin
val session = MediaSession.Builder(context, ErrorForwardingPlayer(context, player)).build()
Java
MediaSession session = new MediaSession.Builder(context, new ErrorForwardingPlayer(context, player)).build();
במחלקה המשנית של ForwardingSimpleBasePlayer אפשר לשנות את getState() כדי ליירט את השגיאה ולהתאים אישית את קוד השגיאה, ההודעה או התוספות. באותו אופן, אתם יכולים גם ליצור שגיאות חדשות שלא קיימות בנגן המקורי:
Kotlin
private 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 } private 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
private static 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. } });