ב-Jetpack Media3 מוגדר ממשק Player שמפרט את הפונקציונליות הבסיסית להפעלה של קובצי וידאו ואודיו. ExoPlayer היא הטמעת ברירת המחדל של הממשק הזה ב-Media3. מומלץ להשתמש ב-ExoPlayer, כי הוא מספק קבוצה מקיפה של תכונות שמכסות את רוב תרחישי השימוש בהפעלה, ואפשר להתאים אותו אישית כדי לטפל בתרחישי שימוש נוספים. בנוסף, ExoPlayer מסתיר את הפרגמנטציה של המכשיר ומערכת ההפעלה, כך שהקוד פועל באופן עקבי בכל הסביבה העסקית של Android. ExoPlayer כולל:
- תמיכה בפלייליסטים
- תמיכה במגוון פורמטים של סטרימינג פרוגרסיבי ואדפטיבי
- תמיכה בהוספת מודעות גם בצד הלקוח וגם בצד השרת
- תמיכה בהפעלה מוגנת באמצעות DRM
בדף הזה נסביר על כמה מהשלבים העיקריים בפיתוח אפליקציה להפעלת מדיה. לפרטים נוספים, אתם יכולים לעיין במדריכים המלאים שלנו בנושא Media3 ExoPlayer.
תחילת העבודה
כדי להתחיל, מוסיפים תלות במודולים ExoPlayer, UI ו-Common של Jetpack Media3:
Kotlin
implementation("androidx.media3:media3-exoplayer:1.11.1")
implementation("androidx.media3:media3-ui:1.11.1")
implementation("androidx.media3:media3-common:1.11.1")
מגניב
implementation "androidx.media3:media3-exoplayer:1.11.1"
implementation "androidx.media3:media3-ui:1.11.1"
implementation "androidx.media3:media3-common:1.11.1"
בהתאם לתרחיש לדוגמה, יכול להיות שתצטרכו גם מודולים נוספים מ-Media3, כמו exoplayer-dash כדי להפעיל סטרימינג בפורמט DASH.
חשוב להחליף את 1.11.1 בגרסה הרצויה של הספרייה. בנתוני הגרסה אפשר לראות את הגרסה האחרונה.
יצירת נגן מדיה
ב-Media3, אתם יכולים להשתמש בהטמעה הכלולה של ממשק Player, ExoPlayer, או ליצור הטמעה בהתאמה אישית.
יצירת ExoPlayer
הדרך הפשוטה ביותר ליצור מופע של ExoPlayer היא:
Kotlin
val player = ExoPlayer.Builder(context).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context).build();
אתם יכולים ליצור את נגן המדיה ב-method של מחזור החיים onCreate() של Activity, Fragment או Service שבו הוא נמצא.
Builder כולל מגוון אפשרויות להתאמה אישית, למשל:
setAudioAttributes()כדי להגדיר את הטיפול במיקוד האודיוsetHandleAudioBecomingNoisy()כדי להגדיר את אופן ההפעלה כשמכשיר להשמעת אודיו מתנתקsetTrackSelector()כדי להגדיר בחירת מסלול
Media3 מספק רכיב ממשק משתמש של PlayerView שאפשר לכלול בקובץ הפריסה של האפליקציה. הרכיב הזה כולל PlayerControlView לרכיבי ה-UI להפעלה, SubtitleView להצגת כתוביות ו-Surface לעיבוד הסרטון.
הנגן בהכנה
מוסיפים קובצי מדיה לפלייליסט להפעלה באמצעות שיטות כמו setMediaItem() ו-addMediaItem(). לאחר מכן, קוראים ל-prepare() כדי להתחיל לטעון את המדיה ולקבל את המשאבים הנדרשים.
אל תבצעו את השלבים האלה לפני שהאפליקציה נמצאת בחזית. אם הנגן נמצא ב-Activity או ב-Fragment, צריך להכין את הנגן ב-method של מחזור החיים onStart() ברמת API 24 ומעלה, או ב-method של מחזור החיים onResume() ברמת API 23 ומטה. אם הנגן נמצא ב-Service,
אפשר להכין אותו ב-onCreate(). ב-Codelab של Exoplayer מופיעה דוגמה להטמעה של שיטות מחזור חיים.
שליטה בנגן
אחרי שהנגן מוכן, אפשר לשלוט בהפעלה באמצעות קריאה לשיטות בנגן, כמו:
-
play()ו-pause()כדי להתחיל ולהשהות את ההפעלה -
seekTo()כדי לדלג למיקום בתוך קובץ המדיה הנוכחי seekToNextMediaItem()ו-seekToPreviousMediaItem()כדי לנווט בפלייליסט
רכיבים של ממשק המשתמש כמו PlayerView או PlayerControlView יתעדכנו בהתאם כשהם מקושרים לנגן.
שחרור השחקן
ההפעלה עשויה לדרוש משאבים שזמינים בכמות מוגבלת, כמו מפענחי וידאו, ולכן חשוב להפעיל את release() בנגן כדי לפנות משאבים כשכבר לא צריך את הנגן.
אם הנגן נמצא ב-Activity או ב-Fragment, צריך לשחרר את הנגן ב-method של מחזור החיים onStop() ברמת API 24 ומעלה, או ב-method onPause() ברמת API 23 ומטה. אם השחקן נמצא בService, אפשר לשחרר אותו בonDestroy(). ב-Codelab של Exoplayer מוסבר איך להטמיע שיטות של מחזור חיים.
ניהול ההפעלה באמצעות סשן מדיה
ב-Android, סשנים של מדיה מספקים דרך סטנדרטית לאינטראקציה עם נגן מדיה מעבר לגבולות התהליך. חיבור של סשן מדיה לנגן מאפשר לכם לפרסם את הפעלת המדיה באופן חיצוני ולקבל פקודות הפעלה ממקורות חיצוניים, למשל כדי לשלב עם ממשק השליטה במדיה של המערכת בניידים ובמכשירים עם מסך גדול.
כדי להשתמש בסשנים של מדיה, צריך להוסיף תלות במודול Media3 Session:
Kotlin
implementation("androidx.media3:media3-session:1.11.1")
מגניב
implementation "androidx.media3:media3-session:1.11.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 מטופלות באופן אוטומטי בסשן.
שיטות אחרות של קריאה חוזרת מאפשרות לכם לטפל, למשל, בבקשות לפקודות הפעלה בהתאמה אישית ובשינוי הפלייליסט. באופן דומה, פונקציות ה-callback האלה כוללות אובייקט ControllerInfo, כדי שתוכלו לקבוע את בקרת הגישה לכל בקשה בנפרד.
הפעלת מדיה ברקע
כדי להמשיך להפעיל מדיה כשהאפליקציה לא פועלת בחזית, למשל כדי להפעיל מוזיקה, ספרי אודיו או פודקאסטים גם כשהמשתמש לא פתח את האפליקציה, צריך להכיל את Player ו-MediaSession בשירות שפועל בחזית. למטרה הזו, Media3 מספק את הממשק MediaSessionService.
הטמעת MediaSessionService
יוצרים מחלקה שמרחיבה את MediaSessionService ויוצרים מופע של MediaSession בשיטת מחזור החיים 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" />
הדוגמה שלמעלה כוללת מסנן Intent גם ל-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 עם אותות נוספים לגבי סוג עץ התוכן שאפליקציית הלקוח מעוניינת בו.