הממשק בליבה של ספריית ExoPlayer נמצא ב-Player
. Player
חושף פונקציונליות מסורתית של נגן מדיה ברמה גבוהה, כמו האפשרות לאחסן מדיה במאגר, להפעיל, להשהות ולחפש מדיה. הטמעת ברירת המחדל ExoPlayer
תוכננה כך שתהיה בה מעט הנחות לגבי סוג המדיה שמופעלת, האופן שבו היא מאוחסנת והאופן שבו היא מוצגת (ולכן היא מטילה מעט הגבלות עליהם). במקום להטמיע את הטעינה והרינדור של מדיה ישירות, הטמעות ExoPlayer
מאצילות את העבודה הזו לרכיבים שמוחדרים כאשר נוצר נגן או כשמקורות מדיה חדשים מועברים לנגן.
הרכיבים המשותפים לכל ההטמעות של ExoPlayer
הם:
- מכונות
MediaSource
שמגדירות את המדיה שרוצים להפעיל, טוענות את המדיה ומאפשרות לקרוא את המדיה הטעונה. מופעMediaSource
נוצר מ-MediaItem
על ידיMediaSource.Factory
בתוך הנגן. אפשר גם להעביר אותם ישירות לנגן באמצעות ממשק ה-API של פלייליסט שמבוסס על מקור מדיה. - מכונות
MediaSource.Factory
שממירותMediaItem
ל-MediaSource
. ה-MediaSource.Factory
מוזרק כשיוצרים את הנגן. - מכונות
Renderer
שמרינדרות רכיבים נפרדים של המדיה. הם מוחדרים בזמן יצירת הנגן. TrackSelector
שבוחר טראקים שסופקו על ידיMediaSource
לשימוש בכלRenderer
זמין. ה-TrackSelector
מוזרק כשהנגן נוצר.LoadControl
שקובע מתיMediaSource
יאגר יותר מדיה, וכמה מדיה ייאגר. ה-LoadControl
מוזרק כשיוצרים את השחקן.LivePlaybackSpeedControl
ששולט במהירות ההפעלה במהלך הפעלות בשידור חי, כדי לאפשר לנגן להישאר קרוב להיסט מוגדר בשידור חי. החדרLivePlaybackSpeedControl
מועבר כשהנגן נוצר.
הקונספט של החדרת רכיבים שמטמיעים חלקים מהפונקציונליות של הנגן קיים בספרייה. הטמעות ברירת המחדל של חלק מהרכיבים מעבירות את העבודה לרכיבים נוספים שהוזנו. כך אפשר להחליף רכיבי משנה רבים בנפרד בהטמעות שמוגדרות באופן מותאם אישית.
התאמה אישית של הנגן
מתוארות כמה דוגמאות נפוצות להתאמה אישית של הנגן על ידי החדרת רכיבים.
הגדרת ערימת הרשת
יש לנו דף בנושא התאמה אישית של מקבץ הרשת שמשמש את ExoPlayer.
שמירת נתונים במטמון שנטענו מהרשת
עיינו במדריכים בנושא שמירה זמנית במטמון תוך כדי תנועה והורדת מדיה.
התאמה אישית של אינטראקציות עם השרת
יכול להיות שחלק מהאפליקציות ירצו ליירט בקשות ותגובות של HTTP. יכול להיות שתרצו להחדיר כותרות בקשה בהתאמה אישית, לקרוא את כותרות התשובה של השרת, לשנות את מזהי ה-URI של הבקשות וכו'. לדוגמה, האפליקציה עשויה לאמת את עצמה על ידי הוספת אסימון ככותרת כשמבקשים את פלחי המדיה.
הדוגמה הבאה ממחישה איך להטמיע את ההתנהגויות האלה על ידי החדרת DataSource.Factory
בהתאמה אישית ל-DefaultMediaSourceFactory
:
Kotlin
val dataSourceFactory = DataSource.Factory { val dataSource = httpDataSourceFactory.createDataSource() // Set a custom authentication request header. dataSource.setRequestProperty("Header", "Value") dataSource } val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory) ) .build()
Java
DataSource.Factory dataSourceFactory = () -> { HttpDataSource dataSource = httpDataSourceFactory.createDataSource(); // Set a custom authentication request header. dataSource.setRequestProperty("Header", "Value"); return dataSource; }; ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)) .build();
בקטע הקוד שלמעלה, ה-HttpDataSource
שהוחדר כולל את הכותרת "Header: Value"
בכל בקשת HTTP. ההתנהגות הזו קבועה לכל אינטראקציה עם מקור HTTP.
לגישה מפורטת יותר, אפשר להחדיר התנהגות בזמן אמת באמצעות ResolvingDataSource
. קטע הקוד הבא מראה איך להחדיר כותרות של בקשות ממש לפני האינטראקציה עם מקור HTTP:
Kotlin
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time request headers. dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)) }
Java
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time request headers. dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));
אפשר גם להשתמש ב-ResolvingDataSource
כדי לבצע שינויים ב-URI בזמן אמת, כפי שמוצג בקטע הקוד הבא:
Kotlin
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time URI resolution logic. dataSpec.withUri(resolveUri(dataSpec.uri)) }
Java
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time URI resolution logic. dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));
התאמה אישית של הטיפול בשגיאות
הטמעה של LoadErrorHandlingPolicy
בהתאמה אישית מאפשרת לאפליקציות להתאים אישית את הדרך שבה ExoPlayer מגיב לשגיאות בטעינה. לדוגמה, יכול להיות שאפליקציה רוצה להיכשל במהירות במקום לנסות שוב הרבה פעמים, או להתאים אישית את לוגיקת ההשהיה לפני ניסיון חוזר שקובעת כמה זמן השחקן ימתין בין כל ניסיון חוזר. קטע הקוד הבא מראה איך ליישם לוגיקה מותאמת אישית של השהיה לפני ניסיון חוזר:
Kotlin
val loadErrorHandlingPolicy: LoadErrorHandlingPolicy = object : DefaultLoadErrorHandlingPolicy() { override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorInfo): Long { // Implement custom back-off logic here. return 0 } } val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy) ) .build()
Java
LoadErrorHandlingPolicy loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy() { @Override public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) { // Implement custom back-off logic here. return 0; } }; ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context) .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)) .build();
הארגומנט LoadErrorInfo
מכיל מידע נוסף על הטעינה שנכשלה כדי להתאים אישית את הלוגיקה על סמך סוג השגיאה או הבקשה שנכשלה.
התאמה אישית של דגלים של חילוץ
אפשר להשתמש בדגלים של חילוץ כדי להתאים אישית את האופן שבו פורמטים ספציפיים מחלצים ממדיה פרוגרסיבית. אפשר להגדיר אותם ב-DefaultExtractorsFactory
שסופק ל-DefaultMediaSourceFactory
. הדוגמה הבאה מעבירה דגל שמאפשר דילוג מבוסס אינדקס לסטרימינג של MP3.
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING) val player = ExoPlayer.Builder(context) .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory)) .build()
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING); ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory(new DefaultMediaSourceFactory(context, extractorsFactory)) .build();
הפעלת דילוג קבוע על קצב נתונים
להפעלת דילוג משוער בסטרימינג של MP3, ADTS ו-AMR, אפשר להשתמש בהנחה של קצב נתונים קבוע באמצעות הדגלים FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
.
אפשר להגדיר את הדגלים האלה למחוללים נפרדים באמצעות השיטות הנפרדות של DefaultExtractorsFactory.setXyzExtractorFlags
, כפי שמתואר למעלה. כדי להפעיל חיפוש בקצב נתונים קבוע בכל ה-extractors שתומכים בכך, משתמשים ב-DefaultExtractorsFactory.setConstantBitrateSeekingEnabled
.
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
לאחר מכן אפשר להזריק את ה-ExtractorsFactory
דרך DefaultMediaSourceFactory
, כפי שמתואר למעלה להתאמה אישית של דגלים לחילוץ נתונים.
הפעלת תורים של מאגרים אסינכרונים
תורים של מאגרים אסינכרונים הם שיפור בצינור עיבוד הנתונים של ExoPlayer, שמפעיל מכונות MediaCodec
במצב אסינכרוני ומשתמש בשרשורים נוספים לתזמון פענוח ועיבוד נתונים. כשמפעילים אותה,
יכול להיות שהפריימים מושמטים ופחות פריימים שמקורם באודיו.
התכונה 'אוסף בקשות אסינכררוני במאגר' מופעלת כברירת מחדל במכשירים עם Android 12 (רמת API 31) ואילך, וניתן להפעיל אותה באופן ידני החל מ-Android 6.0 (רמת API 23). מומלץ להפעיל את התכונה במכשירים ספציפיים שבהם נתקלתם בפריימים שנשמטו או בבעיות של אודיו חסר, במיוחד כשאתם מפעילים תוכן מוגן DRM או תוכן עם קצב פריימים גבוה.
במקרה הפשוט ביותר, צריך להחדיר DefaultRenderersFactory
לנגן באופן הבא:
Kotlin
val renderersFactory = DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing() val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()
Java
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing(); ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();
אם אתם יוצרים ישירות מרינדור, צריך להעביר AsynchronousMediaCodecAdapter.Factory
ל-constructor של MediaCodecVideoRenderer
ו-MediaCodecAudioRenderer
.
תיעוד קריאות ל-method באמצעות ForwardingPlayer
אפשר להתאים אישית חלק מההתנהגות של מופע Player
על ידי עטיפה שלו בתת-סוג של ForwardingPlayer
ושינוי מברירת המחדל של שיטות כדי לבצע אחת מהפעולות הבאות:
- פרמטרים של גישה לפני העברתם למקבל הגישה
Player
. - צריך לגשת לערך המוחזר מהגורם המבצע
Player
לפני שמחזירים אותו. - מטמיעים מחדש את השיטה.
כשמבטלים את הגדרת ברירת המחדל של שיטות ForwardingPlayer
, חשוב לוודא שההטמעה נשארת עקבית ותואמת לממשק Player
, במיוחד כשמדובר בשיטות שאמורות להיות בעלות התנהגות זהה או קשורה. לדוגמה:
- אם רוצים לשנות כל פעולת 'הפעלה', צריך לבטל את
ForwardingPlayer.play
ואתForwardingPlayer.setPlayWhenReady
, כי ה-caller יצפה שההתנהגות ב-methods האלה תהיה זהה כאשרplayWhenReady = true
. - אם רוצים לשנות את הגדלת ההתקדמות, צריך לשנות את הערך של
ForwardingPlayer.seekForward
כדי לבצע התקדמות עם הגדלה בהתאמה אישית, ואת הערך שלForwardingPlayer.getSeekForwardIncrement
כדי לדווח למבצע הקריאה החוזרת על הגדלה בהתאמה אישית נכונה. - אם רוצים לקבוע אילו
Player.Commands
יוצגו על ידי מכשיר הנגן, צריך לשנות את הגדרותPlayer.getAvailableCommands()
ו-Player.isCommandAvailable()
, וגם להאזין להודעת החזרה (callback) שלPlayer.Listener.onAvailableCommandsChanged()
כדי לקבל התראות על שינויים שמגיעים מהנגן הבסיסי.
התאמה אישית של MediaSource
הדוגמאות שלמעלה מחדירות רכיבים מותאמים אישית לשימוש במהלך ההפעלה של כל אובייקטי ה-MediaItem
שמועברים לנגן. במקרים שבהם נדרשת התאמה אישית פרטנית, אפשר גם להוסיף רכיבים מותאמים אישית למכונות MediaSource
נפרדות, שאותן אפשר להעביר ישירות לנגן. בדוגמה הבאה מוסבר איך להתאים אישית את ProgressiveMediaSource
כך שישתמש ב-DataSource.Factory
, ב-ExtractorsFactory
וב-LoadErrorHandlingPolicy
בהתאמה אישית:
Kotlin
val mediaSource = ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri))
Java
ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri));
יצירת רכיבים מותאמים אישית
הספרייה מספקת הטמעות ברירת מחדל של הרכיבים שמפורטים בחלק העליון של הדף הזה, למקרים נפוצים של שימוש. ExoPlayer
יכול להשתמש ברכיבים האלו, אבל הוא עשוי גם להיווצר לשימוש בהטמעות מותאמות אישית אם נדרשות התנהגויות לא סטנדרטיות. לפניכם מספר תרחישים לדוגמה של הטמעות בהתאמה אישית:
Renderer
– יכול להיות שתרצו להטמיעRenderer
בהתאמה אישית כדי לטפל בסוג מדיה שלא נתמך בהטמעות ברירת המחדל שסופקו על ידי הספרייה.TrackSelector
– הטמעתTrackSelector
בהתאמה אישית מאפשרת למפתחי האפליקציות לשנות את האופן שבו הטראקים שמוצגים על ידיMediaSource
נבחרים לשימוש בכל אחד מה-Renderer
הזמינים.LoadControl
– הטמעתLoadControl
בהתאמה אישית מאפשרת למפתחי האפליקציה לשנות את מדיניות האחסון במטמון של הנגן.Extractor
– אם אתם צריכים לתמוך בפורמט מאגר שאינו נתמך כרגע בספרייה, כדאי להטמיע את הכיתהExtractor
בהתאמה אישית.MediaSource
– כדאי להטמיע סוגMediaSource
מותאם אישית אם רוצים לקבל דגימות מדיה כדי להעביר למעבד גרפיקה באופן מותאם אישית, או אם רוצים להטמיע התנהגות מותאמת אישית שלMediaSource
ליצירת קומפוזיציה.MediaSource.Factory
– הטמעה שלMediaSource.Factory
בהתאמה אישית מאפשרת לאפליקציה להתאים אישית את האופן שבו נוצרMediaSource
מ-MediaItem
.DataSource
– חבילת ה-upstream של ExoPlayer כבר מכילה מספר הטמעות שלDataSource
לתרחישים לדוגמה שונים. יכול להיות שתרצו להטמיע את הכיתהDataSource
שלכם כדי לטעון נתונים בדרך אחרת, למשל באמצעות פרוטוקול מותאם אישית, באמצעות סטאק HTTP מותאם אישית או ממאגר מטמון מתמיד מותאם אישית.
כשאתם יוצרים רכיבים מותאמים אישית, מומלץ:
- אם רכיב מותאם אישית צריך לדווח על אירועים לאפליקציה, מומלץ לעשות זאת באמצעות אותו מודל שבו נעשה שימוש ברכיבי ExoPlayer קיימים. לדוגמה, אפשר להשתמש ב-classes
EventDispatcher
או להעבירHandler
יחד עם מאזין ל-constructor של הרכיב. - מומלץ להשתמש באותו מודל ברכיבים מותאמים אישית כמו ברכיבים הקיימים של ExoPlayer, כדי לאפשר לאפליקציה לבצע שינוי תצורה במהלך ההפעלה. לשם כך, צריך להטמיע את
PlayerMessage.Target
ברכיבים מותאמים אישית ולקבל שינויים בהגדרות בשיטהhandleMessage
. קוד האפליקציה צריך להעביר את שינויי ההגדרה באמצעות קריאה ל-methodcreateMessage
של ExoPlayer, הגדרת ההודעה ושליחתה לרכיב באמצעותPlayerMessage.send
. שליחת הודעות שיועברו בשרשור של ההפעלה מבטיחה שהן יבוצעו כדי לבצע פעולות אחרות בנגן.