Android车载应用开发与分析(7)- 车载多媒体(二)- 多媒体应用架构与MediaSession框架

本文详细解读了Android MediaSession框架,包括架构、组件间的通信机制,以及客户端和服务端如何通过MediaBrowser、MediaController和MediaSession进行交互。重点讲解了MediaSession的使用步骤和核心类功能,有助于开发者理解和实现代理车载多媒体应用。

参考资料
媒体应用架构概览 | Android 开发者 | Android Developers
MediaSession | Android Developers
MediaSession框架全解析_qzns木雨的博客-优快云博客_mediasession

1. 多媒体应用架构

1.1 传统应用架构

播放音频或视频的多媒体应用通常由两部分组成:

  • 播放器:接收传入的数据多媒体,并输出音频或视频。可以是MediaPlayer、ExoPlayer或其他Player。
  • 界面:用于显示、控制播放器状态界面。


众所周知,如果需要在应用的后台继续播放音频,我们就需要把Player放置在Service中,那么界面播放器之间通信就非常值得研究了。很长一段时间里,都是由Service提供一个Binder来实现与播放器之间的通信。但是往往下拉的状态栏桌面的Widget都需要与Service之间进行通信,这时候Service就不得不通过实现一系列AIDL接口/广播/ContentProvider完成与其它应用之间的通信,而这些通信手段既增加了应用开发者之间的沟通成本,也增加了应用之间的耦合度。

为了解决上面的问题,Android官方从Android5.0开始提供了MediaSession框架。

1.2 MediaSession 框架

MediaSession框架规范了音视频应用中界面播放器之间的通信接口,实现界面与播放器之间的完全解耦。框架定义了两个重要的类媒体会话媒体控制器,它们为构建多媒体播放器应用提供了一个完善的结构。

媒体会话媒体控制器通过以下方式相互通信:使用与标准播放器操作(播放、暂停、停止等)相对应的预定义回调,以及用于定义应用独有的特殊行为的可扩展自定义调用。

2. MediaSession 介绍

MediaSession框架属于典型的C/S架构,有四个常用的成员类,是整个MediaSession框架流程控制的核心。

2.1 客户端媒体浏览器 - MediaBrowser

媒体浏览器,用来连接MediaBrowserService订阅数据,通过它的回调接口我们可以获取与Service的连接状态以及获取在Service中的音乐库数据。在客户端(也就是上文我们提到的界面,或者说是控制端)中创建。
媒体浏览器不是线程安全的。所有调用都应在构造MediaBrowser的线程上进行。

@RequiresApi(Build.VERSION_CODES.M)
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.1.1 MediaBrowser.ConnectionCallback

用于接收与MediaBrowserService连接事件的回调,在创建MediaBrowser时传入。

@RequiresApi(Build.VERSION_CODES.M)
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()
    }

    override fun onConnectionFailed() {
        super.onConnectionFailed()
    }

    override fun onConnectionSuspended() {
        super.onConnectionSuspended()
    }
}
2.1.2 MediaBrowser.ItemCallback

用于返回MediaBrowser.getItem()的结果。

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {

    override fun onConnected() {
        super.onConnected()
        // ...
        if(mMediaBrowser.isConnected) {
            val mediaId = mMediaBrowser.root
            mMediaBrowser.getItem(mediaId, itemCallback)
        }
    }
}

@RequiresApi(Build.VERSION_CODES.M)
private val itemCallback = object : MediaBrowser.ItemCallback(){

    override fun onItemLoaded(item: MediaBrowser.MediaItem?) {
        super.onItemLoaded(item)
    }

    override fun onError(mediaId: String) {
        super.onError(mediaId)
    }
}
2.1.3 MediaBrowser.MediaItem

包含有关单个媒体项的信息,用于浏览/搜索媒体。MediaItem依赖于服务端提供,因此框架本身无法保证它包含的值都是正确的。

2.1.4 MediaBrowser.SubscriptionCallback

用于订与MediaBrowserServiceMediaBrowser.MediaItem列表变化的回调。

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {

    override fun onConnected() {
        super.onConnected()
        // ...
        if(mMediaBrowser.isConnected) {
            val mediaId = mMediaBrowser.root
            // 需要先取消订阅
            mMediaBrowser.unsubscribe(mediaId)
            // 服务端会调用onLoadChildren
            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)
    }
}

2.2 客户端媒体控制器 - MediaController

媒体控制器,用来向服务端发送控制指令,例如:播放、暂停等等,在客户端中创建。媒体控制器是线程安全的。MediaController还有一个关联的权限android.permission.MEDIA_CONTENT_CONTROL(不是必须加的权限)必须是系统级应用才可以获取,幸运的是车载应用一般都是系统级应用。
MediaController必须在MediaBrowser连接成功后才可以创建。

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {

    override fun onConnected() {
        super.onConnected()
        // ...
        if(mMediaBrowser.isConnected) {
            val sessionToken = mMediaBrowser.sessionToken
            mMediaController = MediaController(applicationContext,sessionToken)
        }
    }
}

2.2.1 MediaController.Callback

用于从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)
    }
    // ...
}
2.2.2 MediaController.PlaybackInfo

保存有关当前播放以及如何处理此会话的音频的信息。使用方式如下:

// 获取当前回话播放的音频信息
val playbackInfo = mMediaController.playbackInfo
2.2.3 MediaController.TransportControls

用于控制会话中媒体播放的接口。这允许客户端向Session发送媒体控制命令。使用方式如下:

private val connectionCallback = object : MediaBrowser.ConnectionCallback() {

    override fun onConnected() {
        super.onConnected()
        // ...
        if(mMediaBrowser.isConnected) {
            val sessionToken = mMediaBrowser.sessionToken
            mMediaController = MediaController(applicationContext,sessionToken)
            // 播放媒体
            mMediaController.transportControls.play()
            // 暂停媒体
            mMediaController.transportControls.pause()
        }
    }
}

2.3 服务端媒体浏览服务 - MediaBrowserService

媒体浏览器服务,继承自ServiceMediaBrowserService属于服务端,也是承载播放器(如MediaPlayer、ExoPlayer等)和MediaSession的容器。
实现MediaBrowserService时会要求复写onGetRootonLoadChildren两个方法。
onGetRoot通过的返回值决定是否允许客户端的MediaBrowser连接到MediaBrowserService
当客户端调用MediaBrowser.subscribe时会触发onLoadChildren方法。

const val FOLDERS_ID = "__FOLDERS__"
const val ARTISTS_ID = "__ARTISTS__"
const val ALBUMS_ID = "__ALBUMS__"
const val GENRES_ID = "__GENRES__"
const val ROOT_ID = "__ROOT__"

class MediaService : MediaBrowserService() {

    override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
    ): BrowserRoot? {
        // 由MediaBrowser.connect触发,可以通过返回null拒绝客户端的连接。
        return BrowserRoot(ROOT_ID, null)
    }

    override fun onLoadChildren(
        parentId: String,
        result: Result<MutableList<MediaBrowser.MediaItem>>
    ) {
    // 由MediaBrowser.subscribe触发
        when (parentId) {
            ROOT_ID -> {
                // 查询本地媒体库
                // ...
                // 将此消息与当前线程分离,并允许稍后进行sendResult调用
  
评论 9
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值