בליבת ספריית ExoPlayer נמצא ממשק Player
. Player
חושף פונקציונליות מסורתית של נגן מדיה ברמה גבוהה, כמו היכולת לבצע באפרינג למדיה, להפעיל, להשהות ולחפש. ההטמעה שמוגדרת כברירת מחדל ExoPlayer
נועדה להניח מעט הנחות לגבי סוג המדיה שמופעלת, איך ואיפה היא מאוחסנת ואיך היא מוצגת, ולכן היא מטילה מעט הגבלות על סוג המדיה שמופעלת, איך ואיפה היא מאוחסנת ואיך היא מוצגת. במקום להטמיע את הטעינה והעיבוד של מדיה ישירות, הטמעות ExoPlayer
מעבירות את העבודה הזו לרכיבים שמוזרקים כשיוצרים נגן או כשמעבירים מקורות מדיה חדשים לנגן.
הרכיבים שמשותפים לכל ההטמעות של ExoPlayer
הם:
-
MediaSource
instances שמגדירים את המדיה להפעלה, טוענים את המדיה ומאפשרים לקרוא את המדיה הטעונה. מופע שלMediaSource
נוצר מתוךMediaItem
על ידיMediaSource.Factory
בתוך הנגן. אפשר גם להעביר אותם ישירות לנגן באמצעות media source based playlist API. - מופע של
MediaSource.Factory
שממירMediaItem
ל-MediaSource
. ה-MediaSource.Factory
מוזרק כשהנגן נוצר. Renderer
מקרים שבהם רכיבים בודדים של המדיה מוצגים. הם מוזרקים כשהנגן נוצר.TrackSelector
שבוחר רצועות שסופקו על ידיMediaSource
לצריכה על ידי כלRenderer
זמין. תגTrackSelector
מוחדר כשיוצרים את נגן.-
LoadControl
שקובע מתיMediaSource
מבצע מאגר זמני של עוד מדיה, וכמה מדיה נשמרת במאגר הזמני. הערךLoadControl
מוזרק כשהשחקן נוצר. LivePlaybackSpeedControl
ששולט במהירות ההפעלה במהלך שידורים חיים כדי שהנגן יישאר קרוב לאופסט של שידור חי שהוגדר. ALivePlaybackSpeedControl
מוזרק כשהנגן נוצר.
הרעיון של הוספת רכיבים שמיישמים חלקים מהפונקציונליות של נגן קיים בספרייה כולה. ההטמעות שמוגדרות כברירת מחדל של חלק מהרכיבים מעבירות עבודה לרכיבים מוזרקים נוספים. כך אפשר להחליף הרבה רכיבי משנה ביישומים שמוגדרים בצורה מותאמת אישית.
התאמה אישית של הנגן
בהמשך מפורטות כמה דוגמאות נפוצות להתאמה אישית של הנגן באמצעות הוספת רכיבים.
הגדרת מחסנית הרשת
יש לנו דף בנושא התאמה אישית של מחסנית הרשת שבה נעשה שימוש ב-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
individual, כפי שמתואר למעלה. כדי להפעיל חיפוש קבוע של קצב העברת נתונים לכל כלי החילוץ שתומכים בו, משתמשים ב-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();
אם אתם יוצרים מופעים של רכיבי עיבוד ישירות, צריך להעביר את הערך
new DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous()
אל בנאי המחלקות
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
או העברתHandler
יחד עם מאזין לקונסטרוקטור של הרכיב. - מומלץ להשתמש באותו מודל עבור רכיבים מותאמים אישית כמו רכיבי ExoPlayer קיימים, כדי לאפשר לאפליקציה לבצע שינוי בהגדרות במהלך ההפעלה. כדי לעשות את זה, רכיבים בהתאמה אישית צריכים להטמיע את
PlayerMessage.Target
ולקבל שינויים בהגדרות בשיטהhandleMessage
. קוד האפליקציה צריך להעביר שינויים בהגדרות על ידי קריאה לשיטהcreateMessage
של ExoPlayer, הגדרת ההודעה ושליחתה לרכיב באמצעותPlayerMessage.send
. שליחת הודעות להעברה בשרשור ההפעלה מבטיחה שהן יבוצעו לפי הסדר עם כל פעולה אחרת שמבוצעת בנגן.