一、多媒体运用架构

1.1 音视频传统运用架构

通常,传统的播映音频或视频的多媒体运用由两部分组成:

  • 播映器:用于吸收数字媒体并将其呈现为视频和/或音频;
  • 界面:带有用于运转播映器并显现播映器状况(可选)的传输控件;
    Android车载多媒体开发——MediaSession框架

在 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 结构界说了媒体会话和媒体操控器两个重要的类,它们为构建多媒体播映器运用供给了一个完善的技能架构。

媒体会话和媒体操控器经过以下办法彼此通讯:运用与规范播映器操作(播映、暂停、中止等)相对应的预界说回调,以及用于界说运用独有的特别行为的可扩展自界说调用。

Android车载多媒体开发——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 是服务端运用的。由于客户端和服务端是异步通讯,所以采用的许多的回调,因此有许多的回调类,结构示意图如下。
    Android车载多媒体开发——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 后,我们需求复写 onGetRoot和 onLoadChildren两个办法。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

包括阅读器服务回来给客户端的成果集。经过调用 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 结构中心类的通讯进程。

Android车载多媒体开发——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()
        }
        //省掉其他办法
    }
}

上文首要介绍车载自媒体开发与MediaSession结构解析;这仅仅其间一小部分;更多的Android车载技能能够前往[私信]领取,里边内容如下脑图所示:(需求能够参考,当做辅导资料)

Android车载多媒体开发——MediaSession框架

文末

车载体系开发,在这几年岗位逐步增多。Android转岗车载开发是个很好的发展方向。轿车的遍及同比10年增长300%以上;近几年的新能源轿车逐步遍及,车载开发人员更是需求很大;遍及短少人才。

感觉Android商场下滑的凶猛,筛选人员逐步增多。一定要眼看未来,才没有近忧。