Jetpack Media3 定义了一个 Player 接口,其中概述了播放视频和音频文件的基本功能。ExoPlayer 是 Media3 中此接口的默认实现。我们建议使用 ExoPlayer,因为它提供了一套全面的功能,可满足大多数播放用例的需求,并且可以自定义以处理您可能遇到的任何其他用例。ExoPlayer 还可抽象化设备和操作系统碎片化问题,让您的代码在整个 Android 生态系统中保持一致的运行效果。ExoPlayer 包括:
- 支持播放列表
- 支持各种渐进式和自适应流式传输格式
- 支持客户端和服务器端广告插播
- 支持受 DRM 保护的播放
本页将引导您完成构建播放应用的一些关键步骤,如需了解更多详情,您可以参阅有关 Media3 ExoPlayer 的完整指南。
使用入门
首先,添加对 Jetpack Media3 的 ExoPlayer、界面和 Common 模块的依赖项:
implementation "androidx.media3:media3-exoplayer:1.8.0" implementation "androidx.media3:media3-ui:1.8.0" implementation "androidx.media3:media3-common:1.8.0"
根据您的使用情形,您可能还需要 Media3 中的其他模块,例如 exoplayer-dash 来播放 DASH 格式的流。
请务必将 1.8.0 替换为您偏好的库版本。您可以参阅版本说明,查看最新版本。
创建媒体播放器
借助 Media3,您可以使用 Player 接口的随附实现 ExoPlayer,也可以构建自己的自定义实现。
创建 ExoPlayer
创建 ExoPlayer 实例的最简单方法如下:
Kotlin
val player = ExoPlayer.Builder(context).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context).build();
您可以在 Activity、Fragment 或 Service 的 onCreate() 生命周期方法中创建媒体播放器。
Builder 包含您可能感兴趣的各种自定义选项,例如:
setAudioAttributes(),以配置音频焦点处理setHandleAudioBecomingNoisy()用于配置音频输出设备断开连接时的播放行为setTrackSelector()以配置轨道选择
Media3 提供了一个 PlayerView 界面组件,您可以将其添加到应用的布局文件中。此组件封装了用于播放控制的 PlayerControlView、用于显示字幕的 SubtitleView 和用于渲染视频的 Surface。
准备播放器
使用 setMediaItem() 和 addMediaItem() 等方法将媒体内容添加到播放列表中以进行播放。
然后,调用 prepare() 以开始加载媒体并获取必要的资源。
您不应在应用处于前台之前执行这些步骤。如果播放器位于 Activity 或 Fragment 中,这意味着在 API 级别 24 及更高级别上,在 onStart() 生命周期方法中准备播放器;在 API 级别 23 及更低级别上,在 onResume() 生命周期方法中准备播放器。对于处于 Service 的玩家,您可以在 onCreate() 中准备好。如需查看如何实现生命周期方法的示例,请参阅 Exoplayer Codelab。
控制播放器
播放器准备就绪后,您可以通过调用播放器上的方法(例如以下方法)来控制播放:
play()和pause()可开始和暂停播放seekTo()可在当前媒体项内寻找某个位置seekToNextMediaItem()和seekToPreviousMediaItem()浏览播放列表
当绑定到播放器时,PlayerView 或 PlayerControlView 等界面组件会相应地更新。
释放播放器
播放可能需要有限的资源,例如视频解码器,因此当不再需要播放器时,请务必在播放器上调用 release() 以释放资源。
如果播放器处于 Activity 或 Fragment 中,请在 API 级别 24 及更高级别的 onStop() 生命周期方法或 API 级别 23 及更低级别的 onPause() 方法中释放播放器。对于处于 Service 状态的球员,您可以在 onDestroy() 中将其解约。如需查看如何实现生命周期方法的示例,请参阅 Exoplayer Codelab。
使用媒体会话管理播放
在 Android 上,媒体会话提供了一种标准化方式,用于跨进程边界与媒体播放器互动。将媒体会话连接到播放器后,您就可以在外部宣传媒体播放,并接收来自外部来源的播放命令,例如与移动设备和大屏设备上的系统媒体控件集成。
如需使用媒体会话,请添加对 Media3 会话模块的依赖项:
implementation "androidx.media3:media3-session:1.8.0"
创建媒体会话
您可以在初始化播放器后创建 MediaSession,如下所示:
Kotlin
val player = ExoPlayer.Builder(context).build() val mediaSession = MediaSession.Builder(context, player).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context).build(); MediaSession mediaSession = new MediaSession.Builder(context, player).build();
Media3 会自动将 Player 的状态与 MediaSession 的状态同步。这适用于任何 Player 实现,包括 ExoPlayer、CastPlayer 或自定义实现。
向其他客户端授予控制权
客户端应用可以实现媒体控制器来控制媒体会话的播放。如需接收这些请求,请在构建 MediaSession 时设置 回调对象。
当控制器即将连接到您的媒体会话时,系统会调用 onConnect() 方法。您可以使用提供的 ControllerInfo 来决定是接受还是拒绝请求。您可以在 Media3 会话演示应用中查看相关示例。
连接后,控制器可以向会话发送播放命令。然后,会话将这些命令委托给播放器。会话会自动处理 Player 接口中定义的播放和播放列表命令。
其他回调方法可让您处理自定义播放命令请求和修改播放列表等操作。这些回调同样包含 ControllerInfo 对象,因此您可以根据具体请求来确定访问权限控制。
在后台播放媒体内容
为了在应用不在前台时继续播放媒体内容(例如,即使在用户未打开应用的情况下,也能播放音乐、有声读物或播客),您的 Player 和 MediaSession 应封装在前台服务中。Media3 提供了 MediaSessionService 接口来实现此目的。
实现 MediaSessionService
创建一个扩展 MediaSessionService 的类,并在 onCreate() 生命周期方法中实例化 MediaSession。
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // Create your Player and MediaSession 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; @Override public void onCreate() { super.onCreate(); ExoPlayer player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player).build(); } @Override public void onDestroy() { mediaSession.getPlayer().release(); mediaSession.release(); mediaSession = null; super.onDestroy(); } }
在清单中,添加 Service 类并添加 MediaSessionService intent 过滤器,然后请求 FOREGROUND_SERVICE 权限以运行前台服务:
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
最后,在您创建的类中,替换 onGetSession() 方法以控制客户端对媒体会话的访问。返回 MediaSession 以接受连接请求,或返回 null 以拒绝请求。
Kotlin
// This example always accepts the connection request override fun onGetSession( controllerInfo: MediaSession.ControllerInfo ): MediaSession? = mediaSession
Java
@Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { // This example always accepts the connection request return mediaSession; }
连接到界面
现在,您的媒体会话位于与播放器界面所在的 Activity 或 Fragment 不同的 Service 中,您可以使用 MediaController 将它们关联起来。在具有界面的 Activity 或 Fragment 的 onStart() 方法中,为 MediaSession 创建 SessionToken,然后使用 SessionToken 构建 MediaController。构建 MediaController 是异步进行的。
Kotlin
override fun onStart() { val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java)) val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync() controllerFuture.addListener( { // Call controllerFuture.get() to retrieve the MediaController. // MediaController implements the Player interface, so it can be // attached to the PlayerView UI component. playerView.setPlayer(controllerFuture.get()) }, MoreExecutors.directExecutor() ) }
Java
@Override public void onStart() { SessionToken sessionToken = new SessionToken(this, new ComponentName(this, PlaybackService.class)); ListenableFuture<MediaController> controllerFuture = new MediaController.Builder(this, sessionToken).buildAsync(); controllerFuture.addListener(() -> { // Call controllerFuture.get() to retrieve the MediaController. // MediaController implements the Player interface, so it can be // attached to the PlayerView UI component. playerView.setPlayer(controllerFuture.get()); }, MoreExecutors.directExecutor()) }
MediaController 实现 Player 接口,因此您可以使用相同的方法(例如 play() 和 pause())来控制播放。与其他组件类似,请记得在不再需要 MediaController 时释放它,例如通过调用 MediaController.releaseFuture() 来释放 Activity 的 onStop() 生命周期方法。
发布通知
前台服务在处于活动状态时必须发布通知。MediaSessionService 会自动为您创建 MediaStyle 通知,以 MediaNotification 的形式显示。
如需提供自定义通知,请创建具有 DefaultMediaNotificationProvider.Builder 的 MediaNotification.Provider,或创建提供器接口的自定义实现。使用 setMediaNotificationProvider 将提供商添加到 MediaSession。
宣传您的内容库
MediaLibraryService 基于 MediaSessionService 构建,允许客户端应用浏览您的应用提供的媒体内容。客户端应用实现 MediaBrowser 以与您的 MediaLibraryService 进行交互。
实现 MediaLibraryService 与实现 MediaSessionService 类似,只不过在 onGetSession() 中,您应返回 MediaLibrarySession 而不是 MediaSession。与 MediaSession.Callback 相比,MediaLibrarySession.Callback 包含其他方法,可让浏览器客户端浏览您的库服务提供的内容。
与 MediaSessionService 类似,在清单中声明 MediaLibraryService 并请求 FOREGROUND_SERVICE 权限以运行前台服务:
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaLibraryService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
上述示例包含 MediaLibraryService 的 intent 过滤器,以及为了实现向后兼容性而包含的旧版 MediaBrowserService。额外的 intent 过滤器可让使用 MediaBrowserCompat API 的客户端应用识别您的 Service。
借助 MediaLibrarySession,您可以采用树状结构(具有单个根 MediaItem)提供内容库。树中的每个 MediaItem 都可以有任意数量的子级 MediaItem 节点。您可以根据客户端应用的要求提供不同的根或不同的树。例如,您向寻找推荐媒体项列表的客户端返回的树可能只包含根 MediaItem 和单层子级 MediaItem 节点,而您向其他客户端应用返回的树可能表示更完整的内容库。
创建 MediaLibrarySession
MediaLibrarySession 扩展了 MediaSession API 以添加内容浏览 API。与 MediaSession 回调相比,MediaLibrarySession 回调添加了以下方法:
onGetLibraryRoot(),用于在客户端请求内容树的根MediaItem时onGetChildren()用于在客户端请求内容树中MediaItem的子项时onGetSearchResult()用于在客户端请求内容树中针对给定查询的搜索结果时
相关回调方法将包含一个 LibraryParams 对象,其中包含有关客户端应用感兴趣的内容树类型的其他信号。