通常,在应用不在前台运行时播放媒体内容是可取的。例如,当用户锁定设备或使用其他应用时,音乐播放器通常会继续播放音乐。Media3 库提供了一系列接口,可让您支持后台播放。
使用 MediaSessionService
如需启用后台播放,您应将 Player
和 MediaSession
包含在单独的服务中。这样一来,即使您的应用不在前台运行,设备也能继续提供媒体内容。

MediaSessionService
允许媒体会话独立于应用的 activity 运行在服务中托管玩家时,您应使用 MediaSessionService
。为此,请创建一个扩展 MediaSessionService
的类,并在其中创建媒体会话。
使用 MediaSessionService
可以让 Google 助理、系统媒体控件、外围设备上的媒体按钮或 Wear OS 等配套设备等外部客户端发现您的服务、连接到该服务并控制播放,而根本无需访问应用的界面 activity。事实上,可以有多个客户端应用同时连接到同一个 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
类,并为其指定 intent 过滤器 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
控制播放
在包含播放器界面的 activity 或 fragment 中,您可以使用 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 媒体控件。
如需进一步自定义通知本身,请使用 DefaultMediaNotificationProvider.Builder
创建 MediaNotification.Provider
,或创建提供程序接口的自定义实现。使用 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 系统界面恢复功能请求恢复播放时,系统会调用 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 准备好播放器并在回调完成时开始播放之前,再使用这些参数配置播放器。
高级控制器配置和向后兼容性
一个常见场景是在应用界面中使用 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(); }
此会话演示应用包含一个汽车模块,演示了对需要单独 APK 的 Automotive OS 的支持。