一、多媒体利用架构

1.1 音视频传统利用架构

通常,传统的播放音频或视频的多媒体利用由两局部组成:

  • 播放器:用于排汇数字媒体并将其出现为视频和/或音频;
  • 界面:带有用于运行播放器并显示播放器状态(可选)的传输控件;

在 Android 利用开发中,从零开始构建本人的播放器还能够思考以下选项:

  • MediaPlayer :提供准系统播放器的基本功能,反对最常见的音频/视频格式和数据源。
  • ExoPlayer :一个提供低层级 Android 音频 API 的凋谢源代码库。ExoPlayer 反对 DASH 和 HLS 流等高性能性能,这些性能在 MediaPlayer 中未提供。
    家喻户晓,如果要在利用的后盾持续播放音频,最常见的形式就是把 Player 搁置在 Service 中,Service 提供一个 Binder 来实现界面播放器之间的通信。然而,如果遇到锁屏时,如果要与 Service 之间进行通信就不得不用到 AIDL 接口/播送/ContentProvider 来实现与其它利用之间的通信,而这些通信伎俩既减少了利用开发者之间的沟通老本,也减少了利用之间的耦合度。为了解决下面的问题,Android 官网从 Android5.0 开始提供了 MediaSession 框架。

1.2 MediaSession 框架

MediaSession 框架标准了音视频利用中界面与播放器之间的通信接口,实现界面与播放器之间的齐全解耦。MediaSession 框架定义了媒体会话和媒体控制器两个重要的类,它们为构建多媒体播放器利用提供了一个欠缺的技术架构。

媒体会话和媒体控制器通过以下形式互相通信:应用与规范播放器操作(播放、暂停、进行等)绝对应的预约义回调,以及用于定义利用独有的非凡行为的可扩大自定义调用。

媒体会话

媒体会话负责与播放器的所有通信。它会对利用的其余局部暗藏播放器的 API。零碎只能从管制播放器的媒体会话中调用播放器。

会话会保护播放器状态(播放/暂停)的示意模式以及播放内容的相干信息。会话能够接管来自一个或多个媒体控制器的 回调 。这样,利用的界面以及运行 Wear OS 和 Android Auto 的配套设施便能够管制您的播放器。响应回调的逻辑必须保持一致。无论哪个客户端利用发动了回调,对 MediaSession 回调的响应都是雷同的。

媒体控制器

媒体控制器的作用是隔离界面,界面的代码只与媒体控制器(而非播放器自身)通信,媒体控制器会将传输管制操作转换为对媒体会话的回调。每当会话状态发生变化时,它也会接管来自媒体会话的回调,这为自动更新关联界面提供了一种机制,媒体控制器一次只能连贯到一个媒体会话。

当您应用媒体控制器和媒体会话时,就能够在运行时部署不同的接口和/或播放器。这样一来,您能够依据运行利用的设施的性能独自更改该利用的外观和/或性能。

二、MediaSession

2.1 概述

MediaSession 框架次要是用来解决音乐界面和服务之间的通信问题,属于典型的 C/S 架构,有四个罕用的成员类,别离是 MediaBrowser、MediaBrowserService、MediaController 和 MediaSession,是整个 MediaSession 框架流程管制的外围。

  • MediaBrowser:媒体浏览器,用来连贯媒体服务 MediaBrowserService 和订阅数据,在注册的回调接口中能够获取到 Service 的连贯状态、获取音乐数据,个别在客户端中创立。
  • MediaBrowserService:媒体服务,它有两个要害的回调函数,onGetRoot(管制客户端媒体浏览器的连贯申请,返回值中决定是否容许连贯),onLoadChildren(媒体浏览器向服务器发送数据订阅申请时会被调用,个别在这里执行异步获取数据的操作,而后在将数据发送回媒体浏览器注册的接口中)。
  • MediaController:媒体控制器,在客户端中工作,通过控制器向媒体服务器发送指令,而后通过 MediaControllerCompat.Callback 设置回调函数来承受服务端的状态。MediaController 创立时须要受控端的配对令牌,因而须要在浏览器连贯胜利后才进行 MediaController 的创立。
  • MediaSession:媒体会话,受控端,通过设置 MediaSessionCompat.Callback 回调来接管 MediaController 发送的指令,收到指令后会触发 Callback 中的回调办法,比方播放暂停等。Session 个别在 Service.onCreate 办法中创立,最初需调用 setSessionToken 办法设置用于和控制器配对的令牌并告诉浏览器连贯服务胜利。
    其中,MediaBrowser 和 MediaController 是客户端应用的,MediaBrowserService 和 MediaSession 是服务端应用的。因为客户端和服务端是异步通信,所以采纳的大量的回调,因而有大量的回调类,框架示意图如下。

