ליבת הספרייה של 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 לשגיאות טעינה. לדוגמה, יכול להיות שתרצו להגדיר לאפליקציה כישלון מהיר במקום ניסיונות חוזרים רבים, או להתאים אישית את הלוגיקה של ההשהיה לפני ניסיון חוזר (back-off) שמגדירה את משך ההמתנה של הנגן בין כל ניסיון חוזר. קטע הקוד הבא מראה איך ליישם לוגיקה מותאמת אישית של השהיה לפני ניסיון חוזר:
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
.
התאמה אישית של פעולות באמצעות ForwardingSimpleBasePlayer
אפשר להתאים אישית חלק מההתנהגות של מופע Player
על ידי עטיפה שלו בתת-סוג של ForwardingSimpleBasePlayer
. המחלקה הזו מאפשרת לכם ליירט 'פעולות' ספציפיות, במקום שתצטרכו להטמיע שיטות Player
ישירות. כך מובטחת התנהגות עקבית של play()
, pause()
ו-setPlayWhenReady(boolean)
, לדוגמה. הוא גם מבטיח שכל השינויים במצב מועברים בצורה נכונה למכונות Player.Listener
הרשמות. ברוב התרחישים לדוגמה של התאמה אישית, מומלץ להשתמש ב-ForwardingSimpleBasePlayer
במקום ב-ForwardingPlayer
, שמועד יותר לשגיאות, בגלל ההתחייבות ליציבות.
לדוגמה, כדי להוסיף לוגיקה מותאמת אישית כשהפעלה מתחילה או נעצרת:
Kotlin
class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) { override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady) } }
Java
class PlayerWithCustomPlay extends ForwardingSimpleBasePlayer { public PlayerWithCustomPlay(Player player) { super(player); } @Override protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady); } }
או כדי לאסור את הפקודה SEEK_TO_NEXT
(ולוודא ש-Player.seekToNext
היא פעולה ללא תוצאה):
Kotlin
class PlayerWithoutSeekToNext(player: Player) : ForwardingSimpleBasePlayer(player) { override fun getState(): State { val state = super.getState() return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build() ) .build() } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
Java
class PlayerWithoutSeekToNext extends ForwardingSimpleBasePlayer { public PlayerWithoutSeekToNext(Player player) { super(player); } @Override protected State getState() { State state = super.getState(); return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build()) .build(); } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
התאמה אישית של 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 קיימים. לדוגמה, אפשר להשתמש ב-
EventDispatcher
classes או להעבירHandler
יחד עם מאזין ל-constructor של הרכיב. - מומלץ להשתמש באותו מודל ברכיבים מותאמים אישית כמו ברכיבים הקיימים של ExoPlayer, כדי לאפשר לאפליקציה לבצע הגדרה מחדש במהלך ההפעלה. לשם כך, צריך להטמיע את
PlayerMessage.Target
ברכיבים מותאמים אישית ולקבל שינויים בהגדרות בשיטהhandleMessage
. קוד האפליקציה צריך להעביר את שינויי ההגדרה באמצעות קריאה ל-methodcreateMessage
של ExoPlayer, הגדרת ההודעה ושליחתה לרכיב באמצעותPlayerMessage.send
. שליחת הודעות להעברה בשרשור ההפעלה מבטיחה שהן יבוצעו לפי הסדר, יחד עם כל הפעולות האחרות שמבוצעות בנגן.