Jetpack Media3 定义了一个 Player
接口,该接口概述了视频和音频文件播放的基本功能。 ExoPlayer
是 Media3 中此接口的默认实现。我们建议使用 ExoPlayer,因为它提供了一套全面的功能,涵盖了大多数播放用例,并且可以自定义以处理您可能遇到的任何其他用例。ExoPlayer 还抽象了设备和操作系统的碎片化,因此您的代码在整个 Android 生态系统中都能一致地工作。ExoPlayer 包括
- 对 播放列表 的支持
- 对各种渐进式和自适应流 格式 的支持
- 对客户端和服务器端 广告插入 的支持
- 对 受 DRM 保护的播放 的支持
此页面将引导您完成构建播放应用程序的一些关键步骤,有关更多详细信息,您可以访问我们关于 Media3 ExoPlayer 的完整指南。
入门
要开始,请添加对 ExoPlayer、UI 和 Jetpack Media3 的 Common 模块的依赖项
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
UI 组件,您可以将其包含在应用程序的布局文件中。此组件封装了用于播放控制的 PlayerControlView
、用于显示字幕的 SubtitleView
和用于渲染视频的 Surface
。
准备播放器
使用诸如 setMediaItem()
和 addMediaItem()
的方法将 媒体项 添加到播放列表以进行播放。然后,调用 prepare()
以开始加载媒体并获取必要的资源。
您不应在应用程序处于前台之前执行这些步骤。如果您的播放器位于 Activity
或 Fragment
中,则意味着在 API 级别 24 及更高版本上在 onStart()
生命周期方法中准备播放器,或者在 API 级别 23 及以下版本上在 onResume()
生命周期方法中准备播放器。对于位于 Service
中的播放器,您可以在 onCreate()
中准备它。
控制播放器
播放器准备就绪后,您可以通过调用播放器上的方法来控制播放,例如
play()
和pause()
用于开始和暂停播放seekTo()
用于跳转到当前媒体项中的某个位置seekToNextMediaItem()
和seekToPreviousMediaItem()
用于在播放列表中导航
当绑定到播放器时,PlayerView
或 PlayerControlView
等 UI 组件将相应更新。
释放播放器
播放可能需要供应有限的资源,例如视频解码器,因此在不再需要播放器时,调用播放器上的 release()
以释放资源非常重要。
如果您的播放器位于 Activity
或 Fragment
中,请在 API 级别 24 及更高版本上的 onStop()
生命周期方法或 API 级别 23 及以下版本上的 onPause()
方法中释放播放器。对于位于 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
时设置 回调 对象。
当控制器即将连接到您的媒体会话时,将调用 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(); } }
在您的清单中,您的具有 MediaSessionService
意图过滤器的 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; }
连接到您的 UI
现在您的媒体会话位于与包含播放器 UI 的 Activity
或 Fragment
分开的 Service
中,您可以使用 MediaController
将它们链接在一起。在包含 UI 的 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()
。
发布通知
前台服务需要在活动时发布通知。一个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
和(为了向后兼容)旧版MediaBrowserService
的意图过滤器。额外的意图过滤器使使用MediaBrowserCompat
API 的客户端应用程序能够识别您的Service
。
一个MediaLibrarySession
允许您以树状结构提供您的内容库,并具有单个根MediaItem
。树中的每个MediaItem
可以具有任意数量的子MediaItem
节点。您可以根据客户端应用程序的请求提供不同的根或不同的树。例如,您返回给正在查找推荐媒体项目列表的客户端的树可能仅包含根MediaItem
和单个级别的子MediaItem
节点,而您返回给其他客户端应用程序的树可能表示更完整的内容库。
创建MediaLibrarySession
一个MediaLibrarySession
扩展了MediaSession
API 以添加内容浏览 API。与MediaSession
回调相比,MediaLibrarySession
回调添加了诸如以下方法:
onGetLibraryRoot()
用于客户端请求内容树的根MediaItem
时onGetChildren()
用于客户端请求内容树中MediaItem
的子项时onGetSearchResult()
用于客户端请求给定查询的内容树中的搜索结果时
相关的回调方法将包含一个LibraryParams
对象,其中包含有关客户端应用程序感兴趣的内容树类型的其他信号。