android-UniversalMusicPlayer 学习笔记(一)

        android-UniversalMusicPlayer (统一简称UMP) 这个开源项目展示了如何实现一个横跨各种Android平台的完整音乐播放器。其中不但介绍了标准的播放器前后台实现,还包括了如何横跨Android Auto, Android Wear, Android TV等平台的提供一致性用户体验。

        本文主要通过对UMP 项目的学习,弄懂弄清播放控制逻辑层 (主要是 MediaSession 框架)的基本使用。


一、UMP基本组成

(1)架构与项目组成

        使用 Android Architecture Components (LiveData \ LifeCycle-ViewModel等等)架构,主要分为两个Module (app(主)和 mediaModule)

(2)开发语言 

         Kotlin (目前Google 最新的Demo 都采用Kotlin 语言 ,如果还没学习Kotlin的小伙伴得抓紧)

(3)底层播放器

         ExoPlayer 

 ExoPlayer is an application level media player for Android. It provides an alternative to Android’s MediaPlayer API for playing audio and video both locally and over the Internet. ExoPlayer supports features not currently supported by Android’s MediaPlayer API, including DASH and SmoothStreaming adaptive playbacks. Unlike the MediaPlayer API, ExoPlayer is easy to customize and extend, and can be updated through Play Store application updates.


二、MediaSession 框架相关点简介

        MediaSession 框架(专门用来解决媒体播放时界面和Service通讯的问题,实现了数据管理播放控制UI更新等功能)主要包含以下几个方面 ,MediaBrowser 、MediaBrowserService、MediaSession、MediaController ,分别如下:

图1 MediaSession 框架核心元素之间的关系图(来自网络)

(1)MediaBrowser

       媒体浏览器,用来连接MediaBrowserService和订阅数据,通过它的回调接口我们可以获取和Service的连接状态以及获取在Service中异步获取的音乐库数据。媒体浏览器一般创建于客户端(可以理解为各个终端负责控制音乐播放的界面)中MediaBrowser 从连接服务到向其订阅数据大致流程 :connect -> onConnected -> subscribe -> onChildrenLoaded 

(2)MediaBrowserService

        浏览器服务,提供onGetRoot(控制客户端媒体浏览器的连接请求,通过返回值决定是否允许该客户端连接服务)和onLoadChildren(媒体浏览器向Service发送数据订阅时调用,一般在这执行异步获取数据的操作, 最后将数据发送至媒体浏览器的回调接口中)这两个抽象方法同时MediaBrowserService还作为承载媒体播放器(如MediaPlayer、ExoPlayer等)和MediaSession的容器。

       MediaBrowserService  总结起来就两个作用:1)播放后台服务 ;2)客户端中获取音乐数据的服务,所有的音乐数据都通过该服务与服务端进行交互获取(或者直接获取手机中的本地音乐数据)。

(3)MediaSession

        媒体会话,即受控端,通过设置MediaSessionCompat.Callback回调来接收媒体控制器MediaController发送的指令,当收到指令时会触发Callback中各个指令对应的回调方法(回调方法中会执行播放器相应的操作,如播放、暂停等)。

       Session一般在Service.onCreate方法中创建,最后需调用setSessionToken方法设置用于和控制器配对的令牌并通知浏览器连接服务成功。

(4)MediaController

       媒体控制器,在客户端中开发者不仅可以使用控制器向Service中的受控端发送指令,还可以通过设置MediaControllerCompat.Callback回调方法接收受控端的状态,从而根据相应的状态刷新界面UI。MediaController的创建需要受控端的配对令牌,因此需在浏览器成功连接服务的回调执行创建的操作。


三、UMP 项目实战剖析

         在第二部分简单介绍了一些MediaSession 框架四大元素 :MediaBrowser 、MediaBrowserService、MediaSession 及MediaController ,现在结合UMP项目进行展开细讲。

(1)MediaBrowser

          connect -> onConnected -> subscribe -> onChildrenLoaded  过程分析 :

      1)  MediaBrowser::connect

        在MainActivity::onCreate()中绑定MusicService(MediaBrowserServiceCompat实例)并创建MediaSessionConnection实例

图2 绑定MusicService 并创建MediaSessionConnection实例

    在MediaSessionConnection中创建 MediaBrowser ,执行connect()

图3 创建MediaBrowser 并执行connect()

2) MediaBrowserConnectionCallback::onConnected()

      MediaBrowser::connect 连接成功后会调用 MediaBrowserConnectionCallback::onConnected(),在onConnected() 中做了两件事情 :其一 实例化MeidaController ;其二 更新 isConnected 状态 。

图4 执行MediaBrowserConnectionCallback::onConnected()

isConnected 使用了LiveData ,一旦值发生变化,就会执行下面定(MainActivityViewModel)的回调接口,进行下一步操作: 重新获取 mRootId(若Service允许客户端连接,则返回结果不为null,其值为数据内容层次结构的根ID;若拒绝连接,则返回null)

