Android 多媒体开发——Media3与MediaSession最全使用指南

一、Media3库简介

1.1 Media3是什么?

官方释义:

Jetpack Media3 is the new home for media libraries that enables Android apps to display rich audio and visual experiences. Media3 offers a simple architecture with powerful customization, reliability, and optimizations based on device capabilities to abstract away the complexity that comes with fragmentation.

个人理解:

Media3是Google推出的Android媒体播放库的最新版本,作为之前Media2库的后续升级版本,集成了ExoPlayer作为核心播放引擎。

Media3的目标是统一之前分散的多个媒体库(如ExoPlayer,Media2等)到单一、现代化的API体系之下。

1.2 Media2与Media3的不同

  • Media3提供了对Media2 API的向前兼容性,同时整合了ExoPlayer强大的媒体处理能力。
  • 代码迁移成本降低:开发者可以更顺畅地从Media2迁移至Media3,尽可能减少影响现有应用功能的风险。可参考《Media3迁移指南》
  • Media3将原有Media2的基础组件与ExoPlayer的高级特性结合起来,使得开发者可以在统一的架构下使用更先进的功能。

二、为什么要用Media3

2.1 目前常用框架类型

目前常用的框架如下所示的媒体框架示意图如下所示:

媒体中心主要对接两端,上端承接各路控制源,下路承接媒体App:

2.1.1 媒体App接入

媒体框架通常有一个中心管理器——MediaCenter,即媒体中心。所有的音、视频多媒体都需要接入MediaCenter,并完成以下任务:

  1. 启动时需要第一时间注册媒体中心,通知媒体中心自己的包名以及其他基本信息
  2. 注册成功媒体中心会返回一个token,后续使用token来进行媒体控制
  3. 所有的播放/暂停/结束/切歌/seek/歌词等播放控制和信息传递都需要通知媒体中心
  4. 媒体App需要监听媒体中心的控制消息,并完成响应的指令

这样,所有的媒体App可以在媒体中心的调度下有条不紊的运行

2.1.2 控制源接入

媒体框架另一端对接各种控制源,主要包括:

  • 控制中心
  • 语音输入
  • 方控

控制源相对比较固定,而且各个控制源的类型不太一样,所以没有完全统一的接入方式。

整体来讲一个App需要接入媒体中心并能够在控制中心同步媒体状态需要经过以下流程:

暂时无法在路特斯桌面文档外展示此内容

2.2 MediaSession解决方案

一个媒体App要做的事情无非就是播放器状态与UI的控制,如下:

  • Player: 播放器负责解码并渲染音视频内容
  • UI: 播放的内容需要在UI上显示,并可以通过UI对播放器状态进行控制

而媒体中心的任务就是调度各个App的Player,同时给用户提供一个统一的显示及控制入口。

MedaSession就是Android官方提供的一个中间管理器

2.2.1 什么是MediaSession

官方释义:

Media sessions provide a universal way of interacting with an audio or video player. In Media3, the default player is the ExoPlayer class, which implements the Player interface. Connecting the media session to the player allows an app to advertise media playback externally and to receive playback commands from external sources.

个人理解:

媒体会话,即向系统公开正在播放的媒体信息,并对外开放控制端口。可以用它在多个App之间协调媒体控制的机制,通过创建一个中心化的会话来管理与音视频播放相关的各种操作。

2.2.2 MediaSession用法

在使用MediaSession之前,我们需要了解几个模块:

  • MediaSession: 媒体会话,用来展示媒体播放信息,并对外提供控制接口
  • MediaSessionService: 将MediaSession及其关联的Player保存在与应用的主 Activity 不同的服务中,以便于后台播放。
  • MediaController: 用于向MediaSession发送命令,例如从其他应用或系统本身发送命令。这些命令会被发送到关联 MediaSession 的底层 Player
  • MediaBrowser: 用来浏览媒体应用的内容库,并选择要播放的内容。

使用步骤如下:

1、连接MediaService

MediaBrowser作为客户端,远程连接Player所在的MediaService:

override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val component = ComponentName(this, MediaService::class.java)mMediaBrowser = MediaBrowser(this, component, connectionCallback, null);mMediaBrowser.connect()
}
2、连接状态回调

在connect之后可以拿到回调,并获取服务端在onGetRoot中设置的MediaID。如果连接成功则可以创建媒体控制器后续对媒体进行控制:

