Jetpack Media3 定义了一个 Player
接口,其中概述了用于播放视频和音频文件的基本功能。ExoPlayer
是 Media3 中此接口的默认实现。我们建议您使用 ExoPlayer,因为它提供了一套全面的功能,可涵盖大多数播放用例,并且可自定义以处理您可能拥有的任何其他用例。ExoPlayer 还会抽象出设备和操作系统碎片化,以便您的代码在整个 Android 生态系统中一致运行。ExoPlayer 包括:
- 支持播放列表
- 支持多种渐进式和自适应流式传输格式
- 同时支持客户端和服务器端广告插播
- 支持受 DRM 保护的播放
本页将逐步引导您完成构建播放应用的一些关键步骤。如需了解更多详情,请参阅我们关于 Media3 ExoPlayer 的完整指南。
使用入门
首先,添加对 Jetpack Media3 的 ExoPlayer、界面和通用模块的依赖项:
implementation "androidx.media3:media3-exoplayer:1.4.1" implementation "androidx.media3:media3-ui:1.4.1" implementation "androidx.media3:media3-common:1.4.1"
根据您的应用场景,您可能还需要 Media3 中的其他模块,例如 exoplayer-dash
,以便以 DASH 格式播放串流。
请务必将 1.4.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()
中准备该播放器。
控制播放器
播放器准备就绪后,您可以通过调用播放器的方法来控制播放,例如:
play()
和pause()
- 开始和暂停播放seekTo()
用于跳转到当前媒体项中的某个位置seekToNextMediaItem()
和seekToPreviousMediaItem()
,用于浏览播放列表
当绑定到玩家时,界面组件(例如 PlayerView
或 PlayerControlView
)将相应更新。
释放播放器
播放可能需要供应量有限的资源(例如视频解码器),因此在不再需要播放器时,请务必对播放器调用 release()
以释放资源。
如果您的播放器处于 Activity
或 Fragment
中,请在 API 级别 24 及更高级别上的 onStop()
生命周期方法或 onPause()
方法(API 级别 23 及更低级别)中释放播放器。对于位于 Service
中的播放器,您可以在 onDestroy()
中释放它。
使用媒体会话管理播放
在 Android 上,媒体会话提供了一种跨进程边界与媒体播放器互动的标准化方式。将媒体会话连接到播放器后,您可以对外宣传媒体播放内容,并接收来自外部来源的播放命令,例如与移动设备和大屏设备上的系统媒体控件集成。
如需使用媒体会话,请添加对 Media3 Session 模块的依赖项:
implementation "androidx.media3:media3-session:1.4.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
时设置 callback 对象。
当控制器即将连接到您的媒体会话时,系统会调用 onConnect()
方法。您可以使用提供的 ControllerInfo
来决定是否接受或拒绝请求。您可以在 Media3 Session 演示版应用中查看相关示例。
连接后,控制器即可向会话发送播放命令。然后,会话将这些命令委托给玩家。会话会自动处理在 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
(例如 Activity
的 onStop()
生命周期方法)时通过调用 MediaController.releaseFuture()
释放 MediaController
。
发布通知
前台服务必须在处于活动状态时发布通知。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 过滤器。借助额外的 intent 过滤器,使用 MediaBrowserCompat
API 的客户端应用可以识别您的 Service
。
借助 MediaLibrarySession
,您可以以树状结构(包含单个根 MediaItem
)提供内容库。树中的每个 MediaItem
都可以有任意数量的子 MediaItem
节点。您可以根据客户端应用的请求提供不同的根目录或不同的树。例如,您返回给客户端以查找推荐媒体内容列表的树可能仅包含根 MediaItem
和单个级别的子 MediaItem
节点,而返回给其他客户端应用的树可能代表更完整的内容库。
创建 MediaLibrarySession
MediaLibrarySession
会扩展 MediaSession
API 以添加内容浏览 API。与 MediaSession
回调相比,MediaLibrarySession
回调可添加如下方法:
onGetLibraryRoot()
(适用于客户端请求内容树的根MediaItem
时)onGetChildren()
适用于客户端请求内容树中MediaItem
的子项的情况onGetSearchResult()
(适用于客户端针对给定查询从内容树请求搜索结果的情况)
相关回调方法将包含 LibraryParams
对象,其中包含有关客户端应用感兴趣的内容树类型的其他信号。