2.2 MediaBrowser

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.2.1 MediaBrowser.ConnectionCallback

连贯状态回调,当 MediaBrowser 向 service 发动连贯申请后,申请后果将在这个 callback 中返回,获取到的 meidaId 对应服务端在 onGetRoot 函数中设置的 mediaId,如果连贯胜利那么就能够做创立媒体控制器之类的操作了。

@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.2.2 MediaBrowser.ItemCallback

媒体控制器是负责向 service 发送例如播放暂停之类的指令的,这些指令的执行后果将在这个回调中返回,可重写的函数有很多,比方播放状态的扭转,音乐信息的扭转等。

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.2.3 MediaBrowser.MediaItem

蕴含无关单个媒体项的信息,用于浏览/搜寻媒体。MediaItem依赖于服务端提供,因而框架自身无奈保障它蕴含的值都是正确的。

2.2.4 MediaBrowser.SubscriptionCallback

连贯胜利后,首先须要的是订阅服务,同样还须要注册订阅回调,订阅胜利的话服务端能够返回一个音乐信息的序列,能够在客户端展现获取的音乐列表数据。例如,上面是订阅 MediaBrowserService 中 MediaBrowser.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.3 MediaController

媒体控制器,用来向服务端发送控制指令,例如:播放、暂停等等,在客户端中创立。媒体控制器是线程平安的,MediaController 还有一个关联的权限 android.permission.MEDIA_CONTENT_CONTROL(不是必须加的权限)必须是零碎级利用才能够获取,侥幸的是车载利用个别都是零碎级利用。

同时,MediaController必须在 MediaBrowser 连贯胜利后才能够创立。 所以,创立 MediaController 的代码如下:

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

用于从 MediaSession 接管回调,所以应用的时候须要将 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)       ... //回调办法接管受控端的状态,从而依据相应的状态刷新界面 UI           }        override fun onExtrasChanged(extras: Bundle?) {        super.onExtrasChanged(extras)    }    // ...}
2.3.2 MediaController.PlaybackInfo

获取以后播放的音频信息,蕴含播放的进度、时长等。

2.3.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.4 MediaBrowserService

