7、媒体应用开发:MediaBrowserService与MediaSession、CarMediaSource、播放列表管理、音频焦点处理
各位同学,今天我们来聊聊车载媒体应用开发的核心部分。说实话,这部分内容在Android Automotive OS里,可以说是最考验基本功的地方。我在做车载项目时,踩过的坑有一半都跟媒体相关。你想想看,用户在车上听歌、听播客,体验好不好,全看这一块做得怎么样。
7.1 MediaBrowserService:媒体的“后台管家”
MediaBrowserService,说白了就是你的媒体应用在系统里的“门面”。它负责向车载系统暴露你的媒体内容。为什么需要它?因为车载系统需要知道你的应用里有什么歌、什么专辑,才能让用户去浏览和选择。
我个人习惯把MediaBrowserService理解成一个“内容提供者”。它不负责播放,只负责告诉系统:“嘿,我这里有哪些媒体资源”。
来看一个简单的实现示例:
class MyMediaService : MediaBrowserServiceCompat() {
override fun onGetRoot(
clientPackageName: String,
clientUid: Int,
rootHints: Bundle?
): BrowserRoot? {
// 检查客户端是否合法
if (!isValidClient(clientPackageName, clientUid)) {
return null
}
return BrowserRoot(MEDIA_ROOT_ID, null)
}
override fun onLoadChildren(
parentId: String,
result: Result<List<MediaItem>>
) {
result.sendResult(buildMediaItems(parentId))
}
}
嗯,这里要注意:onGetRoot()返回null的话,客户端就完全连不上你的服务。我在项目中遇到过,有些开发者直接返回一个固定的root,不做任何校验,结果被其他应用随意访问媒体数据。这其实是个安全隐患。
7.2 MediaSession:播放控制的“遥控器”
MediaSession和MediaBrowserService是搭档。如果说MediaBrowserService是“后台管家”,那MediaSession就是“遥控器”。它负责接收用户的播放指令——播放、暂停、下一首、上一首,然后把这些指令传递给实际的播放器。
为什么需要分开?因为职责不同。MediaBrowserService管“浏览”,MediaSession管“控制”。这样设计的好处是,系统可以只跟MediaSession交互,而不需要知道你的播放器具体怎么实现的。
代码示例:
class MyMediaSession(private val context: Context) {
private val session = MediaSessionCompat(context, TAG)
private val callback = object : MediaSessionCompat.Callback() {
override fun onPlay() {
// 开始播放
player.play()
session.setPlaybackState(buildPlaybackState(PlaybackStateCompat.STATE_PLAYING))
}
override fun onPause() {
player.pause()
session.setPlaybackState(buildPlaybackState(PlaybackStateCompat.STATE_PAUSED))
}
override fun onSkipToNext() {
player.skipToNext()
}
}
fun init() {
session.setCallback(callback)
session.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
}
}
7.3 CarMediaSource:车载专用的媒体源
CarMediaSource是Android Automotive OS专门为车载场景设计的媒体源接口。它跟普通的MediaSource有什么区别?说白了,它更懂车载的需求。
举个例子:用户在车上搜索歌曲,CarMediaSource可以支持增量搜索——用户输入“周”,系统就开始返回“周杰伦”的结果,而不是等用户输完才搜索。这种体验在开车时特别重要,因为用户分心的时间越短越好。
- 支持分页加载:车载屏幕空间有限,一次加载太多内容反而不好
- 支持搜索过滤:通过SearchFilter对象传递搜索条件
- 支持媒体项分组:按专辑、艺术家、播放列表等维度组织
我记得第一次用CarMediaSource时,被它的分页机制搞晕了。它不像普通列表那样一次性返回所有数据,而是通过onLoadChildren()配合分页参数,一页一页地加载。这样做的好处是,即使用户的媒体库有上万首歌,也不会一次性占满内存。
7.4 播放列表管理:别小看这个“队列”
播放列表管理,听起来简单,做起来坑不少。我见过太多应用,播放列表逻辑写得一团糟,导致切歌时闪退、列表顺序错乱、重复播放等问题。
在Android Automotive OS中,播放列表通常通过MediaSessionCompat.QueueItem来管理。每个QueueItem代表一首歌,包含一个唯一的ID和对应的MediaDescription。
播放列表管理的核心逻辑:
class PlaylistManager {
private val queue = mutableListOf<MediaSessionCompat.QueueItem>()
private var currentIndex = 0
fun addToQueue(mediaId: String, title: String) {
val description = MediaDescriptionCompat.Builder()
.setMediaId(mediaId)
.setTitle(title)
.build()
val item = MediaSessionCompat.QueueItem(
description,
generateUniqueId() // 必须唯一
)
queue.add(item)
}
fun getNextItem(): MediaSessionCompat.QueueItem? {
if (queue.isEmpty()) return null
currentIndex = (currentIndex + 1) % queue.size
return queue[currentIndex]
}
fun getPreviousItem(): MediaSessionCompat.QueueItem? {
if (queue.isEmpty()) return null
currentIndex = if (currentIndex - 1 < 0) queue.size - 1 else currentIndex - 1
return queue[currentIndex]
}
}
7.5 音频焦点处理:别跟其他应用“抢麦”
音频焦点,是车载媒体应用开发中最容易被忽视、但又最重要的一环。你想想看,用户在车上可能同时开着导航、接听电话、听音乐。如果每个应用都自顾自地播放,那车里就乱套了。
Android Automotive OS的音频焦点机制,说白了就是一套“谁说话,谁闭嘴”的规则。当你的应用失去音频焦点时,应该暂停播放;当重新获得焦点时,再恢复播放。
| 焦点类型 | 说明 | 使用场景 |
|---|---|---|
| AUDIOFOCUS_GAIN | 长期持有焦点 | 音乐播放 |
| AUDIOFOCUS_GAIN_TRANSIENT | 短暂持有焦点 | 导航语音 |
| AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK | 短暂持有,允许其他应用降低音量 | 通知提示音 |
处理音频焦点的代码:
class AudioFocusHelper(private val context: Context) {
private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
private val focusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
when (focusChange) {
AudioManager.AUDIOFOCUS_LOSS -> {
// 永久失去焦点,暂停播放
player.pause()
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
// 暂时失去焦点,暂停播放
player.pause()
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
// 可以降低音量继续播放
player.setVolume(0.3f)
}
AudioManager.AUDIOFOCUS_GAIN -> {
// 重新获得焦点,恢复播放
player.setVolume(1.0f)
player.play()
}
}
}
fun requestFocus(): Int {
return audioManager.requestAudioFocus(
focusChangeListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
)
}
}
7.6 知识体系总览
下面这张图,我把本章的核心知识点串起来了。你可以看到,MediaBrowserService和MediaSession是“双核”,一个管浏览,一个管控制。CarMediaSource是车载专用的数据源,播放列表管理负责维护播放队列,音频焦点处理则确保多个应用和谐共存。
好了,这一章的内容就到这里。媒体应用开发在车载系统里确实是个重头戏,但只要你把MediaBrowserService、MediaSession、CarMediaSource、播放列表管理和音频焦点处理这几个核心组件搞明白,大部分场景都能应对。记住我在项目中踩过的那些坑,你就能少走很多弯路。
公众号:蓝海资料掘金营,微信deep3321