CarPlay媒体应用开发:CPNowPlayingTemplate、CPListTemplate与音乐播放控制
好,咱们今天来聊聊CarPlay媒体应用开发里最核心的几个模板。说实话,这部分内容我当年刚接触时也绕了不少弯路。CPNowPlayingTemplate、CPListTemplate,再加上音乐播放控制,这三个东西组合起来,基本就撑起了一个音乐类App在车机上的全部体验。
我个人习惯把CarPlay的媒体应用分成两层来看:一层是“浏览”,一层是“播放”。浏览靠CPListTemplate,播放靠CPNowPlayingTemplate。两者之间通过播放控制来串联。嗯,咱们一个一个拆开讲。
CPListTemplate:你的内容目录
CPListTemplate说白了就是CarPlay上的列表页。你想想看,用户在车上想听歌,第一步肯定是找歌、找专辑、找播放列表。这个模板就是干这个的。
它的用法其实很直接。你创建一个CPListTemplate实例,设置好标题,然后往里塞CPListItem。每个CPListItem代表一行,可以显示标题、副标题、图片,甚至还能带个进度条——比如播客的播放进度。
核心要点:CPListTemplate是用户操作的起点。它的响应速度直接影响用户体验。我在项目中遇到过列表加载太慢导致用户反复点击的情况,后来发现是数据源没有做预加载。
// 创建列表模板
let listTemplate = CPListTemplate(title: "我的音乐", sections: [])
// 创建列表项
let item = CPListItem(text: "周杰伦",
detailText: "15张专辑",
image: UIImage(named: "jay"))
item.handler = { [weak self] item, completion in
// 点击后的处理逻辑
self?.pushAlbumList(for: "周杰伦")
completion()
}
// 添加到section
let section = CPListSection(items: [item])
listTemplate.updateSections([section])
这里有个坑,我必须要提一下。CPListItem的handler是逃逸闭包,里面一定要调用completion()。我曾经有一次忘了调,结果列表项点击后卡死,用户怎么点都没反应。嗯,这种bug在车机上特别致命,因为司机不能分心去排查问题。
CPNowPlayingTemplate:播放中的主战场
当用户选好了一首歌,接下来就进入播放界面了。CPNowPlayingTemplate是CarPlay专门为媒体播放设计的模板,它自带了一套标准的播放控制UI。
这个模板最方便的地方在于,它自动支持了方向盘上的控制按键。你只要把播放状态、进度信息填进去,CarPlay会帮你处理好和硬件的交互。我记得第一次看到这个效果时,心里想的是:苹果确实把车机场景想得很透。
我的建议:CPNowPlayingTemplate不要自己创建实例,而是通过共享实例来获取。系统保证只有一个播放界面存在。
// 获取共享实例
let nowPlaying = CPNowPlayingTemplate.shared
// 配置播放控制按钮
let shuffleButton = CPNowPlayingButton.shuffleButton { button in
// 切换随机播放
MusicPlayer.shared.toggleShuffle()
}
let repeatButton = CPNowPlayingButton.repeatButton { button in
// 切换循环模式
MusicPlayer.shared.cycleRepeatMode()
}
nowPlaying.updateNowPlayingButtons([shuffleButton, repeatButton])
这里要注意的是,CPNowPlayingTemplate的按钮是有数量限制的。最多只能加4个自定义按钮,加上系统自带的播放/暂停、上一首、下一首,总共7个。别贪多,用户开车时没空看那么多按钮。
音乐播放控制:连接浏览与播放的桥梁
有了列表模板和播放模板,接下来就是怎么让它们协同工作。说白了,就是用户点了列表里的歌,怎么让播放界面响应。
CarPlay里有一套MPNowPlayingInfoCenter和MPRemoteCommandCenter,它们是iOS系统级的媒体控制中心。你在CarPlay里看到的专辑封面、歌曲名、进度条,其实都是通过它们来更新的。
避坑指南:我曾经在MPNowPlayingInfoCenter里忘了设置播放状态,结果CarPlay上一直显示暂停图标,方向盘按键也失灵。后来排查了半天,发现是MPNowPlayingInfoPropertyPlaybackRate没传。
// 更新播放信息
var nowPlayingInfo = [String: Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = "晴天"
nowPlayingInfo[MPMediaItemPropertyArtist] = "周杰伦"
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = "叶惠美"
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in image }
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 1.0
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackProgress] = currentTime / duration
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
远程控制命令这块,我建议你注册所有可能用到的命令,哪怕有些暂时用不上。因为CarPlay的硬件按键映射是动态的,你注册了某个命令,系统才会把对应的按键事件分发给你。
// 注册远程控制命令
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.addTarget { event in
MusicPlayer.shared.play()
return .success
}
commandCenter.pauseCommand.addTarget { event in
MusicPlayer.shared.pause()
return .success
}
commandCenter.nextTrackCommand.addTarget { event in
MusicPlayer.shared.nextTrack()
return .success
}
commandCenter.previousTrackCommand.addTarget { event in
MusicPlayer.shared.previousTrack()
return .success
}
三者如何配合?一张图说清楚
下面这张图是我自己总结的CarPlay媒体应用核心流程。你看一眼就能明白CPListTemplate、CPNowPlayingTemplate和播放控制之间的关系。
从图上你能看到,整个流程是双向的。用户通过CPListTemplate选择内容,触发播放控制,播放控制更新CPNowPlayingTemplate的显示。同时,方向盘按键通过MPRemoteCommandCenter回调到应用层,应用层再更新MPNowPlayingInfoCenter,最终反映到CPNowPlayingTemplate上。
实际开发中的几个关键点
讲完了三个核心组件,我再补充几个实际开发中容易踩的坑。
- 生命周期管理:CPListTemplate和CPNowPlayingTemplate都是通过CPInterfaceController来管理的。push和pop操作要确保在主线程执行,否则会偶现UI不刷新。
- 图片缓存:专辑封面图片一定要做缓存。CarPlay的屏幕分辨率不低,每次从网络加载会明显卡顿。我习惯用NSCache做一级缓存,磁盘做二级。
- 播放队列:CPNowPlayingTemplate不支持直接操作播放队列。你需要自己在应用层维护一个队列,然后通过MPNowPlayingInfoPropertyPlaybackQueueIndex来告诉系统当前播到哪了。
- 后台播放:别忘了配置Audio Session。我见过不少App在CarPlay上切到后台就没声了,就是因为没设置AVAudioSessionCategoryPlayback。
一个小技巧:调试CarPlay媒体应用时,可以在Xcode里选择“External Display”模拟器,这样能直接看到CarPlay界面的效果,不用每次都跑真车。
好了,关于CPNowPlayingTemplate、CPListTemplate和音乐播放控制,核心内容就这些。说白了,CarPlay的媒体应用开发并不复杂,关键是把这三个组件的配合关系理清楚。你只要记住:列表负责展示,播放模板负责呈现,播放控制负责桥接,整个链路就通了。