媒体浏览器服务,继承自 Service,MediaBrowserService 属于服务端,也是承载播放器(如 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 -> {                // 查问本地媒体库                result.detach()                result.sendResult()            }            FOLDERS_ID -> {            }            ALBUMS_ID -> {            }            ARTISTS_ID -> {            }            GENRES_ID -> {            }            else -> {            }        }    }}

最初,还须要在 manifest 中注册这个 Service。

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

返回蕴含浏览器服务首次连贯时须要发送给客户端的信息。构造函数如下:

MediaBrowserService.BrowserRoot(String rootId, Bundle extras)

除此之外,还有两个办法:

  • getExtras():获取无关浏览器服务的附加信息
  • getRootId():获取用于浏览的根 ID

    2.4.2 MediaBrowserService.Result<T>

蕴含浏览器服务返回给客户端的后果集。通过调用 sendResult()将后果返回给调用方,然而在此之前须要调用 detach()

  • detach():将此音讯与以后线程拆散,并容许稍后进行调用 sendResult(T)
  • sendResult():将后果发送回调用方。

    2.5 MediaSession

媒体会话,即受控端。通过设定 MediaSession.Callback回调来接管媒体控制器 MediaController发送的指令,如管制音乐的【上一曲】、【下一曲】等。

创立 MediaSession后还须要调用 setSessionToken()办法设置用于和**控制器配对的令牌,应用形式如下:

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() {    private lateinit var mediaSession: MediaSession;    override fun onCreate() {        super.onCreate()        mediaSession = MediaSession(this, "TAG")        mediaSession.setCallback(callback)        sessionToken = mediaSession.sessionToken    }    // 与 MediaController.transportControls 中的大部分办法都是一一对应的    // 在该办法中实现对 播放器 的管制,    private val callback = object : MediaSession.Callback() {        override fun onPlay() {            super.onPlay()            // 解决 播放器 的播放逻辑。            // 车载利用的话,别忘了解决音频焦点        }        override fun onPause() {            super.onPause()        }    }        override fun onGetRoot(        clientPackageName: String,        clientUid: Int,        rootHints: Bundle?    ): BrowserRoot? {        Log.e("TAG", "onGetRoot: $rootHints")        return BrowserRoot(ROOT_ID, null)    }    override fun onLoadChildren(        parentId: String,        result: Result<MutableList<MediaBrowser.MediaItem>>    ) {        result.detach()        when (parentId) {            ROOT_ID -> {                result.sendResult(null)            }            FOLDERS_ID -> {            }            ALBUMS_ID -> {            }            ARTISTS_ID -> {            }            GENRES_ID -> {            }            else -> {            }        }    }    override fun onLoadItem(itemId: String?, result: Result<MediaBrowser.MediaItem>?) {        super.onLoadItem(itemId, result)        Log.e("TAG", "onLoadItem: $itemId")    }}
2.5.1 MediaSession.Callback

接管来自客户端或零碎的媒体按钮、传输控件和命令,入【上一曲】、【下一曲】。与 MediaController.transportControls 中的大部分办法都是一一对应的。

private val callback = object : MediaSession.Callback() {        override fun onPlay() {        super.onPlay()               if (!mediaSession.isActive) {            mediaSession.isActive = true        }                //更新播放状态.        val state = PlaybackState.Builder()            .setState(                PlaybackState.STATE_PLAYING,1,1f            )            .build()        mediaSession.setPlaybackState(state)    }        override fun onPause() {        super.onPause()    }        override fun onStop() {        super.onStop()    }}
2.5.2 MediaSession.QueueItem

播放队列一部分的单个我的项目,相比 MediaMetadata,多了一个 ID 属性。罕用的办法有:

  • getDescription():返回介质的阐明,蕴含媒体的根底信息,如题目、封面等。
  • getQueueId():获取此我的项目的队列 ID。

    2.5.3 MediaSession.Token

示意正在进行的会话,能够通过会话所有者传递给客户端,以容许客户端与服务端之间建设通信。

2.6 PlaybackState

用于承载播放状态的类。如以后播放地位和以后管制性能。在 MediaSession.Callback更改状态后须要调用 MediaSession.setPlaybackState把状态同步给客户端。

private val callback = object : MediaSession.Callback() {    override fun onPlay() {        super.onPlay()        // 更新状态        val state = PlaybackState.Builder()            .setState(                PlaybackState.STATE_PLAYING,1,1f            )            .build()        mediaSession.setPlaybackState(state)    }}
2.6.1 PlaybackState.Builder

PlaybackState.Builder 次要用来创立 PlaybackState 对象,创立它应用的是建造者模式,如下。

PlaybackState state = new PlaybackState.Builder()        .setState(PlaybackState.STATE_PLAYING,                mMediaPlayer.getCurrentPosition(), PLAYBACK_SPEED)        .setActions(PLAYING_ACTIONS)        .addCustomAction(mShuffle)        .setActiveQueueItemId(mQueue.get(mCurrentQueueIdx).getQueueId())        .build();
2.6.2 PlaybackState.CustomAction

CustomActions可用于通过将特定于应用程序的操作发送给 MediaControllers,这样就能够扩大规范传输控件的性能。

CustomAction action = new CustomAction        .Builder("android.car.media.localmediaplayer.shuffle",        mContext.getString(R.string.shuffle),        R.drawable.shuffle)        .build();PlaybackState state = new PlaybackState.Builder()        .setState(PlaybackState.STATE_PLAYING,                mMediaPlayer.getCurrentPosition(), PLAYBACK_SPEED)        .setActions(PLAYING_ACTIONS)        .addCustomAction(action)        .setActiveQueueItemId(mQueue.get(mCurrentQueueIdx).getQueueId())        .build();

常见的 API,有如下一些:

  • getAction():返回 CustomAction 的 action
  • getExtras():返回附加项,这些附加项提供无关操作的其余特定于应用程序的信息
  • getIcon():返回 package 中图标的资源 ID
  • getName():返回此操作的显示名称

    2.7 MediaMetadata

蕴含无关我的项目的根底数据,例如题目、艺术家等。个别须要服务端从本地数据库或远端查问出原始数据在封装成 MediaMetadata 再通过 MediaSession.setMetadata(metadata)返回到客户端MediaController.Callback.onMetadataChanged中。

常见的 API 有如下:

  • containsKey():如果给定的 key 蕴含在元数据中,则返回 true。
  • describeContents():形容此可打包实例的封送解决示意中蕴含的非凡对象的品种。
  • getBitmap():返回给定的 key 的 Bitmap,如果给定 key 不存在位图,则返回 null。
  • getBitmapDimensionLimit():获取创立此元数据时位图的宽度/高度限制
  • getDescription():获取此元数据的简略阐明以进行显示。
  • keySet():返回一个 Set,其中蕴含在此元数据中用作 key 的字符串。

    三、示例

下图是 MediaSession 框架外围类的通信过程。


能够看到,在 MediaSession 框架中,首先客户端通过 MediaBrowserService 连贯到 MediaBrowserService,MediaBrowserService 承受到申请之后解决相干的申请,MediaSession 管制播放状态,并将状态同步给客户端,客户端最初 MediaController 进行相应的操作。

客户端示例代码:

class MainActivity : AppCompatActivity() {    private lateinit var mMediaBrowser: MediaBrowser    private lateinit var mMediaController: MediaController    @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);        // 连贯到 MediaBrowserService,会触发 MediaBrowserService 的 onGetRoot 办法。        mMediaBrowser.connect()        findViewById<Button>(R.id.btn_play).setOnClickListener {            mMediaController.transportControls.play()        }    }        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)                // 获取根 mediaId                val rootMediaId = mMediaBrowser.root                // 获取根 mediaId 的 item 列表,会触发 MediaBrowserService.onLoadItem 办法                mMediaBrowser.getItem(rootMediaId,itemCallback)                mMediaBrowser.unsubscribe(rootMediaId)                // 订阅服务端 media item 的扭转,会触发 MediaBrowserService.onLoadChildren 办法                mMediaBrowser.subscribe(rootMediaId, subscribeCallback)            }        }    }        private val controllerCallback = object : MediaController.Callback() {        override fun onPlaybackStateChanged(state: PlaybackState?) {            super.onPlaybackStateChanged(state)            Log.d("TAG", "onPlaybackStateChanged: $state")            when(state?.state){                PlaybackState.STATE_PLAYING ->{                    // 解决 UI                }                PlaybackState.STATE_PAUSED ->{                    // 解决 UI                }                // 还有其它状态须要解决            }        }                //音频信息,音量        override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {            super.onAudioInfoChanged(info)            val currentVolume = info?.currentVolume            // 显示在 UI 上        }                override fun onMetadataChanged(metadata: MediaMetadata?) {            super.onMetadataChanged(metadata)            val artUri = metadata?.getString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI)            // 显示 UI 上        }                override fun onSessionEvent(event: String, extras: Bundle?) {            super.onSessionEvent(event, extras)            Log.d("TAG", "onSessionEvent: $event")        }        // ...    }        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)        }    }        private val itemCallback = object : MediaBrowser.ItemCallback() {        override fun onItemLoaded(item: MediaBrowser.MediaItem?) {            super.onItemLoaded(item)        }        override fun onError(mediaId: String) {            super.onError(mediaId)        }    }}

上面是服务端的示例源码,次要用于解决客户端的申请,并将后果返回给客户端。

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() {    // 管制是否容许客户端连贯,并返回 root media id 给客户端    override fun onGetRoot(        clientPackageName: String,        clientUid: Int,        rootHints: Bundle?    ): BrowserRoot? {        Log.e("TAG", "onGetRoot: $rootHints")        return BrowserRoot(ROOT_ID, null)    }    // 解决客户端的订阅信息    override fun onLoadChildren(        parentId: String,        result: Result<MutableList<MediaBrowser.MediaItem>>    ) {        Log.e("TAG", "onLoadChildren: $parentId")        result.detach()        when (parentId) {            ROOT_ID -> {                result.sendResult(null)            }            FOLDERS_ID -> {            }            ALBUMS_ID -> {            }            ARTISTS_ID -> {            }            GENRES_ID -> {            }            else -> {            }        }    }    override fun onLoadItem(itemId: String?, result: Result<MediaBrowser.MediaItem>?) {        super.onLoadItem(itemId, result)        Log.e("TAG", "onLoadItem: $itemId")        // 依据 itemId,返回对用 MediaItem        result?.detach()        result?.sendResult(null)    }    private lateinit var mediaSession: MediaSession;    override fun onCreate() {        super.onCreate()        mediaSession = MediaSession(this, "TAG")        mediaSession.setCallback(callback)        // 设置 token        sessionToken = mediaSession.sessionToken    }    // 与 MediaController.transportControls 中的办法是一一对应的。    // 在该办法中实现对 播放器 的管制,    private val callback = object : MediaSession.Callback() {        override fun onPlay() {            super.onPlay()            // 解决 播放器 的播放逻辑。            // 车载利用的话,别忘了解决音频焦点            Log.e("TAG", "onPlay:")            if (!mediaSession.isActive) {                mediaSession.isActive = true            }            // 更新状态            val state = PlaybackState.Builder()                .setState(                    PlaybackState.STATE_PLAYING, 1, 1f                )                .build()            mediaSession.setPlaybackState(state)        }        override fun onPause() {            super.onPause()        }        override fun onStop() {            super.onStop()        }        //省略其余办法    }}