人们通常希望在应用不在前台时播放媒体。对于 例如,如果用户锁定了设备,音乐播放器通常会继续播放音乐 或使用其他应用Media3 库提供了一系列 可让您支持后台播放功能的接口。
使用 MediaSessionService
如需启用后台播放功能,应包含 Player
和
MediaSession
位于单独的 Service 中。
这样,即使您的应用未打开,设备也能继续传送媒体内容
前景。
在 Service 中托管播放器时,您应使用 MediaSessionService
。
为此,请创建一个扩展 MediaSessionService
的类,并
其中包含媒体会话
使用 MediaSessionService
可允许 Google 等外部客户端
支持 Google 助理、系统媒体控件或 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" />
您还必须使用 intent 过滤器在清单中声明 Service
类
共 MediaSessionService
个。
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
您必须定义
foregroundServiceType
(当您的应用在搭载 Android 的设备上运行时,此属性包含 mediaPlayback
)
10(API 级别 29)及更高版本。
使用 MediaController
控制播放
在包含播放器界面的 activity 或 fragment 中,您可以与
使用 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 媒体 控件。
通知自定义
要自定义通知,请创建一个
MediaNotification.Provider
与DefaultMediaNotificationProvider.Builder
共享
也可以创建提供程序接口的自定义实现添加您的
将提供商与您的 MediaSessionService
相关联,具体如下:
setMediaNotificationProvider
。
继续播放
媒体按钮是 Android 设备和其他外围设备上的硬件按钮 设备(例如蓝牙耳机上的播放或暂停按钮)。媒体 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>
实现继续播放回调
当蓝牙设备或
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 会话演示应用
是实现此类场景的应用示例。
这些外部客户端可能会使用旧版的 MediaControllerCompat
AndroidX 库或 Android 的 android.media.session.MediaController
框架。Media3 完全向后兼容旧版库
提供与 Android 框架 API 的互操作性。
使用媒体通知控制器
请务必注意,这些旧版控制器或框架控制器
框架 PlaybackState.getActions()
中的相同值
PlaybackState.getCustomActions()
。要确定
框架会话,那么应用可以使用媒体通知控制器
并设置其可用命令和自定义布局。该服务将媒体
通知控制器发送到您的会话,并且该会话使用
由回调的 onConnect()
返回的 ConnectionResult
来配置
操作和自定义操作。
鉴于仅针对移动设备的情况,应用可以提供
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 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
需要相应的可用命令,否则 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(); }
会话演示应用具有 汽车模块, ,用于证明对需要单独 APK 的 Automotive OS 的支持。