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和播放控制之间的关系。

CarPlay媒体应用核心流程 CPListTemplate 浏览内容列表 专辑/歌单/歌手 点击选择 播放控制 MPRemoteCommandCenter 播放/暂停/切歌 状态同步 CPNowPlayingTemplate 播放界面展示 专辑封面/进度条 数据流与状态同步 MPNowPlayingInfoCenter 方向盘硬件按键 应用层回调 用户操作 → CPListTemplate选择 → 播放控制响应 → CPNowPlayingTemplate展示 硬件按键 → MPRemoteCommandCenter → 应用层处理 → 状态回写

从图上你能看到,整个流程是双向的。用户通过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的媒体应用开发并不复杂,关键是把这三个组件的配合关系理清楚。你只要记住:列表负责展示,播放模板负责呈现,播放控制负责桥接,整个链路就通了。


公众号:蓝海资料掘金营,微信deep3321