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.rhkb.cn/news/361318.html

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

相关文章

Git 和 TortoiseGit 安装和配置(图文详解)

使用git&#xff0c;需要在Windows上需要安装两个软件&#xff1a;1&#xff09;Git 2&#xff09;TortoiseGit 若需要&#xff0c;可以下载TortoiseGit汉化语言包。 注意&#xff1a;tortoiseGit是在安装了Git的基础上运行的&#xff0c;所以需要先安装Git&#xff0c;后安装…

Mysql索引的实现原理,B+Tree,WAL

InnoDB 引擎&#xff0c;每一个数据表有两个文件 .frm和.ibd&#xff0c;分别为表结构&#xff0c;数据和索引&#xff0c;数据挂在主索引的叶子节点上&#xff0c;此主索引称为聚簇索引。 MyISAM 引擎&#xff0c;每一个数据表有三个文件.frm和.MYI和.MYD&#xff0c;分别为表…

深入理解计算机系统 CSAPP 家庭作业7.13

用一下496页提到的工具咯 A: whereis libm.a file lidm.a gedit libm.a libm.a是个ASCII text文件打开一看原来 libm-2.27.a 和libmvec.a才是我们要看的 所以我们cd到目标地址后 ar -t libm-2.27.a ar -t libmvec.a B: gcc -Og bar5.c foo5.c 用之前的两个文件链接后生成…

【CS.DS】数据结构 —— 图:深入了解三种表示方法之邻接表(Adjacency List)

文章目录 1 概念2 无向图的邻接表2.1 示例2.2 Mermaid 图示例2.3 C实现2.3.1 简单实现2.3.2 优化封装 2.4 总结 3 有向图的邻接表3.1 示例3.2 C实现3.3 总结 4 邻接图的遍历5 拓展补充References 数据结构 1 概念 优点&#xff1a;空间效率高&#xff0c;适合稀疏图。动态性强…

springboot 整合redis

