應用程式不在前景時,通常會需要播放媒體。舉例來說,使用者鎖定裝置或使用其他應用程式時,音樂播放器通常會繼續播放音樂。Media3 程式庫提供一系列介面,可讓您支援背景播放功能。
使用 MediaSessionService
如要啟用背景播放功能,應在獨立的 Service 中包含 Player
和 MediaSession
。即使應用程式不在前景,裝置仍可繼續提供媒體服務。

MediaSessionService
可讓媒體工作階段與應用程式活動分開執行在服務中代管玩家時,您應使用 MediaSessionService
。如要這麼做,請建立擴充 MediaSessionService
的類別,並在其中建立媒體工作階段。
使用 MediaSessionService
,Google 助理、系統媒體控制項、週邊裝置上的媒體按鈕或 Wear OS 等隨附裝置等外部用戶端就能探索您的服務、連線至服務,以及控制播放作業,完全不必存取應用程式的 UI 活動。事實上,多個用戶端應用程式可以同時連線至同一個 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()
檢查播放作業是否正在進行,以及前景服務是否已啟動。
提供媒體工作階段的存取權
覆寫 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
類別,並使用 MediaSessionService
的意圖篩選器和包含 mediaPlayback
的 foregroundServiceType
。
<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
控制播放功能
在含有播放器 UI 的 Activity 或 Fragment 中,您可以使用 MediaController
建立 UI 與媒體工作階段之間的連結。UI 會使用媒體控制器,將指令從 UI 傳送至工作階段中的播放器。如要進一步瞭解如何建立及使用 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
,或建立提供者介面的自訂實作項目。使用 setMediaNotificationProvider
,將供應商新增至 MediaSessionService
。
繼續播放
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>
實作繼續播放回呼
當藍牙裝置或 Android 系統 UI 的繼續播放功能要求繼續播放時,系統會呼叫 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 系統 UI 繼續通知。如要顯示豐富通知,建議您使用本地可用的值填入目前項目的 MediaMetadata
欄位,例如 title
和 artworkData
或 artworkUri
,因為網路存取可能尚未開放。您也可以在 MediaMetadata.extras
中加入 MediaConstants.EXTRAS_KEY_COMPLETION_STATUS
和 MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE
,指出要從哪個位置繼續播放。
進階控制器設定和回溯相容性
常見情境是在應用程式 UI 中使用 MediaController
控制播放功能及顯示播放清單。同時,工作階段會向外部用戶端公開,例如行動裝置或電視上的 Android 媒體控制項和 Google 助理、手錶上的 Wear OS,以及車輛上的 Android Auto。Media3 工作階段試用版應用程式就是實作這類情境的應用程式範例。
這些外部用戶端可能會使用舊版 AndroidX 程式庫的 MediaControllerCompat
或 Android 平台的 android.media.session.MediaController
等 API。Media3 完全回溯相容於舊版程式庫,並與 Android 平台 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。