图5 isConnected 绑定

       其中,mediaSessionConnection.rootMediaId 就是 MediaBrowser 的mRootId,而且获取mRootId时 MediaBrowser要求一定要连接上,不然会抛IllegalStateException。mRootId 设置是在 重写MediaBrowserServiceCompat ::onGetRoot 中构建的 BrowserRoot(Called to get the root information for browsing by a particular client ,用于区分

        在上面《MediaSession 框架相关点简介》已介绍 MediaBrowser是通过订阅方式向Service请求数据的。 而发起订阅请求需要两个参数,其一为mRootId ;而如果该mRootId已经被其他Browser实例订阅,则需要在订阅之前取消mRootId的订阅者( 注意 :订阅一个已被订阅的mRootId时会取代原Browser的订阅回调,但却无法触发onChildrenLoaded回调

      关于BrowserRoot 会在后续MediaBrowserService 部分讲解。

3)MediaBrowser:: subscribe

       在MediaItemFragment::onActivityCreated 初始化MediaItemItemFragmentViewModel  利用MediaBrowser 向Service订阅服务 。

图6 MediaBrowser:: subscribe

    从Service 返回订阅数据,更新List 

图7 从Service 返回数据 更新UI 

4)onChildrenLoaded 

      Service 通过 onChildrenLoaded  将数据返回给Client (MediaBrowser),这部分也在后续MediaBrowserService 部分讲解。

(2) MediaBrowserService 

1)注册MusicService (MediaBrowserService )

图8 注册MediaBrowserService 

2)播放后台服务

        MediaBrowserService 作为一个后台播放服务却不是通过其自身直接实现的,而是通过MediaSession媒体会话这个类来实现的。在使用过程中媒体会话会与该服务关联起来,所有的播放操作都交由MediaSession实现。

       在MusicService::onCreate() 中创建MediaSession ,设置sessionToken , 设置token后会触发MediaBrowserCompat.ConnectionCallback的回调方法。

图9 在MusicService中创建MediaSession

 另外,由于播放器使用的是ExoPlayer ,它会帮忙管理MediaSession。

3)在MusicService中重写onGetRoot(类似添加白名单)和 onLoadChildren(返回数据)

图10 重写onGetRoot()
图11  onLoadChildren 

        上面注释写得比较清楚,不展开讲。另外,UMP中播放器的Service是MusicService 继承自MediaBrowserServiceCompat,选择的IBinder方案是Messenger方案(Messenger 与AIDL异同点

图12 采用Messenger方案

4)其他

    BecomingNoisyReceiver :耳机插拔监听广播

   MediaControllerCallback :数据状态变化 、播放状态 及 相关通知更新等


(3)MediaController 

        MediaController 的创建依赖于Session的配对令牌,当Browser和BrowserService连接成功(在前面已经介绍MediaBrowserConnectionCallback::onConnected() 中创建)我们就可以通过Browser拿到这个令牌了。控制器创建后,我们就可以通过MediaController.getTransportControls的方法发送播放指令,同时也可以注册MediaControllerCompat.Callback回调接收播放状态,用以刷新界面UI(播放状态 和数据状态)。

图 13 MediaController 创建

  MediaController 核心  :mediaBrowser.sessionToken 和 MediaControllerCallback , MediaControllerCallback 实现如下 :

图 14 MediaControllerCallback 具体实现

  刷新UI 

图 15 UI 刷新

四、播放实例讲解

       从上面介绍知,在已连接 有数据情况下,人操作播放器播放按钮,主要涉及 MediaController 和 MediaSession ,下面已播放为例来说明整个播放空中流程。

图16 播放过程 (图摘抄自网络)

一般的播放流程包括 :

(1)获取MediaController对象

        连接成功后创建MediaController对象

图17 创建MediaController

(2)通过MediaController拿到TransportControler对象

图18 获取TransportControler对象

(3)通过TransportControler发送play()的指令

图19 TransportControler发送play() 等操作

(4)MediaSession.Callback收到指令回调onPlay()方法

         transportControls.play( )  ->  

         MediaControllerCompatApi21.TransportControls.play(mControlsObj) ->

图20   transportControls.play( )

        对应对应MediaSessionRecord::ISessionController.Stub 实现  ,里面 play( )方法  调用  ISessionCallback ,ISessionCallback  接口如下 :

图21 ISessionCallback

      到此, transportControls 与MediaSession.Callback 就对接上了 ,执行onPlay 方法

图22 MediaSession::dispatchPlay

(5)MediaController.Callback收到状态改变

图23 MediaController.Callback收到状态改变

(6)刷新UI界面

      更新播放状态,最终更新UI界面

图24  更新播放状态

五、遇到的问题

       在导入UMP项目时发现不能正常编译 ,AS报错如下 :

图25 报错信息

       查看extension-mediasession 版本是2.8.4,寻根到底发现  exoplayer2.ext 库不存在。

 图26 extension-mediasession 版本

     解决办法 :升级库版本 到2.9.1,对应修改如下:

图 27 修复版本升级兼容性问

相关资料 :

(1)android-UniversalMusicPlayer 

(2)Android 跨进程双向通信(Messenger与AIDL)详解

(3)Android - 基于MediaSession的音乐播放流程

(4)关于媒体浏览器服务(MediaBrowserService)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,757评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,478评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,540评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,593评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,903评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,329评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,659评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,383评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,055评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,337评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,864评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,227评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,820评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,999评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,750评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,365评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,260评论 2 258

推荐阅读更多精彩内容