ב-Jetpack Media3 מוגדר ממשק Player
שמפרט את הפונקציונליות הבסיסית להפעלת קובצי וידאו ואודיו. ExoPlayer
הוא הטמעת ברירת המחדל של הממשק הזה ב-Media3. מומלץ להשתמש ב-ExoPlayer, כי הוא מספק מערכת מקיפה של תכונות שמכסות את רוב תרחישי ההפעלה, ואפשר להתאים אותו אישית כדי לטפל בכל תרחישי השימוש הנוספים. בנוסף, ExoPlayer מספק תכונות אבסורטקטיות שמבודדות את המכשיר ואת מערכת ההפעלה, כך שהקוד יפעל באופן עקבי בכל הסביבה של Android. ExoPlayer כולל:
- תמיכה בפלייליסטים
- תמיכה במגוון פורמטים של סטרימינג פרוגרסיבי וסטרימינג אדפטיבי
- תמיכה בהוספת מודעות גם בצד הלקוח וגם בצד השרת
- תמיכה בהפעלה שמוגנת על ידי DRM
בדף הזה נסביר על חלק מהשלבים העיקריים לפיתוח אפליקציית הפעלה. לפרטים נוספים, אפשר לעיין במדריכים המלאים שלנו בנושא Media3 ExoPlayer.
תחילת העבודה
כדי להתחיל, מוסיפים יחסי תלות למודולים ExoPlayer, UI ו-Common של Jetpack Media3:
implementation "androidx.media3:media3-exoplayer:1.4.1" implementation "androidx.media3:media3-ui:1.4.1" implementation "androidx.media3:media3-common:1.4.1"
בהתאם לתרחיש לדוגמה שלכם, ייתכן שתצטרכו גם מודולים נוספים מ-Media3, כמו exoplayer-dash
כדי להפעיל שידורים בפורמט DASH.
חשוב להחליף את 1.4.1
בגרסה המועדפת של הספרייה. אפשר לעיין בנתוני הגרסה כדי לראות מהי הגרסה האחרונה.
יצירת נגן מדיה
ב-Media3, אפשר להשתמש בהטמעה הכלולה של ממשק Player
, ExoPlayer
, או ליצור הטמעה מותאמת אישית משלכם.
יצירת ExoPlayer
הדרך הפשוטה ביותר ליצור מכונה של ExoPlayer
היא:
Kotlin
val player = ExoPlayer.Builder(context).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context).build();
אפשר ליצור את נגן המדיה בשיטה onCreate()
של מחזור החיים ב-Activity
, ב-Fragment
או ב-Service
במקום שבו הוא נמצא.
Builder
כולל מגוון אפשרויות התאמה אישית שעשויות לעניין אתכם, כמו:
setAudioAttributes()
כדי להגדיר את הטיפול במיקוד האודיוsetHandleAudioBecomingNoisy()
כדי להגדיר את התנהגות ההפעלה כשמנותק פלט אודיוsetTrackSelector()
כדי להגדיר את בחירת הטראק
Media3 מספק רכיב UI מסוג PlayerView
שאפשר לכלול בקובץ הפריסה של האפליקציה. הרכיב הזה מכיל PlayerControlView
לבקרות ההפעלה, SubtitleView
להצגת כתוביות ו-Surface
לעיבוד וידאו.
הכנת הנגן
אפשר להוסיף פריטי מדיה לפלייליסט כדי להפעיל אותם בשיטות כמו setMediaItem()
ו-addMediaItem()
.
לאחר מכן, צריך להפעיל את prepare()
כדי להתחיל לטעון את המדיה ולקבל את המשאבים הנדרשים.
אין לבצע את השלבים האלה לפני שהאפליקציה נמצאת בחזית. אם הנגן נמצא ב-Activity
או ב-Fragment
, צריך להכין את הנגן בשיטת מחזור החיים onStart()
ברמת API 24 ואילך, או בשיטת מחזור החיים onResume()
ברמת API 23 ומטה. אם השחקן נמצא ב-Service
, אפשר להכין אותו ב-onCreate()
.
שליטה בנגן
אחרי שהנגן מוכן, אפשר לשלוט בהפעלה על ידי קריאה לשיטות בנגן, כמו:
play()
ו-pause()
כדי להתחיל ולהשהות את ההפעלהseekTo()
כדי לדלג למיקום מסוים בתוך פריט המדיה הנוכחיseekToNextMediaItem()
ו-seekToPreviousMediaItem()
כדי לנווט בפלייליסט
רכיבי ממשק המשתמש, כמו PlayerView
או PlayerControlView
, יעודכנו בהתאם כשהם מקושרים לנגן.
שחרור הנגן
ההפעלה עשויה לדרוש משאבים במלאי מוגבל, כמו מקודדים של וידאו, לכן חשוב להפעיל את release()
בנגן כדי לפנות משאבים כשאין יותר צורך בנגן.
אם הנגן נמצא ב-Activity
או ב-Fragment
, צריך להשיק את הנגן בשיטת מחזור החיים onStop()
ברמת API 24 ואילך, או בשיטה onPause()
ברמת API 23 ומטה. אם השחקן נמצא ב-Service
, אפשר לשחרר אותו ב-onDestroy()
.
ניהול ההפעלה באמצעות סשן מדיה
ב-Android, סשנים של מדיה מספקים דרך סטנדרטית לאינטראקציה עם נגן מדיה מעבר לגבולות התהליך. חיבור סשן מדיה לנגן מאפשר לכם לפרסם את הפעלת המדיה שלכם באופן חיצוני ולקבל פקודות הפעלה ממקורות חיצוניים, למשל כדי לשלב עם פקדי המדיה של המערכת במכשירים ניידים ובמכשירים עם מסך גדול.
כדי להשתמש בסשנים של מדיה, מוסיפים תלות במודול Media3 Session:
implementation "androidx.media3:media3-session:1.4.1"
יצירת סשן מדיה
אפשר ליצור MediaSession
אחרי שמפעילים נגן באופן הבא:
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 מסנכרן באופן אוטומטי את המצב של Player
עם המצב של MediaSession
. אפשר לעשות את זה עם כל הטמעה של Player
, כולל ExoPlayer
, CastPlayer
או הטמעה בהתאמה אישית.
הענקת שליטה ללקוחות אחרים
אפליקציות לקוח יכולות להטמיע בקר מדיה כדי לשלוט בהפעלה של סשן המדיה. כדי לקבל את הבקשות האלה, צריך להגדיר אובייקט callback בזמן היצירה של MediaSession
.
כשהבקר עומד להתחבר לסשן המדיה, מתבצעת קריאה ל-method onConnect()
. אתם יכולים להשתמש ב-ControllerInfo
שסופק כדי להחליט אם לאשר או לדחות את הבקשה. אפשר לראות דוגמה לכך באפליקציית ההדגמה של Media3 Session.
לאחר החיבור, הבקר יכול לשלוח פקודות הפעלה לסשן. לאחר מכן, הסשן מעביר את הפקודות האלה לנגן. פקודות ההפעלה והפלייליסט שהוגדרו בממשק Player
מטופלות באופן אוטומטי על ידי הסשן.
שיטות אחרות של קריאה חוזרת מאפשרות לכם לטפל, למשל, בבקשות לפקודות הפעלה בהתאמה אישית ובשינוי הפלייליסט. באופן דומה, פונקציות ה-callbacks האלה כוללות אובייקט ControllerInfo
כדי שתוכלו לקבוע את בקרת הגישה על בסיס בקשה.
הפעלת מדיה ברקע
כדי להמשיך להפעיל מדיה כשהאפליקציה לא בחזית, למשל כדי להפעיל מוזיקה, אודיו-ספרים או פודקאסטים גם כשהאפליקציה לא פתוחה, צריך להכיל את Player
ו-MediaSession
בשירות בחזית. Media3 מספק את הממשק MediaSessionService
למטרה הזו.
הטמעת MediaSessionService
יצירת מחלקה שמרחיבה את MediaSessionService
ומייצרת מופע של MediaSession
באמצעות method onCreate()
של מחזור החיים.
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // Create your Player and MediaSession in the onCreate lifecycle event override fun onCreate() { super.onCreate() val player = ExoPlayer.Builder(this).build() mediaSession = MediaSession.Builder(this, player).build() } // Remember to release the player and media session in onDestroy override fun onDestroy() { mediaSession?.run { player.release() release() mediaSession = null } super.onDestroy() } }
Java
public class PlaybackService extends MediaSessionService { private MediaSession mediaSession = null; @Override public void onCreate() { super.onCreate(); ExoPlayer player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player).build(); } @Override public void onDestroy() { mediaSession.getPlayer().release(); mediaSession.release(); mediaSession = null; super.onDestroy(); } }
במניפסט, מגדירים את הכיתה Service
עם מסנן Intent MediaSessionService
ומבקשים את ההרשאה FOREGROUND_SERVICE
להפעיל שירות בחזית:
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
לבסוף, בכיתה שיצרתם, משנים את השיטה onGetSession()
כדי לשלוט בגישה של הלקוח לסשן המדיה. מחזירים את הערך MediaSession
כדי לאשר את בקשת החיבור, או את הערך null
כדי לדחות אותה.
Kotlin
// This example always accepts the connection request override fun onGetSession( controllerInfo: MediaSession.ControllerInfo ): MediaSession? = mediaSession
Java
@Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { // This example always accepts the connection request return mediaSession; }
חיבור לממשק המשתמש
עכשיו, כשסשן המדיה נמצא ב-Service
נפרד מה-Activity
או מה-Fragment
שבו נמצא ממשק המשתמש של הנגן, אפשר להשתמש ב-MediaController
כדי לקשר ביניהם. ב-method onStart()
של Activity
או Fragment
עם ממשק המשתמש, יוצרים SessionToken
ל-MediaSession
ואז משתמשים ב-SessionToken
כדי ליצור MediaController
. היצירה של MediaController
מתבצעת באופן אסינכרוני.
Kotlin
override fun onStart() { val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java)) val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync() controllerFuture.addListener( { // Call controllerFuture.get() to retrieve the MediaController. // MediaController implements the Player interface, so it can be // attached to the PlayerView UI component. playerView.setPlayer(controllerFuture.get()) }, MoreExecutors.directExecutor() ) }
Java
@Override public void onStart() { SessionToken sessionToken = new SessionToken(this, new ComponentName(this, PlaybackService.class)); ListenableFuture<MediaController> controllerFuture = new MediaController.Builder(this, sessionToken).buildAsync(); controllerFuture.addListener(() -> { // Call controllerFuture.get() to retrieve the MediaController. // MediaController implements the Player interface, so it can be // attached to the PlayerView UI component. playerView.setPlayer(controllerFuture.get()); }, MoreExecutors.directExecutor()) }
MediaController
מטמיע את הממשק Player
, כך שאפשר להשתמש באותן שיטות, כמו play()
ו-pause()
, כדי לשלוט בהפעלה. בדומה לרכיבים אחרים, חשוב לזכור לשחרר את MediaController
כשאין בו יותר צורך, למשל את שיטת מחזור החיים onStop()
של Activity
, על ידי קריאה ל-MediaController.releaseFuture()
.
פרסום התראה
שירותים שפועלים בחזית נדרשים לפרסם התראה בזמן הפעילות. MediaSessionService
ייצור בשבילכם התראת MediaStyle
באופן אוטומטי בפורמט MediaNotification
.
כדי לספק התראה בהתאמה אישית, אתם יכולים ליצור MediaNotification.Provider
עם DefaultMediaNotificationProvider.Builder
או ליצור הטמעה מותאמת אישית של ממשק הספק. מוסיפים את הספק MediaSession
באמצעות setMediaNotificationProvider
.
פרסום ספריית התוכן
MediaLibraryService
מבוסס על MediaSessionService
ומאפשר לאפליקציות לקוח לעיין בתוכן המדיה שסופק על ידי האפליקציה. אפליקציות לקוח מטמיעות MediaBrowser
כדי לקיים אינטראקציה עם MediaLibraryService
.
ההטמעה של MediaLibraryService
דומה להטמעה של MediaSessionService
, אבל ב-onGetSession()
צריך להחזיר MediaLibrarySession
במקום MediaSession
. בהשוואה ל-MediaSession.Callback
, ה-MediaLibrarySession.Callback
כולל שיטות נוספות שמאפשרות ללקוח דפדפן לנווט בתוכן שמוצג על ידי שירות הספרייה.
בדומה ל-MediaSessionService
, מגדירים את MediaLibraryService
במניפסט ומבקשים את ההרשאה FOREGROUND_SERVICE
כדי להריץ שירות שפועל בחזית:
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaLibraryService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
הדוגמה שלמעלה כוללת מסנן כוונה גם ל-MediaLibraryService
וגם, לצורך תאימות לאחור, ל-MediaBrowserService
הקודם. מסנן Intent הנוסף מאפשר לאפליקציות לקוח שמשתמשות ב-API MediaBrowserCompat
לזהות את Service
.
השדה MediaLibrarySession
מאפשר למלא את ספריית התוכן במבנה של עץ, עם בסיס MediaItem
אחד. לכל MediaItem
בעץ יכולים להיות כל מספר של צומתי MediaItem
צאצאים. אפשר להציג שורש אחר או עץ אחר, בהתאם לבקשה של אפליקציית הלקוח. לדוגמה, העץ שתחזירו ללקוח שמחפש רשימה של פריטי מדיה מומלצים עשוי להכיל רק את הצומת הבסיסי MediaItem
ורמה אחת של צמתים צאצאים MediaItem
, בעוד שהעץ שתחזירו לאפליקציית לקוח אחרת עשוי לייצג ספריית תוכן מלאה יותר.
יצירת MediaLibrarySession
MediaLibrarySession
מרחיב את ה-API של MediaSession
כדי להוסיף ממשקי API לגלישה בתוכן. בהשוואה ל-callback של MediaSession
, ל-callback של MediaLibrarySession
נוספות שיטות כמו:
onGetLibraryRoot()
כשלקוח מבקש את הבסיסMediaItem
של עץ תוכןonGetChildren()
כשלקוח מבקש את הצאצאים שלMediaItem
בעץ התוכןonGetSearchResult()
למקרים שבהם לקוח מבקש תוצאות חיפוש מעץ התוכן של שאילתה נתונה
שיטות קריאה חוזרת רלוונטיות יכללו אובייקט LibraryParams
עם אותות נוספים לגבי סוג עץ התוכן שבו אפליקציית הלקוח מעוניינת.