לעיתים קרובות רצוי להפעיל מדיה כשהאפליקציה לא בחזית. עבור לדוגמה, נגן מוזיקה בדרך כלל ממשיך להשמיע מוזיקה כשהמשתמש ננעל במכשיר שלהם או משתמש באפליקציה אחרת. ספריית Media3 מספקת סדרה של ממשקים שמאפשרים לתמוך בהפעלה ברקע.
שימוש ב-MediaSessionService
כדי לאפשר הפעלה ברקע, יש לכלול את הקובץ Player
וגם
MediaSession
בתוך שירות נפרד.
הפעולה הזו מאפשרת למכשיר להמשיך להציג מדיה גם כשהאפליקציה לא נמצאת
את החזית.
כשמארחים שחקן בשירות, צריך להשתמש ב-MediaSessionService
.
כדי לעשות את זה, צריך ליצור כיתה שמרחיבה את MediaSessionService
וליצור
את סשן המדיה בתוכו.
השימוש ב-MediaSessionService
מאפשר ללקוחות חיצוניים כמו Google
Assistant, פקדי המדיה של המערכת או מכשירים נלווים כמו Wear OS יכולים לגלות
באמצעות השירות שלך, להתחבר אליו ולשלוט בהפעלה, כל זאת מבלי לגשת
על הפעילות של האפליקציה בממשק המשתמש. למעשה, יכולות להיות כמה אפליקציות לקוח מחוברות
לאותו MediaSessionService
בו-זמנית, לכל אפליקציה
MediaController
.
הטמעת מחזור החיים של השירות
צריך להטמיע שלוש שיטות למחזור החיים של השירות:
- נשלחת קריאה אל
onCreate()
כשהבקר הראשון עומד להתחבר השירות נוצר באופן מיידי ומוחל. זה המקום הטוב ביותר לבנייתPlayer
MediaSession
. - מתבצעת קריאה אל
onTaskRemoved(Intent)
כשהמשתמש סוגר את האפליקציה של המשימות האחרונות. אם ההפעלה נמשכת, האפליקציה יכולה לבחור להשאיר את השירות שפועלות בחזית. אם הנגן מושהה, השירות לא נמצא חזית ויש לעצור אותה. - מתבצעת קריאה אל
onDestroy()
כשהשירות מופסק. כל מקורות המידע צריך לשחרר את הנגן והסשן.
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() } // The user dismissed the app from the recent tasks override fun onTaskRemoved(rootIntent: Intent?) { val player = mediaSession?.player!! if (!player.playWhenReady || player.mediaItemCount == 0 || player.playbackState == Player.STATE_ENDED) { // Stop the service if not playing, continue playing in the background // otherwise. stopSelf() } } // 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(); } // The user dismissed the app from the recent tasks @Override public void onTaskRemoved(@Nullable Intent rootIntent) { Player player = mediaSession.getPlayer(); if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0 || player.getPlaybackState() == Player.STATE_ENDED) { // Stop the service if not playing, continue playing in the background // otherwise. stopSelf(); } } // 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?) { val player = mediaSession.player if (player.playWhenReady) { // Make sure the service is not in foreground. player.pause() } stopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { Player player = mediaSession.getPlayer(); if (player.getPlayWhenReady()) { // Make sure the service is not in foreground. player.pause(); } stopSelf(); }
צריך לספק גישה לסשן המדיה
כדי לתת ללקוחות אחרים גישה למדיה שלך, צריך לשנות את השיטה 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
למניפסט, ואם מטרגטים ל-API 34
מעל גם 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
.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
עליך להגדיר
foregroundServiceType
שכולל את הנתון mediaPlayback
כשהאפליקציה שלך פועלת במכשיר עם Android
10 (רמת API 29) ואילך.
שליטה בהפעלה באמצעות 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();
אפליקציות יכולות להתאים אישית את לחצני הפקודה של בקרי המדיה של Android. מידע נוסף מידע על התאמה אישית של Android Media הפקדים.
התאמה אישית של התראות
כדי להתאים אישית את ההתראה, צריך ליצור
MediaNotification.Provider
עם DefaultMediaNotificationProvider.Builder
או על ידי יצירה של ממשק ספק בהתאמה אישית. הוספה של
הספק של MediaSessionService
באמצעות
setMediaNotificationProvider
.
המשך הפעלה
לחצני מדיה הם לחצני חומרה שקיימים במכשירי Android ובציוד היקפי אחר מכשירים, למשל לחצן ההפעלה או ההשהיה באוזניות Bluetooth. מדיה3 מטפל עבורך בקלט של לחצני מדיה כשהשירות פועל.
הצהרה על המקלט של לחצן המדיה של Media3
Media3 כולל API שמאפשר למשתמשים להמשיך
הפעלה אחרי שהאפליקציה הסתיימה וגם אחרי שהמכשיר נסגר
בוצעה הפעלה מחדש. כברירת מחדל, האפשרות לחידוש ההפעלה מושבתת. כלומר, המשתמש
לא ניתן להמשיך את ההפעלה כשהשירות אינו פועל. כדי להביע הסכמה, קודם צריך לבצע
הצהרה על MediaButtonReceiver
במניפסט:
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
הטמעת קריאה חוזרת (callback) של המשך ההפעלה
כשמכשיר Bluetooth או ה-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 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 and the start position // to use here MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist(); settableFuture.set(resumptionPlaylist); }, MoreExecutors.directExecutor()); return settableFuture; }
אם שמרתם פרמטרים אחרים, כמו מהירות ההפעלה, מצב חזרה או
מצב אקראי, onPlaybackResumption()
הוא מקום טוב להגדרת הנגן
עם הפרמטרים האלה לפני ש-Media3 מכינה את הנגן ומתחילה את ההפעלה כש
הקריאה החוזרת (callback) תסתיים.
הגדרה מתקדמת של בקר ותאימות לאחור
תרחיש נפוץ הוא שימוש ב-MediaController
בממשק המשתמש של האפליקציה לצורך שליטה
הפעלה והצגת הפלייליסט. במקביל, הסשן חשוף
ללקוחות חיצוניים כמו בקרי מדיה של Android ו-Assistant בנייד או בטלוויזיה,
Wear OS לשעונים ול-Android Auto במכוניות. אפליקציית ההדגמה של הסשן של Media3
היא דוגמה לאפליקציה שמממשת תרחיש כזה.
הלקוחות החיצוניים האלה יכולים להשתמש בממשקי API כמו MediaControllerCompat
של הגרסה הקודמת
ספריית AndroidX או android.media.session.MediaController
של Android
. Media3 תואם באופן מלא לספרייה הקודמת
שמספק יכולת פעולה הדדית עם Android framework API.
שימוש בבקר ההתראות למדיה
חשוב להבין שנאמני המידע מהדור הקודם או של ה-framework הם קוראים את
אותם ערכים מהמסגרת PlaybackState.getActions()
ו
PlaybackState.getCustomActions()
. כדי לזהות פעולות ופעולות מותאמות אישית של
בסשן של ה-framework, האפליקציה יכולה להשתמש בבקר ההתראות של המדיה
ולהגדיר את הפקודות הזמינות ואת הפריסה המותאמת אישית שלו. השירות מחבר את המדיה
בקר ההתראות לסשן שלך, והסשן משתמש
ConnectionResult
הוחזר על ידי onConnect()
של הקריאה החוזרת כדי להגדיר
הפעולות והפעולות המותאמות אישית בסשן של ה-framework.
בהינתן תרחיש לניידים בלבד, אפליקציה יכולה לספק הטמעה של
MediaSession.Callback.onConnect()
כדי להגדיר פקודות זמינות
פריסה מותאמת אישית במיוחד עבור סשן ה-framework, באופן הבא:
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 layout and available commands to configure the legacy/framework session. return AcceptedResultBuilder(session) .setCustomLayout( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward)) ) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands with default custom layout 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 layout and available commands to configure the legacy/framework session. return new AcceptedResultBuilder(session) .setCustomLayout( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward))) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands without default custom layout 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 with default custom layout 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 without default custom layout for all other controllers. return new AcceptedResultBuilder(session).build(); }
לאפליקציית ההדגמה של הסשן יש מודול כלי רכב, שמוכיח תמיכה ב-Automotive OS שנדרשת לה APK נפרד.