override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val component = ComponentName(this, MediaService::class.java)mMediaBrowser = MediaBrowser(this, component, connectionCallback, null);mMediaBrowser.connect()
}
private val connectionCallback = object : MediaBrowser.ConnectionCallback() {override fun onConnected() {super.onConnected()// todo 可创建MediaContrller}override fun onConnectionFailed() {super.onConnectionFailed()}override fun onConnectionSuspended() {super.onConnectionSuspended()}
}
3、媒体控制结果返回
private val connectionCallback = object : MediaBrowser.ConnectionCallback() {override fun onConnected() {super.onConnected()if(mMediaBrowser.isConnected) {val mediaId = mMediaBrowser.rootmMediaBrowser.getItem(mediaId, itemCallback)}}
}private val itemCallback = object : MediaBrowser.ItemCallback(){override fun onItemLoaded(item: MediaBrowser.MediaItem?) {super.onItemLoaded(item)}override fun onError(mediaId: String) {super.onError(mediaId)}
}
4、订阅服务

在连接成功后,我们需要订阅服务,同样也需要注册订阅回调。订阅成功会拿到当前的媒体信息(MediaItem),可以在UI中展示当前的音乐列表数据:

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {override fun onConnected() {super.onConnected()...if(mMediaBrowser.isConnected) {val mediaId = mMediaBrowser.rootmMediaBrowser.unsubscribe(mediaId)mMediaBrowser.subscribe(mediaId, subscribeCallback)}}
}private val subscribeCallback = object : MediaBrowser.SubscriptionCallback(){override fun onChildrenLoaded(parentId: String,children: MutableList<MediaBrowser.MediaItem>) {super.onChildrenLoaded(parentId, children)}override fun onChildrenLoaded(parentId: String,children: MutableList<MediaBrowser.MediaItem>,options: Bundle) {super.onChildrenLoaded(parentId, children, options)}override fun onError(parentId: String) {super.onError(parentId)}override fun onError(parentId: String, options: Bundle) {super.onError(parentId, options)}
}
5、播放控制

MediaController的创建需要对应的MediaID,所以必须在MediaBrowser连接成功并拿到MediaID之后才可以创建:

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {override fun onConnected() {super.onConnected()// ...if(mMediaBrowser.isConnected) {val sessionToken = mMediaBrowser.sessionTokenmMediaController = MediaController(applicationContext,sessionToken)}}
}

然后就可以使用mMediaController对媒体进行控制了,比如控制媒体播放的代码如下:

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {override fun onConnected() {super.onConnected()// ...if(mMediaBrowser.isConnected) {val sessionToken = mMediaBrowser.sessionTokenmMediaController = MediaController(applicationContext, sessionToken)}}
}private fun play() {// 控制播放mMediaController.transportControls.play()
}private fun pause() {// 控制暂停mMediaController.transportControls.pause()
}

我们可以通过MediaContrllertransportControls接口完成播放控制

6、接收MediaSession回调

为了保持UI和Player的状态一直,我们除了控制播放器之外,还需要监听由MediaSession发过来的回调事件:

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {override fun onConnected() {super.onConnected()...if(mMediaBrowser.isConnected) {val sessionToken = mMediaBrowser.sessionTokenmMediaController = MediaController(applicationContext,sessionToken)mMediaController.registerCallback(controllerCallback)}}
}private val controllerCallback = object : MediaController.Callback() {override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {super.onAudioInfoChanged(info)}override fun onExtrasChanged(extras: Bundle?) {super.onExtrasChanged(extras)}
}
7、实现MediaBrowserService

用于承载Player的Service,MediaSession也和Player一样位于Service中。实现MediaBrowserService需要复写亮哥方法:

  • onGetRoot: 客户端连接的时候调用,在里面可以决定是否允许客户端连接,返回null表示拒绝,否则同意
  • onLoadChildren: 客户端订阅服务时触发,可以选择返回给客户端的服务数据

示例代码如下:

class MediaService : MediaBrowserService() {override fun onGetRoot(clientPackageName: String,clientUid: Int,rootHints: Bundle?): BrowserRoot? {return BrowserRoot(ID, null)}override fun onLoadChildren(parentId: String,result: Result<MutableList<MediaBrowser.MediaItem>>) {when (parentId) {ID -> {// todo 查询媒体或者数据库,找到客户端需要的数据result.detach()result.sendResult()}else -> {}}}
}

最后,记得在Manifest里注册Service:

<serviceandroid:name=".MediaService"android:label="@string/media_service"><intent-filter><action android:name="android.media.browse.MediaBrowserService" /></intent-filter>
</service>

三、基于Media2的改进

3.1 前台播放

3.1.1 Media2时代方案

前台播放的时候,比如视频、直播场景。可以把播放器和UI放在同一个Activity中。Media2的架构如下:

由于在同一个Activity,Player和UI可以很方便的相互操作,而媒体信息同步到MediaSession需要有一个注册回调的过程,这中间需要提供一个连接器,即Connector,所有的操作都需要从Connector进行中转,增加了系统复杂性及出错概率。

3.1.2 Media3改进方式

Media3直接将ExoPlayer作为了Player的默认实现,并且实现了标准的播放器接口,从而UI和MediaSession都可以支持改接口。这样就可以免去连接器,使整体框架更稳定

3.2 后台播放

3.2.1 Media2时代方案

后台播放的改进也很明显,与前台播放不一样,后台播放需要把Player放到Service中,而UI保留在Activity中,整个会变成C/S架构:

首先我们将Player和UI进行了分离,那么相互之间的控制就需要通过MediaSession来进行。其中MediaSession在Server端进行统一管理,而UI作为Client创建MediaController连接MediaSession进行通信。

这里同样会有前台播放的问题,即Player无法与MediaSession直接通信,需要单独增加Connector。在Client端,MediaContrller和UI的接口也不同,则也需要一个Connector进行连接。项目整体复杂度进一步增加。

3.2.2 Media3改进方案

以下是Media3的后台播放方案:

如前文所述,Media3使用了一种通用的Player接口来消除连接器,并且默认使用ExoPlayer作为播放器,内部已经实现了Player接口,如此即可直接与MediaSeesion兼容,这样就可以通过Player直接完成MediaSession、MediaController的直接通信,可以去掉Connetor中多余的中转代码。

四、Media3改进的秘密

综上,Media3一个非常明显的好处就是省略了Connector连接器,大幅简化的层级结构,代码量也可以减少很多。这个得益于Media3直接将ExoPlayer纳入麾下,成为了默认的播放器实现。那下面来聊聊为什么ExoPlayer的加入带来这么多好处。

4.1 设计思想的改进

4.1.1 Media2的设计思想

在Media2中,Player和UI的通信依赖MediaBrowserServiceMediaBrowser配合使用,提供了一种Client和Service之间的沟通机制。当一个App想要播放媒体时,实际上是创建了一个MediaBrowser实例,并通过它来连接到后台的 MediaBrowserService来实现的。这个过程涉及到绑定服务、处理异步回调等复杂的交互流程,这就是我们说的的"connection"。

这种模型允许媒体控制和播放在应用的不同组件(例如,不同的Activity、Fragment或者后台服务)之间能够保持一致性。此外,它也支持跨应用的媒体控制,比如可以从其他应用或者Android系统级别的媒体控制界面控制播放。

4.1.2 Media3的设计思想

进入Media3时代,Google对这套API进行了重新设计,摒弃了连接这个概念。Media3直接整合了ExoPlayer,提供了一套更为简洁的API。

Media3兼容类似Media2中的MediaBrowser和远程播放控制功能,但它实现了一种更轻量级的方式来管理这些操作,不再需要显式地管理服务连接。

Media3利用了新的架构,将MediaBrowser和播放能力内聚在少数几个组件中,比如 MediaSessionMediaController。通过这种设计,Media3能够让媒体播放和控制更加直接和高效,同时也简化了应用架构。

总体来讲,Media2是需要连接器的,因为它采用了C/S架构来处理媒体播放任务,使其能够支持跨应用的媒体共享和控制。而Media3是无需连接的,通过简化API和直接整合ExoPlayer的方式来提升开发效率和用户体验。

4.2 代码上的改进

无需再创建Connection进行连接,ExoPlayer可以直接构建出MediaSession对象,播控由ExoPlayer内部完成MediaController和MediaSession的交互,并实现了统一的Player接口,用来回调播放器的状态,摆脱了C/S架构,代码更整洁

class ExamplePlaybackService : MediaSessionService() {private var exoPlayer: ExoPlayer? = nullprivate var mediaSession: MediaSession? = nulloverride fun onCreate() {super.onCreate()// 创建ExoPlayerexoPlayer = ExoPlayer.Builder(this).build()// 基于已创建的ExoPlayer创建MediaSessionexoPlayer?.let { mediaSession = MediaSession.Builder(this, it).build() }}override fun onDestroy() {// 释放相关实例exoPlayer?.stop()exoPlayer?.release()exoPlayer = nullmediaSession?.release()mediaSession = nullsuper.onDestroy()}override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {return mediaSession}
}

五、车载媒体开发技术展望

Media3技术目前已经比较成熟,各大媒体App都相继支持,且作为官方大力推荐的工具,可以将很多复杂的工作交由Android系统完成,兼容性和稳定性都有一定的保障。

未来车载媒体可能会接入更多第三方媒体,比如爱奇艺、优酷、喜马拉雅、在线音乐等,按照当前架构就需要他们集成MediaCenter.jar并按照我们定义的接口协议完成开发,除了第三方的工作量之外,我们也需要提供不少的技术支持及Bugfix的排查定位工作。

综上,MediaCenter未来会计划接入MediaSession,目前还在调研阶段,希望未来能够更快的实现媒体接口的统一。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/35756.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软考出成绩了,速查,查分方式看这里

各位考生&#xff0c;软考出成绩啦&#xff01;大家赶紧查一下&#xff0c;各科都45分就是通过&#xff01; 软考成绩查询入口已开通&#xff0c;此刻你是不是既激动又忐忑&#xff1f;速查&#xff01; ★ 查询网站 中国计算机技术职业资格网https://bm.ruankao.org.cn/sign/…

如何轻松获取 GitLab 指定分支特定路径下的文件夹内容

第一步&#xff1a; 获取 accessToken 及你的 项目 id &#xff1a; 获取 accessToken ,点击用户头像进入setting 按图示操作&#xff0c;第 3 步 填写你发起请求的域名。 获取项目 id , 简单粗暴方案 进入 你项目仓库页面后 直接 源码搜索 project_id&#xff0c; value 就…

记录Gstreamer的uridecodebin可以自动选择硬解码器

记录&#xff1a; uridecodebin3 和uridecodebin优先硬解码 这两个插件&#xff0c;本来是负责动态选择合适的解码器来处理特定的媒体流&#xff0c;使用案例&#xff1a; gst-launch-1.0 uridecodebin urirtsp://192.168.1.120:8554/test ! glimagesink -v gst-launch-1.0 …

Linux通用LInux高危漏洞(CVE-2024-1086)修复案例

一、漏洞描述 2024年3月28日&#xff0c;监 Linux kernel权限提升漏洞&#xff08;CVE-2024-1086&#xff09;的PoC/EXP在互联网上公开&#xff0c;该漏洞的CVSS评分为7.8&#xff0c;目前漏洞细节已经公开披露&#xff0c;美国网络安全与基础设施安全局&#xff08;CISA&…

【UE5.3】笔记4-自定义材质蓝图

正常来说&#xff0c;我们都是拿到什么材质用什么材质&#xff0c;那么我们如何去创建自定义的材质呢&#xff1f; 首先&#xff0c;创建MyMaterials文件夹用来存放我们自制的材质&#xff1b; 然后&#xff0c;右键创建一个材质&#xff0c;起个名字&#xff0c;双击打开&am…

Linux-笔记 全志平台休眠功能初探

前言 全志平台支持的休眠功能主要包括两种模式&#xff1a;休眠模式和待机模式。这两种模式用于降低设备的功耗&#xff0c;并在需要时快速恢复工作状态。由于平台为T113&#xff0c;所以可以很方便的使用RTC来做唤醒源。唤醒源指的是能够让系统从休眠状态恢复到工作状态的信号…

【Linux】Linux基础开发工具(yum)

Linux 软件包管理器 yum 什么是软件包 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序.但是这样太麻烦了, 于是有些人把一些常用的软件提前编译好, 做成软件包(可以理解成windows上的安 装程序)放在一个服务器上, 通过包管理器可以很方便…

不同材质的不锈钢氮气柜优缺点和使用场景分析

不锈钢氮气柜是一种用于存储对湿度敏感的物品的专用储藏设备&#xff0c;通过充入干燥的氮气来降低柜内湿度&#xff0c;保护存储物免受氧化或腐蚀。 根据不同的材质&#xff0c;不锈钢氮气柜可分为&#xff1a; 一、201不锈钢氮气柜&#xff1a; 201不锈钢因其较低的镍含量而成…

【第14章】探索新技术:如何自学SD3模型(找官方资料/精读/下载/安装/3款工作流/效果测试)ComfyUI基础入门教程

近期,也就是2024年6月12日,StabilityAI开源了最新的SD3模型的2B版本,而神奇的是,ComfyUI早在6月11号就已经适配了SD3!相比之下,SD WebUI 的更新速度却远远落后... 所以,如果想要尝试一些AI绘画领域的新技术,ComfyUI是一个非常值得投入时间学习的工具。 这节课,我们就…

防火墙虚拟系统

防火墙虚拟系统 防火墙虚拟系统的应用场景 大中型企业的网络隔离 通过防火墙的虚拟系统将网络隔离为研发部门、财经部门和行政部门。各部门之间可以根据权限互相访问&#xff0c;不同部门的管理员权限区分明确。 云计算中心的安全网关 通过配置虚拟系统&#xff0c;可让部署…

[XYCTF新生赛2024]-PWN:ptmalloc2 it‘s myheap plus解析(glibc2.35,堆中的栈迁移,orw)

查看保护 查看ida 思路&#xff1a; 泄露libc和堆地址就不多说了&#xff0c;fastbin duf也不解释了。这里主要是利用fastbin duf在environ附近创建堆块&#xff0c;泄露environ中的栈地址&#xff0c;然后就利用fastbin duf修改rbp和返回地址进行栈迁移了&#xff0c;迁移目标…

Xcode安装Simulator失败问题解决方法

Xcode安装Simulator_Runtime失败&#xff0c;安装包离线安装保姆级教程 Xcode更新之后有时候会提示要安装模拟器运行时环境&#xff0c;但是用Xcode更新会因为网络原因&#xff0c;我觉得基本上就是因为苹果服务器的连接不稳定导致的&#xff0c;更可气的是不支持断点续…

数据结构与算法笔记:高级篇 - 概率统计:如何利用朴素贝叶斯算法过滤垃圾短信?

概述 上篇文章我们讲到&#xff0c;如何用位图、布隆过滤器&#xff0c;来过滤重复数据。本章&#xff0c;我们再讲一个跟过滤相关的问题&#xff0c;如果过滤垃圾短信&#xff1f; 垃圾短信和骚扰电话&#xff0c;我想每个人都收到过吧&#xff1f;买房、贷款、投资理财、开…

Git 冲突处理指南:恢复 Git Reset

⭐️我叫忆_恒心&#xff0c;一名喜欢书写博客的研究生&#x1f468;‍&#x1f393;。 如果觉得本文能帮到您&#xff0c;麻烦点个赞&#x1f44d;呗&#xff01; 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧&#xff0c;喜欢的小伙伴给个三连支…

【Liunx-后端开发软件安装】Liunx安装FDFS并整合nginx

【Liunx-后端开发软件安装】Liunx安装nacos 文章中涉及的相关fdfs相关软件安装包请点击下载&#xff1a; https://download.csdn.net/download/weixin_49051190/89471122 一、简介 FastDFS是一个开源的轻量级分布式文件系统&#xff0c;它对文件进行管理&#xff0c;功能包括…

黑马点评06短信登录-用户请求和会话管理过程

用户请求发送&#xff1a; 用户的浏览器向服务器发送请求&#xff08;例如&#xff0c;访问网页或提交表单&#xff09;。请求头包含之前存储在浏览器中的Cookie&#xff0c;其中包括会话ID&#xff08;Session ID&#xff09;。 服务器接收请求&#xff1a; 服务器接收到用户的…

杭州代理记账报税全程托管专业实力全面指南

杭州代理记税报税服务可以为企业提供全程托管财务管理解决方案&#xff0c;确保企业的财务工作专业、高效、合规。以下是杭州代理记税报税服务全面指南&#xff1a; https://www.9733.cn/news/detail/185.html 一、代理记账报税服务的内容 基础服务&#xff1a; 每日记&#xf…

Python-井字棋

井字棋 1.设计登录界面1.1导入需要的工具包1.2窗口显示1.3登录界面图片显示1.6标签按钮输入框显示 2.登录功能实现2.1用户数据存储 2.2登录和注册2.2.1登录功能实现2.2.2注册功能实现 3.井字棋游戏3.1 导入需要的工具包3.2 窗口显示3.2 按钮标签显示3.3 棋盘设置初始状态3.4 游…

ChatUI:使用Gradio.NET为LLamaWorker快速创建大模型演示界面

Gradio.NET 是 Gradio 的.NET 移植版本。它是一个能够助力迅速搭建机器学习模型演示界面的库&#xff0c;其提供了简洁的 API&#xff0c;仅需寥寥数行代码就能创建出一个具备交互性的界面。在本篇文章中&#xff0c;我们将会阐述如何借助 Gradio.NET 为 LLamaWorker 快捷地创建…

新需求:如何实现一个ShardingSphere分库分表平台

大家好&#xff0c;目前我们正面对一个既具挑战又令人兴奋的任务——构建一套高效、稳定的数据处理系统&#xff0c;特别是一个结合了SpringBoot、ShardingSphere、MyBatisPlus和MySQL技术的综合数据分库分表平台。简单来说&#xff0c;我们要做的就是打造一个能轻松应对大数据…