Jetpack Media3 定义了一个 Player
接口,该接口概述了播放视频和音频文件的基本功能。ExoPlayer
是 Media3 中此接口的默认实现。我们建议您使用 ExoPlayer,因为它提供了一套全面的功能,涵盖了大多数播放用例,并且可自定义以处理您可能拥有的其他任何用例。ExoPlayer 还可以将设备和操作系统碎片化,以便您的代码在整个 Android 生态系统中始终如一地运行。ExoPlayer 包含:
- 支持播放列表
- 支持各种渐进式和自适应流式传输格式
- 支持客户端和服务器端广告插播
- 支持受 DRM 保护的播放
本页将向您介绍构建播放应用的一些关键步骤;如需了解详情,请参阅 Media3 ExoPlayer 上的完整指南。
快速入门
首先,为 Jetpack Media3 的 ExoPlayer、界面和常用模块添加一个依赖项:
implementation "androidx.media3:media3-exoplayer:1.3.1" implementation "androidx.media3:media3-ui:1.3.1" implementation "androidx.media3:media3-common:1.3.1"
根据您的用例,您可能还需要来自 Media3 的其他模块,例如用于播放 DASH 格式的流的 exoplayer-dash
。
请务必将 1.3.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 会话模块的依赖项:
implementation "androidx.media3:media3-session:1.3.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(); } }
在清单中,包含 MediaSessionService
intent 过滤器的 Service
类,并请求 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
会以 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
和旧版 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
对象,其中包含有关客户端应用感兴趣的内容树类型的额外信号。