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.root
            mMediaBrowser.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.root
           
            mMediaBrowser.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.sessionToken
            mMediaController = MediaController(applicationContext,sessionToken)
        }
    }
}

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

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {
    override fun onConnected() {
        super.onConnected()
        // ...
        if(mMediaBrowser.isConnected) {
            val sessionToken = mMediaBrowser.sessionToken
            mMediaController = 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.sessionToken
            mMediaController = 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:

<service
    android: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? = null
    private var mediaSession: MediaSession? = null

    override fun onCreate() {
        super.onCreate()
        // 创建ExoPlayer
        exoPlayer = ExoPlayer.Builder(this).build()
        // 基于已创建的ExoPlayer创建MediaSession
        exoPlayer?.let { mediaSession = MediaSession.Builder(this, it).build() }
    }

    override fun onDestroy() {
        // 释放相关实例
        exoPlayer?.stop()
        exoPlayer?.release()
        exoPlayer = null
        mediaSession?.release()
        mediaSession = null
        super.onDestroy()
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
        return mediaSession
    }
}

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

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

为什么部署数字人系统源码一定要找源头工厂?

随着人工智能技术的发展成熟&#xff0c;数字人的应用场景被不断拓展&#xff0c;从而让更多人看到了数字人所蕴含的前景和潜力。于是&#xff0c;越来越多的创业者开始找数字人源头工厂进行数字人源码部署&#xff0c;以打造自己的数字人系统。许多骗子也因此看到了可乘之机&a…

SK Hynix 3D DRAM良率突破56.1%,开启存储新时代

根据韩国财经媒体Business Korea独家报道&#xff1a;在刚刚结束的VLSI 2024国际研讨会上&#xff0c;韩国半导体巨头SK Hynix公布了一项振奋人心的进展&#xff1a;其五层堆叠3D DRAM的制造良率已达到56.1%。此成果标志着3D DRAM技术在商业化道路上迈出了坚实的一步&#xff0…

【python】eval函数

1.eval函数的语法及用法 &#xff08;1&#xff09;语法&#xff1a;eval(expression) 参数说明&#xff1a; expression&#xff1a;必须为字符串表达式&#xff0c;可为算法&#xff0c;也可为input函数等。 说明&#xff1a;表达式必需是字符串&#xff0c;否则会报错&a…

【Text2SQL 论文】MCS-SQL:利用多样 prompts + 多项选择来做 Text2SQL

论文&#xff1a;MCS-SQL: Leveraging Multiple Prompts and Multiple-Choice Selection For Text-to-SQL Generation ⭐⭐⭐ arXiv:2405.07467 一、论文速读 已有研究指出&#xff0c;在使用 LLM 使用 ICL 时&#xff0c;ICL 的 few-shot exemplars 的内容、呈现顺序都会敏感…

大自然高清风景视频无水印素材在哪下载?下载视频素材网分享

在视频创作领域&#xff0c;一段高清的风景视频可以极大地提升你的作品质感。无论是作为背景、过渡片段还是主要内容&#xff0c;优质的风景视频素材都是必不可少的。然而&#xff0c;寻找既高清又无水印的风景视频素材并非易事。为了帮助大家轻松获取这类素材&#xff0c;我整…

计算机缺失d3dx9_43.dll的多种解决方法,哪种更推荐使用

我在使用计算机时遇到了一个问题&#xff0c;系统提示我丢失了d3dx9_43.dll文件。丢失d3dx9_43.dll文件通常是由于DirectX组件未正确安装或损坏所致&#xff0c;这直接影响到依赖于DirectX的游戏和应用的运行。经过一番搜索和尝试&#xff0c;我找到了多种修复这个问题的方法&a…

突然断供中国!OpenAI变CloseAI,用户连夜搬家

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 更多资源欢迎关注 OpenAI&#xff0c;这把变成CloseAI了。 6月25日早上&#xff0c;有中国开发者表示收到了来自OpenAI的“警告信”&#xff1a;将采取额外措施停止其不支持的地区的API&#xff08;应用接口&#xff09…

typescript学习回顾(二)

今天来分享一下ts的基础&#xff0c;如何使用ts&#xff0c;以及ts具体的作用&#xff0c;如何去约束我们开发中常见的一些数据的&#xff0c;最后做一个小练习来巩固对ts基础的掌握程度。 类型约束 如何加类型约束呢 变量、函数的参数、函数的返回值位置加上:类型 比如 //约…

微信小程序-自定义组件checkbox

一.自定义Coponent组件 公共组件&#xff1a;将页面内公共的模块抽取为自定义组件&#xff0c;在不同页面复用。 页面组件&#xff1a;将复杂页面进行拆分&#xff0c;降低耦合度&#xff0c;有利于代码维护。 可以新建文件夹component放组件&#xff1a; 组件名为custom-che…

msvcr110.dll丢失的解决方法,亲测有效的几种解决方法

最近&#xff0c;我在启动一个程序时&#xff0c;系统突然弹出一个错误提示&#xff0c;告诉我电脑缺失了一个名为msvcr110.dll的文件。这让我感到非常困惑&#xff0c;因为我之前从未遇到过这样的问题。经过一番搜索和尝试&#xff0c;我总结了5种靠谱的解决方法。下面分享给大…

1.k8s:架构,组件,基础概念

目录 一、k8s了解 1.什么是k8s 2.为什么要k8s &#xff08;1&#xff09;部署方式演变 &#xff08;2&#xff09;k8s作用 &#xff08;3&#xff09;Mesos&#xff0c;Swarm&#xff0c;K8S三大平台对比 二、k8s架构、组件 1.k8s架构 2.k8s基础组件 3.k8s附加组件 …

【2024最新版】Eclipse安装配置全攻略:图文详解

目录 1. Eclipse介绍1.1 背景1.2 主要特点和功能1.3 版本发布1.4 优势与劣势 2. 下载Eclipse3. 安装Eclipse4. 启动Eclipse 1. Eclipse介绍 Eclipse是一个开源的、基于Java的可扩展开发平台&#xff0c;主要用于Java开发者&#xff0c;但也支持其他语言如C/C、PHP、Python等。…

CCS的安装步骤

CCS的安装步骤 安装之前有几件重要的事情要做&#xff1a; 首先肯定是要下载安装包啦&#xff01;点击此处是跳到官网下载地址安装包不能处的路径中不能包含中文关闭病毒防护和防火墙&#xff0c;以及其他杀毒软件最后是在重启后进行安装 主要的步骤如下&#xff1a; 找到安…

【SpringBoot Web框架实战教程(开源)】01 使用 pom 方式创建 SpringBoot 第一个项目

导读 这是一系列关于 SpringBoot Web框架实战 的教程&#xff0c;从项目的创建&#xff0c;到一个完整的 web 框架&#xff08;包括异常处理、拦截器、context 上下文等&#xff09;&#xff1b;从0开始&#xff0c;到一个可以直接运用在生产环境中的web框架。而且所有源码均开…

202485读书笔记|《我还有一片风景要完成》——溪水急着要流向海洋 浪潮却渴望重回土地 弱水长流,我只能尽一瓢饮,世界大千,我只能作一瞬观

202485读书笔记|《我还有一片风景要完成》——溪水急着要流向海洋 浪潮却渴望重回土地 弱水长流&#xff0c;我只能尽一瓢饮&#xff0c;世界大千&#xff0c;我只能作一瞬观 《华语散文温柔的一支笔&#xff1a;张晓风作品集&#xff08;共5册&#xff09;》张晓风&#xff0c…

对https://registry.npm.taobao.org/tyarn的请求失败,原因:证书过期

今天安装yarn时&#xff0c;报错如下&#xff1a; request to https://registry.npm.taobao.org/yarn failed, reason: certificate has expired 原来淘宝镜像过期了&#xff0c;需要重新搞一下 记录一下解决过程&#xff1a; 1.查看当前npm配置 npm config list 2.清…

【Windows 常用工具系列 17 -- windows bat 脚本多参数处理】

请阅读【嵌入式开发学习必备专栏】 文章目录 bat 脚本命令行参数使用示例多参数处理使用示例遍历所有参数 bat 脚本命令行参数 在Windows批处理&#xff08;.bat&#xff09;脚本中接收命令行参数是一个常见的需求&#xff0c;这样的脚本能够根据提供的参数执行不同的操作。命…

【每日刷题】Day76

【每日刷题】Day76 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 561. 数组拆分 - 力扣&#xff08;LeetCode&#xff09; 2. 删除有序链表中重复的元素-II_牛客题霸…

月入稳定还是创业冒险:你的选择决定未来

大家好&#xff0c;我是汇舟问卷。在现在这个环境下&#xff0c;无论是就业还是创业都不好做。在传统就业与创业之间的选择时&#xff0c;我们应避免一概而论或过度推崇某一方向。 事实上&#xff0c;并非所有人都适合创业&#xff0c;对于那些满足于稳定工作&#xff0c;每月…

【前端】Vue项目和微信小程序生成二维码和条形码

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;我是前端菜鸟的自我修养&#xff01;今天给大家分享Vue项目和微信小程序如何生成二维码和条形码&#xff0c;介绍了JsBarcode、wxbarcode等插件&#xff0c;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01…