באפליקציות של היום, שמתמקדות במדיה, חשוב לספק חוויית הפעלה חלקה ללא הפרעות כדי להבטיח חוויית משתמש מהנה. המשתמשים מצפים שהסרטונים יתחילו באופן מיידי ויופעלו בצורה חלקה ללא הפסקות.
האתגר המרכזי הוא זמן האחזור. בדרך כלל, נגן וידאו מתחיל לפעול – להתחבר, להוריד, לנתח, לבצע באפרינג – רק אחרי שהמשתמש בוחר פריט להפעלה. הגישה הזו איטית מדי בהקשר של סרטונים קצרים של היום. הפתרון הוא להיות פרואקטיביים. אנחנו צריכים לצפות מה המשתמש יצפה בו בהמשך ולהכין את התוכן מראש. זהו עיקרון הטעינה מראש.
היתרונות העיקריים של טעינה מראש:
- 🚀 התחלת הפעלה מהירה יותר: הסרטונים כבר מוכנים להפעלה, ולכן המעברים בין הפריטים מהירים יותר וההתחלה מיידית יותר.
- 📉 פחות באפרינג: טעינת נתונים מראש מפחיתה את הסיכוי שההפעלה תיעצר, למשל בגלל בעיות ברשת.
- ✨ חוויית משתמש חלקה יותר: השילוב של הפעלה מהירה יותר ופחות באפרינג יוצר אינטראקציה חלקה יותר שמשתמשים יכולים ליהנות ממנה.
בסדרת המאמרים הזו בת שלושה חלקים, נציג את כלי השירות המתקדמים של Media3 לטעינה (מראש) של רכיבים, ונתעמק בהם.
- בחלק הראשון נסביר את היסודות: נלמד על האסטרטגיות השונות לטעינה מראש שזמינות ב-Media3, נסביר איך להפעיל את PreloadConfiguration ואיך להגדיר את DefaultPreloadManager כדי לאפשר לאפליקציה לטעון מראש פריטים. בסוף הפוסט הזה בבלוג, תוכלו לטעון מראש ולהפעיל פריטי מדיה עם הדירוג והמשך שהגדרתם.
- בחלק 2 נתעמק בנושאים מתקדמים יותר של DefaultPreloadManager: שימוש ב-listeners לניתוח נתונים, שיטות מומלצות שמוכנות לייצור כמו sliding window pattern ורכיבים משותפים בהתאמה אישית של DefaultPreloadManager ו-ExoPlayer.
- בחלק 3, נתעמק בנושא של שמירת נתונים במטמון בדיסק באמצעות DefaultPreloadManager.
הטעינה מראש באה לעזרה! 🦸♀️
הרעיון המרכזי מאחורי טעינה מראש הוא פשוט: טעינת תוכן מדיה לפני שצריך אותו. עד שהמשתמש מחליק לסרטון הבא, הפלחים הראשונים של הסרטון כבר מורדים וזמינים, ומוכנים להפעלה מיידית.
אפשר לחשוב על זה כמו על מסעדה. במטבח עמוס לא מחכים להזמנה כדי להתחיל לקצוץ בצל. 🧅 הם מבצעים את עבודת ההכנה מראש. טעינה מראש היא עבודת ההכנה של נגן הווידאו.
כשהטעינה מראש מופעלת, היא יכולה לעזור לצמצם את זמן האחזור של ההצטרפות כשמשתמש מדלג לפריט הבא לפני שמאגר הנתונים הזמני של ההפעלה מגיע לפריט הבא. התקופה הראשונה של החלון הבא מוכנה, ודגימות של סרטונים, אודיו וטקסט נשמרות במאגר. התקופה שנטענה מראש מתווספת מאוחר יותר לתור של הנגן עם דגימות שנשמרו בזיכרון המטמון וזמינות באופן מיידי, ומוכנות להעברה לקודק לצורך עיבוד.
ב-Media3 יש שני ממשקי API עיקריים לטעינה מראש, שכל אחד מהם מתאים לתרחישי שימוש שונים. השלב הראשון הוא לבחור את ה-API הנכון.
1. טעינה מראש של פריטים בפלייליסט באמצעות PreloadConfiguration
זוהי גישה פשוטה שמתאימה למדיה ליניארית ורציפה כמו פלייליסטים, שבהם סדר ההפעלה צפוי (למשל, סדרה של פרקים). אתם מעבירים לנגן את הרשימה המלאה של פריטי המדיה באמצעות ממשקי ה-API של playlist ב-ExoPlayer ומגדירים את PreloadConfiguration לנגן, ואז הוא טוען מראש באופן אוטומטי את הפריטים הבאים ברצף בהתאם להגדרה. ממשק ה-API הזה מנסה לבצע אופטימיזציה של זמן האחזור של ההצטרפות כשמשתמש מדלג לפריט הבא לפני שמאגר ההפעלה חופף כבר לפריט הבא.
טעינה מראש מתחילה רק כשלא מתבצעת טעינה של מדיה להפעלה הנוכחית, וכך נמנעת תחרות על רוחב הפס עם ההפעלה הראשית.
אם אתם עדיין לא בטוחים אם אתם צריכים טעינה מראש, ה-API הזה הוא אפשרות מצוינת ופשוטה לנסות את התכונה.
player.preloadConfiguration =
PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L)עם PreloadConfiguration שלמעלה, הנגן מנסה לטעון מראש חמש שניות של מדיה עבור הפריט הבא בפלייליסט.
אחרי שמפעילים את האפשרות הזו, אפשר להשבית אותה שוב באמצעות PreloadConfiguration.DEFAULT:
player.preloadConfiguration = PreloadConfiguration.DEFAULT
2. טעינה מראש של רשימות דינמיות באמצעות PreloadManager
בממשקי משתמש דינמיים כמו פידים אנכיים או קרוסלות, שבהם הפריט הבא נקבע לפי אינטראקציה של המשתמש, מתאים להשתמש ב-PreloadManager API. זהו רכיב חדש ועוצמתי ועצמאי בספריית Media3 ExoPlayer, שנועד במיוחד לטעינה מראש באופן יזום. הוא מנהל אוסף של MediaSource פוטנציאליים, מתעדף אותם על סמך הקרבה למיקום הנוכחי של המשתמש ומציע שליטה גרנולרית על מה לטעון מראש, ומתאים לתרחישים מורכבים כמו פידים דינמיים של סרטונים קצרים.
הגדרת PreloadManager
DefaultPreloadManager הוא ההטמעה הקנונית של PreloadManager.
הכלי ליצירת DefaultPreloadManager יכול ליצור גם את DefaultPreloadManager וגם כל מופע של ExoPlayer שיפעיל את התוכן שנטען מראש. כדי ליצור DefaultPreloadManager, צריך להעביר TargetPreloadStatusControl, שמנהל הטעינה מראש יכול לשלוח לו שאילתות כדי לגלות כמה לטעון מראש עבור פריט. בקטע הבא נסביר ונגדיר דוגמה ל-TargetPreloadStatusControl.
val preloadManagerBuilder = DefaultPreloadManager.Builder(context, targetPreloadStatusControl) val preloadManager = val preloadManagerBuilder.build() // Build ExoPlayer with DefaultPreloadManager.Builder val player = preloadManagerBuilder.buildExoPlayer()
חשוב להשתמש באותו builder גם ב-ExoPlayer וגם ב-DefaultPreloadManager, כדי לוודא שהרכיבים שמתחת לפני השטח משותפים בצורה נכונה.
זה הכול! עכשיו יש לכם מנהל שמוכן לקבל הוראות.
הגדרת משך ודירוג באמצעות TargetPreloadStatusControl
מה קורה אם רוצים לבצע טעינה מראש של סרטון באורך של 10 שניות, למשל? אתם יכולים לציין את המיקום של פריטי המדיה בקרוסלה, והמחלקה DefaultPreloadManager נותנת עדיפות לטעינת הפריטים לפי המרחק שלהם מהפריט שהמשתמש מפעיל כרגע.
אם רוצים לשלוט במשך הזמן של הטעינה מראש של הפריט, אפשר להגדיר זאת באמצעות הערך שמוחזר על ידי DefaultPreloadManager.PreloadStatus.
לדוגמה,
- פריט 'א' הוא בעדיפות הכי גבוהה, צריך לטעון 5 שניות של סרטון.
- הפריט 'B' הוא בעדיפות בינונית, אבל כשמגיעים אליו, צריך לטעון 3 שניות של סרטון.
- פריט C הוא בעדיפות נמוכה יותר, והמערכת טוענת רק את הרצועות.
- פריט 'ד' הוא בעדיפות נמוכה עוד יותר, רק צריך להתכונן.
- כל שאר הפריטים רחוקים, אל תטען מראש שום דבר.
השליטה הפרטנית הזו יכולה לעזור לכם לשפר את ניצול המשאבים, ומומלץ לעשות זאת כדי להבטיח הפעלה חלקה.
import androidx.media3.exoplayer.DefaultPreloadManager.PreloadStatus
class MyTargetPreloadStatusControl(
currentPlayingIndex: Int = C.INDEX_UNSET
) : TargetPreloadStatusControl<Int,PreloadStatus> {
// The app is responsible for updating this based on UI state
override fun getTargetPreloadStatus(index: Int): PreloadStatus? {
val distance = index - currentPlayingIndex
// Adjacent items (Next): preload 5 seconds
if (distance == 1) {
// Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED and suggest loading // 5000ms from the default start position
return PreloadStatus.specifiedRangeLoaded(5000L)
}
// Adjacent items (Previous): preload 3 seconds
else if (distance == -1) {
// Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED //and suggest loading 3000ms from the default start position
return PreloadStatus.specifiedRangeLoaded(3000L)
}
// Items two positions away: just select tracks
else if (distance) == 2) {
// Return a PreloadStatus that is labelled by STAGE_TRACKS_SELECTED
return PreloadStatus.TRACKS_SELECTED
}
// Items four positions away: just select prepare
else if (abs(distance) <= 4) {
// Return a PreloadStatus that is labelled by STAGE_SOURCE_PREPARED
return PreloadStatus.SOURCE_PREPARED
}
// All other items are too far away
return null
}
}טיפ: PreloadManager יכול לשמור את הפריטים הקודמים והבאים בטעינה מראש, בעוד ש-PreloadConfiguration יטען מראש רק את הפריטים הבאים.
ניהול פריטים לטעינה מראש
אחרי שיוצרים את המנהל, אפשר להתחיל להגיד לו מה לעשות. בזמן שהמשתמש גולל בפיד, המערכת מזהה את הסרטונים הבאים ומוסיפה אותם למנהל. האינטראקציה עם PreloadManager היא שיחה מבוססת-מצב בין ממשק המשתמש שלכם לבין מנוע הטעינה מראש.
1. הוספת קובצי מדיה
כשממלאים את הפיד, צריך להודיע למנהל על המדיה שהוא צריך לעקוב אחריה. אם אתם מתחילים, אתם יכולים להוסיף את כל הרשימה שאתם רוצים לטעון מראש. לאחר מכן, תוכלו להמשיך ולהוסיף פריט אחד לרשימה לפי הצורך. יש לכם שליטה מלאה על הפריטים ברשימת הטעינה מראש, ולכן אתם גם צריכים לנהל את הפריטים שנוספים למנהל ומוסרים ממנו.
val initialMediaItems = pullMediaItemsFromService(/* count= */ 20)
for (index in 0 until initialMediaItems.size) {
preloadManager.add(
initialMediaItems.get(index),index)
)
}מערכת ניהול הנתונים תתחיל לאחזר נתונים עבור MediaItem הזה ברקע.
אחרי ההוספה, צריך להודיע למנהל להעריך מחדש את הרשימה החדשה (לרמז שמשהו השתנה, כמו הוספה או הסרה של פריט, או שהמשתמש עבר להפעיל פריט חדש).
preloadManager.invalidate()
2. אחזור והפעלה של פריט
כאן מתחילת הלוגיקה העיקרית של ההפעלה. כשהמשתמש מחליט להפעיל את הסרטון הזה, לא צריך ליצור MediaSource חדש. במקום זאת, מבקשים מ-PreloadManager את התשובה שהוא כבר הכין. אפשר לאחזר את MediaSource מ-Preload Manager באמצעות MediaItem.
אם הפריט שאוחזר מ-PreloadManager הוא null, המשמעות היא ש-mediaItem עדיין לא נטען מראש או לא נוסף ל-PreloadMamager, ולכן בוחרים להגדיר את mediaItem ישירות.
// When a media item is about to display on the screen
val mediaSource = preloadManager.getMediaSource(mediaItem)
if (mediaSource!= null) {
player.setMediaSource(mediaSource)
} else {
// If mediaSource is null, that mediaItem hasn't been added yet.
// So, send it directly to the player.
player.setMediaItem(mediaItem)
}
player.prepare()
// When the media item is displaying at the center of the screen
player.play()הכנת ה-MediaSource שאוחזר מ-PreloadManager מאפשרת מעבר חלק מטעינה מראש להפעלה, באמצעות הנתונים שכבר נמצאים בזיכרון. כך שעת ההתחלה תהיה מהירה יותר.
3. שמירה על סנכרון בין האינדקס הנוכחי לבין ממשק המשתמש
מכיוון שהפיד או הרשימה שלנו יכולים להיות דינמיים, חשוב להודיע ל-PreloadManager על האינדקס הנוכחי של הפריט שמופעל, כדי שהוא תמיד יוכל לתת עדיפות לפריטים שהכי קרובים לאינדקס הנוכחי לצורך טעינה מראש.
preloadManager.setCurrentPlayingIndex(currentIndex) // Need to call invalidate() to update the priorities preloadManager.invalidate()
4. הסרת פריט
כדי לשמור על יעילות המנהל, כדאי להסיר פריטים שהוא כבר לא צריך לעקוב אחריהם, כמו פריטים שנמצאים רחוק מהמיקום הנוכחי של המשתמש.
// When an item is too far from the current playing index preloadManager.remove(mediaItem)
אם רוצים למחוק את כל הפריטים בבת אחת, אפשר להתקשר למספר preloadManager.reset().
5. שחרור חשבון הניהול
כשאתם לא צריכים יותר את PreloadManager (למשל, כשממשק המשתמש מושמד), אתם צריכים לשחרר אותו כדי לפנות את המשאבים שלו. כדאי לעשות את זה במקום שבו אתם כבר משחררים את המשאבים של השחקן. מומלץ לשחרר את נגן המודעות לפני השחקן, כי השחקן יכול להמשיך לפעול אם לא צריך יותר טעינה מראש.
// In your Activity's onDestroy() or Composable's onDispose preloadManager.release()
זמן הדגמה
כדאי לראות את זה בפעולה 👍
בהדגמה שלמטה, בצד ימין אפשר לראות את ההשפעה של PreloadManager, שמאפשר זמני טעינה מהירים יותר, ובצד שמאל אפשר לראות את חוויית השימוש הקיימת. אפשר גם לראות את הדוגמה של הקוד להדגמה. (בונוס: מוצג גם זמן האחזור של ההפעלה לכל סרטון)
המאמר הבא
זהו, סיימנו את חלק 1. עכשיו יש לכם את הכלים לבניית מערכת דינמית לטעינה מראש. אפשר להשתמש ב-PreloadConfiguration כדי לטעון מראש את הפריט הבא בפלייליסט ב-ExoPlayer, או להגדיר DefaultPreloadManager, להוסיף ולהסיר פריטים תוך כדי הפעלה, להגדיר את סטטוס הטעינה מראש של היעד ולאחזר בצורה נכונה את התוכן שנטען מראש להפעלה.
בחלק 2, נסביר לעומק על DefaultPreloadManager. נבדוק איך להאזין לאירועים של טעינה מראש, נדון בשיטות מומלצות כמו שימוש בחלון הזזה כדי למנוע בעיות בזיכרון, ונציץ אל מאחורי הקלעים של רכיבים משותפים בהתאמה אישית של ExoPlayer ו-DefaultPreloadManager.
יש לך משוב לשתף? נשמח לשמוע ממך.
בקרוב נפרסם עוד מידע. בינתיים, כדאי לנסות לשפר את מהירות האפליקציה. 🚀
-
חדשות על מוצריםברוכים הבאים לחלק השני בסדרה בת שלושה חלקים בנושא טעינה מראש של מדיה באמצעות Media3. הסדרה הזו נועדה להדריך אתכם בתהליך של יצירת חוויות מדיה רספונסיביות מאוד עם זמן אחזור נמוך באפליקציות ל-Android.
Mayuri Khinvasara Khabya • משך הקריאה: 9 דקות -
חדשות על מוצריםלמפתחי Android יש הרבה אפשרויות בכל הנוגע לסוכנים, למודלים גדולים של שפה (LLM), לכלים ולממשקי שורת פקודה (CLI) שבהם הם משתמשים לפיתוח אפליקציות. המטרה שלנו היא לעזור לכם לפתח אפליקציות יפות ואיכותיות ל-Android, לא משנה איך תבחרו לפתח אותן.
Simona Milanovic • משך הקריאה: 4 דקות -
חדשות על מוצריםב-Google Play, אנחנו מרחיבים כל הזמן את פלטפורמת המינויים שלנו כדי לעזור לכם להגדיל את הצמיחה, להתאים את עצמכם למודלים עסקיים חדשים ולהגיע למשתמשים שלכם בדיוק במקום שבו הם נמצאים.
Sheenam Mittal • משך הקריאה: 4 דקות
רוצים לקבל טיפים עדכניים לפיתוח Android ישירות לאימייל כל שבוע?