车载媒体应用开发:MediaSession与MediaBrowserService、音频焦点管理、CarMediaSource集成、播放列表管理
车载媒体应用,说白了就是让用户在开车时能安全地听歌、听播客、听有声书。这活儿看着简单,但坑不少。我最早做车载媒体开发时,以为就是把手机端的播放器搬过来,结果被音频焦点、蓝牙连接、CarMediaSource这些玩意儿折腾得够呛。今天咱们就把这块儿掰开揉碎了讲清楚。
核心要点:车载媒体应用不是简单的播放器移植,而是要在Android Automotive框架下,通过MediaSession、MediaBrowserService、音频焦点管理和CarMediaSource四驾马车,构建一个安全、稳定、符合车载规范的媒体体验。
MediaSession与MediaBrowserService:媒体应用的双引擎
先说说MediaSession。这玩意儿是Android媒体框架的核心,它负责管理播放状态、处理媒体控制命令。我习惯把它比作「遥控器」——用户通过它来控制播放、暂停、切歌等操作。
// 创建MediaSession
MediaSessionCompat mediaSession = new MediaSessionCompat(context, "MyMediaSession");
mediaSession.setCallback(new MediaSessionCompat.Callback() {
@Override
public void onPlay() {
// 处理播放请求
startPlayback();
}
@Override
public void onPause() {
// 处理暂停请求
pausePlayback();
}
@Override
public void onSkipToNext() {
// 切到下一首
skipToNextTrack();
}
});
mediaSession.setActive(true);
嗯,这里要注意:MediaSession必须保持活跃状态,否则车载系统无法识别你的应用。我在项目中遇到过一个问题——用户切到后台后,MediaSession被系统回收了,结果方向盘上的切歌按钮完全失效。后来我加了个Service来保活,问题才解决。
MediaBrowserService呢?它负责向外部提供媒体内容目录。说白了,就是告诉系统「我有哪些歌、哪些专辑、哪些播放列表」。车载系统通过它来浏览你的媒体库。
public class MyMediaBrowserService extends MediaBrowserServiceCompat {
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
Bundle rootHints) {
// 验证客户端身份
if (!isValidClient(clientPackageName, clientUid)) {
return null;
}
return new BrowserRoot("root", null);
}
@Override
public void onLoadChildren(String parentId,
Result<List<MediaItem>> result) {
// 加载子节点
List<MediaItem> mediaItems = buildMediaItems(parentId);
result.sendResult(mediaItems);
}
}
你想想看,MediaSession和MediaBrowserService就像一对搭档:一个负责「怎么播」,一个负责「播什么」。两者缺一不可。
音频焦点管理:别让声音打架
车载环境里,音频源太多了——导航、电话、音乐、语音助手……如果不管好音频焦点,就会出现导航播报时音乐还在响,或者电话进来时音乐不暂停的尴尬情况。
音频焦点管理,说白了就是「谁有发言权」。我建议用AudioManager来请求和释放焦点:
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
AudioAttributes attributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
// 请求音频焦点
int result = audioManager.requestAudioFocus(
focusRequest -> {
switch (focusRequest.getAudioFocusChange()) {
case AudioManager.AUDIOFOCUS_GAIN:
// 获得焦点,恢复播放
resumePlayback();
break;
case AudioManager.AUDIOFOCUS_LOSS:
// 永久失去焦点,停止播放
stopPlayback();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// 暂时失去焦点,暂停播放
pausePlayback();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 可以降低音量继续播放
duckVolume();
break;
}
},
attributes,
AudioManager.AUDIOFOCUS_GAIN,
0
);
避坑指南:我曾经在某个项目中,没有处理AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK的情况。结果导航播报时,音乐音量没降,用户完全听不清导航。后来我加了个音量衰减逻辑,问题才解决。记住:车载环境下,导航优先级永远高于媒体。
CarMediaSource集成:让车载系统认识你的媒体
CarMediaSource是Android Automotive特有的API,它让你的媒体应用能被车载系统的媒体中心识别。我刚开始接触时觉得这玩意儿挺玄乎,其实说白了就是注册一个媒体源。
// 在AndroidManifest.xml中声明
<service
android:name=".MyMediaBrowserService"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
// 在代码中注册CarMediaSource
CarMediaSource mediaSource = new CarMediaSource.Builder(context)
.setPackageName(context.getPackageName())
.setClassName(MyMediaBrowserService.class.getName())
.setDisplayName("我的音乐")
.setIcon(Icon.createWithResource(context, R.drawable.ic_music))
.build();
Car car = Car.createCar(context);
MediaCarService mediaCarService = (MediaCarService) car.getCarManager(Car.MEDIA_SERVICE);
mediaCarService.registerMediaSource(mediaSource);
这里有个细节:displayName和icon会显示在车载系统的媒体中心里。我建议用简洁明了的名称,别整花里胡哨的。用户开车时没工夫看你的艺术字。
播放列表管理:别让用户手忙脚乱
播放列表管理,说白了就是「下一首播什么」。车载环境下,用户不能像手机那样频繁操作,所以播放列表的设计要更智能。
我习惯用MediaSession的Queue来管理播放列表:
// 设置播放队列
List<MediaSessionCompat.QueueItem> queue = new ArrayList<>();
for (int i = 0; i < tracks.size(); i++) {
Track track = tracks.get(i);
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setMediaId(track.getId())
.setTitle(track.getTitle())
.setSubtitle(track.getArtist())
.build();
queue.add(new MediaSessionCompat.QueueItem(description, i));
}
mediaSession.setQueue(queue);
mediaSession.setQueueTitle("我的播放列表");
个人经验:我在做播放列表时,发现一个常见问题——用户切到下一首后,如果网络不好,会卡住。我的解决方案是预加载下一首的元数据,并在播放列表里维护一个「待播队列」。这样即使用户快速切歌,也不会出现空白。
另外,播放列表的持久化也很重要。用户可能熄火后重新上车,这时候播放列表应该能恢复。我建议用Room数据库保存播放状态:
@Entity
public class PlaybackState {
@PrimaryKey
public int id = 1;
public String currentMediaId;
public long position;
public List<String> queueIds; // 用Gson序列化
}
知识体系总览
下面这张图,是我梳理的车载媒体应用开发的核心逻辑。你看一眼就能明白各个组件之间的关系:
从这张图你能看到,用户交互通过MediaSession下发指令,MediaSession再协调音频焦点、MediaBrowserService和播放列表管理。最后通过CarMediaSource集成到车载系统的媒体中心。环环相扣,缺一不可。
好了,车载媒体应用开发的核心内容就这些。记住:安全第一,稳定第二,功能第三。别为了炫酷的功能牺牲用户体验。我在项目中见过太多「功能很牛但开车时根本没法用」的案例了。