Jetpack Media3 定义了一个 Player 接口,其中概述了播放视频和音频文件的基本功能。ExoPlayer 是 Media3 中此接口的默认实现。我们建议使用 ExoPlayer,因为它提供了一套全面的功能,可满足大多数播放使用情形,并且可以自定义以处理您可能遇到的任何其他使用情形。ExoPlayer 还可抽象化设备和操作系统碎片化问题,让您的代码在整个 Android 生态系统中保持一致的运行效果。ExoPlayer 包含:
- 支持播放列表
- 支持各种渐进式和自适应流式传输格式
- 支持客户端和服务器端广告插播
- 支持受 DRM 保护的播放
本页将引导您完成构建播放应用的一些关键步骤,如需了解更多详情,您可以参阅有关 Media3 ExoPlayer 的完整指南。
使用入门
首先,添加对 Jetpack Media3 的 ExoPlayer、UI 和 Common 模块的依赖项:
Kotlin
implementation("androidx.media3:media3-exoplayer:1.11.1")
implementation("androidx.media3:media3-ui:1.11.1")
implementation("androidx.media3:media3-common:1.11.1")
Groovy
implementation "androidx.media3:media3-exoplayer:1.11.1"
implementation "androidx.media3:media3-ui:1.11.1"
implementation "androidx.media3:media3-common:1.11.1"
根据您的使用场景,您可能还需要 Media3 中的其他模块,例如 exoplayer-dash,以播放 DASH 格式的流。
请务必将 1.11.1 替换为您偏好的库版本。您可以参阅版本说明,查看最新版本。
创建媒体播放器
借助 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 会话模块的依赖项:
Kotlin
implementation("androidx.media3:media3-session:1.11.1")
Groovy
implementation "androidx.media3:media3-session:1.11.1"
创建媒体会话
您可以在初始化播放器后创建 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())来控制播放。与其他组件类似,请务必在 Activity 的 onStop() 生命周期方法中调用 MediaController.releaseFuture() 来释放 MediaController(不再需要时)。
发布通知
前台服务在处于活跃状态时必须发布通知。MediaSessionService 会自动以 MediaNotification 的形式为您创建 MediaStyle 通知。如需提供自定义通知,请使用 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 过滤器。额外的 intent 过滤器可让使用 MediaBrowserCompat API 的客户端应用识别您的 Service。
借助 MediaLibrarySession,您可以采用树状结构(只有一个根 MediaItem)提供内容库。树中的每个 MediaItem 都可以有任意数量的子 MediaItem 节点。您可以根据客户端应用的请求提供不同的根或不同的树。例如,您返回给正在寻找推荐媒体项列表的客户端的树可能只包含根 MediaItem 和单层子级 MediaItem 节点,而您返回给其他客户端应用的树可能表示更完整的内容库。
创建 MediaLibrarySession
MediaLibrarySession 扩展了 MediaSession API,以添加内容浏览 API。与 MediaSession 回调相比,MediaLibrarySession 回调添加了以下方法:
- 当客户端请求内容树的根
MediaItem时,使用onGetLibraryRoot() onGetChildren()用于在客户端请求内容树中MediaItem的子项时onGetSearchResult(),用于在客户端请求内容树中针对给定查询的搜索结果时
相关回调方法将包含一个 LibraryParams 对象,其中包含有关客户端应用感兴趣的内容树类型的其他信号。