לפעמים רוצים להפעיל מדיה כשהאפליקציה לא בחזית. לדוגמה, נגן מוזיקה בדרך כלל ממשיך להפעיל מוזיקה כשהמשתמש נועל את המכשיר או משתמש באפליקציה אחרת. ספריית Media3 מספקת סדרה של ממשקי API שמאפשרים תמיכה בהפעלה ברקע.
שימוש ב-MediaSessionService
כדי להפעיל הפעלה ברקע, צריך להוסיף את Player
ואת MediaSession
לService נפרד.
ההרשאה הזו מאפשרת למכשיר להמשיך להפעיל מדיה גם כשהאפליקציה לא בחזית.

MediaSessionService
מאפשר להפעיל את פעילות המדיה בנפרד מהפעילות של האפליקציהכשמארחים נגן בתוך שירות, צריך להשתמש ב-MediaSessionService
.
כדי לעשות את זה, יוצרים מחלקה שמרחיבה את MediaSessionService
ויוצרים בתוכה את סשן המדיה.
השימוש ב-MediaSessionService
מאפשר ללקוחות חיצוניים כמו Google Assistant, אמצעי בקרה של מדיה במערכת, לחצני מדיה במכשירים היקפיים או במכשירים נלווים כמו WearOS לגלות את השירות שלכם, להתחבר אליו ולשלוט בהפעלה, בלי לגשת לפעילות בממשק המשתמש של האפליקציה. למעשה, יכולות להיות כמה אפליקציות לקוח שמחוברות לאותו MediaSessionService
באותו הזמן, וכל אפליקציה מקבלת MediaController
משלה.
הטמעה של מחזור החיים של השירות
צריך להטמיע שתי שיטות מחזור חיים של השירות:
-
onCreate()
נקראת כשהבקר הראשון עומד להתחבר והשירות מופעל. זה המקום הכי טוב ליצירתPlayer
וMediaSession
. -
onDestroy()
נקראת כשהשירות מופסק. צריך לשחרר את כל המשאבים, כולל הנגן והסשן.
אפשר גם לשנות את onTaskRemoved(Intent)
כדי להתאים אישית את מה שקורה כשהמשתמש סוגר את האפליקציה מהמשימות האחרונות. כברירת מחדל, השירות ממשיך לפעול אם ההפעלה נמשכת, ומופסק בכל מקרה אחר.
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // Create your player and media session 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; // Create your Player and MediaSession in the onCreate lifecycle event @Override public void onCreate() { super.onCreate(); ExoPlayer player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player).build(); } // Remember to release the player and media session in onDestroy @Override public void onDestroy() { mediaSession.getPlayer().release(); mediaSession.release(); mediaSession = null; super.onDestroy(); } }
כחלופה להמשך ההפעלה ברקע, אפשר להפסיק את השירות בכל מקרה שבו המשתמש סוגר את האפליקציה:
Kotlin
override fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
בכל הטמעה ידנית אחרת של onTaskRemoved
, אפשר להשתמש ב-isPlaybackOngoing()
כדי לבדוק אם ההפעלה נחשבת להפעלה מתמשכת והשירות של האפליקציה בחזית הופעל.
מתן גישה להפעלת המדיה
מחליפים את ה-method onGetSession()
כדי לתת ללקוחות אחרים גישה להפעלת המדיה שנבנתה כשנוצר השירות.
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // [...] lifecycle methods omitted override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
public class PlaybackService extends MediaSessionService { private MediaSession mediaSession = null; // [...] lifecycle methods omitted @Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { return mediaSession; } }
הצהרה על השירות בקובץ המניפסט
אפליקציה צריכה את ההרשאות FOREGROUND_SERVICE
ו-FOREGROUND_SERVICE_MEDIA_PLAYBACK
כדי להפעיל שירות הפעלה שפועל בחזית:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
צריך גם להצהיר על המחלקה Service
במניפסט עם מסנן Intent של MediaSessionService
ועם foregroundServiceType
שכולל mediaPlayback
.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
שליטה בהפעלה באמצעות MediaController
בפעילות או בקטע שמכילים את ממשק המשתמש של הנגן, אפשר ליצור קישור בין ממשק המשתמש לבין סשן המדיה באמצעות MediaController
. ממשק המשתמש משתמש בבקר המדיה כדי לשלוח פקודות מממשק המשתמש לנגן בתוך הסשן. במדריך בנושא יצירת MediaController
מוסבר איך ליצור MediaController
ולהשתמש בו.
איך משתמשים בפקודות MediaController
ה-MediaSession
מקבל פקודות מהבקר דרך MediaSession.Callback
. הפעלת MediaSession
יוצרת הטמעה של MediaSession.Callback
כברירת מחדל, שמטפלת באופן אוטומטי בכל הפקודות ש-MediaController
שולח לנגן.
התראה
מערכת MediaSessionService
יוצרת בשבילכם באופן אוטומטי MediaNotification
, שאמור לפעול ברוב המקרים. כברירת מחדל, ההתראה שמתפרסמת היא התראה של MediaStyle
שמתעדכנת כל הזמן עם המידע העדכני מהסשן של המדיה ומציגה את אמצעי הבקרה של ההפעלה. ה-MediaNotification
מודע לסשן שלכם, ואפשר להשתמש בו כדי לשלוט בהפעלה של כל אפליקציה אחרת שמחוברת לאותו סשן.
לדוגמה, אפליקציית סטרימינג של מוזיקה שמשתמשת ב-MediaSessionService
תיצור MediaNotification
שמציג את הכותרת, האומן ועטיפת האלבום של פריט המדיה הנוכחי שמופעל, לצד אמצעי בקרה להפעלה שמבוססים על ההגדרה של MediaSession
.
אפשר לספק את המטא-נתונים הנדרשים במדיה או להצהיר עליהם כחלק מפריט המדיה, כמו בקטע הקוד הבא:
Kotlin
val mediaItem = MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build() ) .build() mediaController.setMediaItem(mediaItem) mediaController.prepare() mediaController.play()
Java
MediaItem mediaItem = new MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( new MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build()) .build(); mediaController.setMediaItem(mediaItem); mediaController.prepare(); mediaController.play();
מחזור החיים של ההתראות
ההתראה נוצרת ברגע שיש Player
מופעים של MediaItem
בפלייליסט.
כל העדכונים של ההתראות מתבצעים באופן אוטומטי על סמך המצב של Player
ושל MediaSession
.
אי אפשר להסיר את ההתראה בזמן שהשירות בחזית פועל. כדי להסיר את ההתראה באופן מיידי, צריך להתקשר אל Player.release()
או לנקות את הפלייליסט באמצעות Player.clearMediaItems()
.
אם ההפעלה של הנגן מושהית, מופסקת או נכשלת למשך יותר מ-10 דקות בלי אינטראקציות נוספות של המשתמש, השירות עובר אוטומטית ממצב של שירות בחזית למצב שבו המערכת יכולה להרוס אותו. אתם יכולים להטמיע הפעלה חוזרת של תוכן כדי לאפשר למשתמש להפעיל מחדש את מחזור החיים של השירות ולהמשיך את ההפעלה בנקודה מאוחרת יותר בזמן.
התאמה אישית של התראות
אפשר לשנות את המטא-נתונים של הפריט שמופעל כרגע על ידי שינוי של MediaItem.MediaMetadata
. אם רוצים לעדכן את המטא-נתונים של פריט קיים, אפשר להשתמש ב-Player.replaceMediaItem
כדי לעדכן את המטא-נתונים בלי להפריע להפעלה.
אפשר גם להתאים אישית חלק מהלחצנים שמוצגים בהתראה על ידי הגדרת העדפות מותאמות אישית של לחצני מדיה עבור אמצעי הבקרה של המדיה ב-Android. מידע נוסף על התאמה אישית של אמצעי הבקרה על המדיה ב-Android
כדי להתאים אישית עוד יותר את ההתראה עצמה, יוצרים MediaNotification.Provider
עם DefaultMediaNotificationProvider.Builder
או על ידי יצירת הטמעה מותאמת אישית של ממשק הספק. מוסיפים את הספק ל-MediaSessionService
באמצעות setMediaNotificationProvider
.
המשך הפעלה
אחרי ש-MediaSessionService
מסתיים, ואפילו אחרי שהמכשיר מופעל מחדש, אפשר להציע למשתמשים להמשיך את ההפעלה כדי שהם יוכלו להפעיל מחדש את השירות ולהמשיך את ההפעלה מהמקום שבו הם הפסיקו. כברירת מחדל, האפשרות להמשך הפעלה מושבתת. כלומר, המשתמש לא יכול להמשיך את ההפעלה כשהשירות לא פועל. כדי להביע הסכמה לשימוש בתכונה הזו, צריך להצהיר על מקלט לחצני מדיה ולהטמיע את השיטה onPlaybackResumption
.
הצהרה על מקלט כפתורי המדיה של Media3
כדי להתחיל, צריך להצהיר על MediaButtonReceiver
במניפסט:
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
הטמעה של קריאה חוזרת להמשך ההפעלה
כשמכשיר Bluetooth או תכונת ההפעלה מחדש של ממשק המשתמש של מערכת Android מבקשים להמשיך את ההפעלה, מתבצעת קריאה לשיטת הקריאה החוזרת onPlaybackResumption()
.
Kotlin
override fun onPlaybackResumption( mediaSession: MediaSession, controller: ControllerInfo ): ListenableFuture<MediaItemsWithStartPosition> { val settable = SettableFuture.create<MediaItemsWithStartPosition>() scope.launch { // Your app is responsible for storing the playlist, metadata (like title // and artwork) of the current item and the start position to use here. val resumptionPlaylist = restorePlaylist() settable.set(resumptionPlaylist) } return settable }
Java
@Override public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption( MediaSession mediaSession, ControllerInfo controller ) { SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create(); settableFuture.addListener(() -> { // Your app is responsible for storing the playlist, metadata (like title // and artwork) of the current item and the start position to use here. MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist(); settableFuture.set(resumptionPlaylist); }, MoreExecutors.directExecutor()); return settableFuture; }
אם שמרתם פרמטרים אחרים כמו מהירות הפעלה, מצב חזרה או מצב ערבוב, onPlaybackResumption()
הוא מקום טוב להגדיר את הנגן עם הפרמטרים האלה לפני ש-Media3 מכין את הנגן ומתחיל את ההפעלה כשהקריאה החוזרת מסתיימת.
המערכת קוראת לשיטה הזו במהלך ההפעלה כדי ליצור את ההתראה על חידוש ממשק המשתמש של מערכת Android אחרי הפעלה מחדש של המכשיר. כדי ליצור התראה עשירה, מומלץ למלא את השדות MediaMetadata
כמו title
ו-artworkData
או artworkUri
של הפריט הנוכחי בערכים שזמינים באופן מקומי, כי יכול להיות שגישת הרשת עדיין לא זמינה. אפשר גם להוסיף את התגים MediaConstants.EXTRAS_KEY_COMPLETION_STATUS
ו-MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE
ל-MediaMetadata.extras
כדי לציין את מיקום ההפעלה לחידוש.
הגדרה מתקדמת של בקר ותאימות לאחור
תרחיש נפוץ הוא שימוש ב-MediaController
בממשק המשתמש של האפליקציה כדי לשלוט בהשמעה ולהציג את רשימת ההשמעה. במקביל, הסשן חשוף ללקוחות חיצוניים כמו אמצעי הבקרה של מדיה ב-Android ו-Assistant בנייד או בטלוויזיה, Wear OS בשעונים ו-Android Auto במכוניות. אפליקציית ההדגמה של סשן Media3 היא דוגמה לאפליקציה שמטמיעה תרחיש כזה.
יכול להיות שהלקוחות החיצוניים האלה משתמשים בממשקי API כמו MediaControllerCompat
של ספריית AndroidX מדור קודם או android.media.session.MediaController
של פלטפורמת Android. Media3 תואמת לאחור באופן מלא לספרייה הקודמת ומספקת יכולת פעולה הדדית עם Android platform API.
שימוש בבקר של התראות מדיה
חשוב להבין שלבקרים מדור קודם ולבקרי פלטפורמה יש מצב משותף, ואי אפשר להתאים אישית את הנראות לפי בקר (לדוגמה, PlaybackState.getActions()
ו-PlaybackState.getCustomActions()
). אפשר להשתמש בבקר של התראות מדיה כדי להגדיר את המצב שמוגדר בסשן המדיה של הפלטפורמה לצורך תאימות לבקרים מדור קודם ולבקרי פלטפורמה.
לדוגמה, אפליקציה יכולה לספק הטמעה של MediaSession.Callback.onConnect()
כדי להגדיר פקודות זמינות והעדפות של לחצני מדיה באופן ספציפי לסשן הפלטפורמה, באופן הבא:
Kotlin
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { if (session.isMediaNotificationController(controller)) { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build() val playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build() // Custom button preferences and commands to configure the platform session. return AcceptedResultBuilder(session) .setMediaButtonPreferences( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward)) ) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands with default button preferences for all other controllers. return AcceptedResultBuilder(session).build() }
Java
@Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { if (session.isMediaNotificationController(controller)) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build(); Player.Commands playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS .buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build(); // Custom button preferences and commands to configure the platform session. return new AcceptedResultBuilder(session) .setMediaButtonPreferences( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward))) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands with default button preferences for all other controllers. return new AcceptedResultBuilder(session).build(); }
איך מאשרים ל-Android Auto לשלוח פקודות מותאמות אישית
כשמשתמשים ב-MediaLibraryService
כדי לתמוך ב-Android Auto באמצעות האפליקציה לנייד, בקר Android Auto צריך פקודות מתאימות שזמינות, אחרת Media3 ידחה פקודות מותאמות אישית נכנסות מהבקר הזה:
Kotlin
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { val sessionCommands = ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build() if (session.isMediaNotificationController(controller)) { // [...] See above. } else if (session.isAutoCompanionController(controller)) { // Available session commands to accept incoming custom commands from Auto. return AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands for all other controllers. return AcceptedResultBuilder(session).build() }
Java
@Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build(); if (session.isMediaNotificationController(controller)) { // [...] See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return new AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands for all other controllers. return new AcceptedResultBuilder(session).build(); }
לאפליקציית ההדגמה של הסשן יש מודול לרכב שמדגים תמיכה ב-Automotive OS, שנדרש לה קובץ APK נפרד.