文章目录 一、Jedis二、Lettuce三、RedisTemplate(重点)单机3.1 springboot 整合swagger3.2 序列化中文问题集群3.3 applications配置3.4 问题 一、Jedis package com.example.redis;import redis.clients.jedis.Jedis;import javax.print.DocFlavor; import java.util.*;/***…

【编译原理】绪论

1.计算机程序语言以及编译 编译是对高级语言的翻译 源程序是句子的集合&#xff0c;树可以较好的反应句子的结构 编译程序是一种翻译程序 2.编号器在语言处理系统中的位置 可重定位&#xff1a;在内存中存放的起始位置不是固定的 加载器&#xff1a;修改可重定位地址&#x…

古文字识别笔记

前置知识 部件&#xff1a;大部分的汉字是由若干组笔画结构拼合而成的&#xff0c;这些相对独立的笔画结构称为「部件」。 部件是大于基本笔画&#xff08;例如&#xff1a;点、横、撇、捺等&#xff09;而小于或等同于 偏旁 的结构单位。 例如「测」字有三个部件&#xff1a;…

【学习】使用PyTorch训练与评估自己的ResNet网络教程

参考&#xff1a;保姆级使用PyTorch训练与评估自己的ResNet网络教程_训练自己的图像分类网络resnet101 pytorch-CSDN博客 项目地址&#xff1a;GitHub - Fafa-DL/Awesome-Backbones: Integrate deep learning models for image classification | Backbone learning/comparison…

高效修复机床导轨磨损,保障加工精度!

机床导轨是支承和引导运动构件沿着一定轨迹运动的传动装置&#xff0c;在机器设备中是个十分重要的部件&#xff0c;在机床中是常见的部件。机床的加工精度与导轨精度有直接的联系&#xff0c;且导轨一旦损坏&#xff0c;维修较复杂且困难。我们简单总结了以下几点对于机床导轨…

编程设计思想

健康检查脚本 nmap:扫描端口 while true do healthycurl B:httpPORT/healthy -i | grep HTTP/1.1 | tail -n 1 | awk {print $2} done 批量操作类型脚本&#xff08;记录每一步日志&#xff09; 将100个nginx&#xff1a;vn推送到harbor仓库192.168.0.100 根据镜像对比sha值…

【开源项目】自然语言处理领域的明星项目推荐:Hugging Face Transformers

在当今人工智能与大数据飞速发展的时代&#xff0c;自然语言处理&#xff08;NLP&#xff09;已成为推动科技进步的重要力量。而在NLP领域&#xff0c;Hugging Face Transformers无疑是一个备受瞩目的开源项目。本文将从项目介绍、代码解释以及技术特点等角度&#xff0c;为您深…

面向对象修炼手册(四)(多态与空间分配)(Java宝典)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;面向对象修炼手册 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 前言 1 多态 1.1 多态的形式&…

需求之 实现获取调试信息在h5页面,在手机端可以查看调试(二)

事实证明 chatgpt很好用&#xff0c;有不懂的问题可以问它 https://zhuanlan.zhihu.com/p/690118775 国内外9个免费的ChatGPT网站 我筛选出来的比较好用免费的网站 fchat.dykyzdh.cn/ 这个也可以 阿里云的 通义灵码 在vscode中安装使用 而且阿里云有一个产品&#xff0c;可以…

面试-Java线程池

1.利用Excutors创建不同的线程池满足不同场景的需求 分析&#xff1a; 如果并发的请求的数量非常多&#xff0c;但每个线程执行的时间非常短&#xff0c;这样就会频繁的创建和销毁线程。如此一来&#xff0c;会大大降低系统的效率。 可能出现&#xff0c;服务器在为每个线程创建…

jdk1.8升级到jdk11遇到的各种问题

一、第三方依赖使用了BASE64Decoder 如果项目中使用了这个类 sun.misc.BASE64Decoder&#xff0c;就会导致错误&#xff0c;因为再jdk11中&#xff0c;该类已经被删除。 Caused by: java.lang.NoClassDefFoundError: sun/misc/BASE64Encoder 当然这个类也有替换方式&#xf…

MySQL实训--原神数据库

原神数据库 er图DDL/DML语句查询语句存储过程/触发器 er图 DDL/DML语句 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;DROP TABLE IF EXISTS artifacts; CREATE TABLE artifacts (id int NOT NULL AUTO_INCREMENT,artifacts_name varchar(255) CHARACTER SET utf8 COLLATE …

一文搞懂Linux多线程【下】

目录 &#x1f6a9;多线程代码的健壮性 &#x1f6a9;多线程控制 &#x1f6a9;线程返回值问题 &#x1f6a9;关于Linux线程库 &#x1f6a9;对Linux线程简单的封装 在观看本博客之前&#xff0c;建议大家先看一文搞懂Linux多线程【上】由于上一篇博客篇幅太长&#xff0c;为…

文件操作<C语言>

导言 平时我们在写程序时&#xff0c;在运行时申请内存空间&#xff0c;运行完时内存空间被收回&#xff0c;如果想要持久化的保存&#xff0c;我们就可以使用文件&#xff0c;所以下文将要介绍一些在程序中完成一些文件操作。 目录 导言 文件流 文件指针 文件的打开与关闭 …

《黑神话悟空》电脑配置要求

《黑神话&#xff1a;悟空》这款国内优秀的3A游戏大作&#xff0c;拥有顶级的特效与故事剧情&#xff0c;自公布以来便备受玩家期待&#xff0c;其精美的画面与流畅的战斗体验&#xff0c;对玩家的电脑配置提出一定要求。那么这款优秀的游戏需要什么样的电脑配置&#xff0c;才…

记录:[android] SSLHandshakeException: Handshake failed 问题;已解决!

1、问题描述&#xff1a;在使用Retrofit2 时在安卓老设备上&#xff08;安卓6.0&#xff09;网络无法请求、安卓 10 、 11 未出现此问题&#xff1f;what? 原因&#xff1a;服务端 TLS 版本过高 2、废话不多说、解决方案A 、添加依赖&#xff1a;implementation org.conscrypt…