I am trying to build a sample audio app with CarPlay integration. The app is a test project - no API, no streaming, no complicated UI. Just a short list of song titles with the functionality to select one and play it. My goal is to handle the callback when the play button is pressed on the Now Playing screen and play a file.
I don't have any problems setting up the MPPlayableContentManager, MPPlayableContentDataSource and MPPlayableContentDelegate. My content is parsed from a JSON file and it shows up correctly on the simulator's screen.
// MARK: - MPPlayableContentDataSource methods
extension PlayableContentManager: MPPlayableContentDataSource {
func numberOfChildItems(at indexPath: IndexPath) -> Int {
if indexPath.count == 0 {
return playlists.count
} else if indexPath.count == 1 {
return playlists[indexPath.first ?? 0].playlist.count
} else if indexPath.count == 2 {
return playlists.last?.playlist.count ?? 0
}
return 0
}
func contentItem(at indexPath: IndexPath) -> MPContentItem? {
if indexPath.count == 1 {
return createTabbar(item: playlists[indexPath.first ??
0].name.capitalized, itemType: playlists[indexPath.first ?? 0].type)
} else if indexPath.count == 2 {
if indexPath.first == 0 {
return createContent(item: playlists[0].playlist[indexPath.last
?? 0], isContainer: false, isPlayable: true)
} else {
return createContainerContent(item: playlists[indexPath.first
?? 0].playlist[indexPath.last ?? 0])
}
}
return createTabbar(item: "", itemType: nil)
}
}
This code gives the following graphic output:
The two tabs contain the two playlists. Each playlist has a number of songs.
When I tap on a song the app becomes the Now Playing app but only if I have both began and ended receiving remote control events at the time of the user's interaction with the row.
The initiatePlaybackOfContentItemAt method is called when a click on the table row is detected.
// MARK: - MPPlayableContentDelegate methods
extension PlayableContentManager: MPPlayableContentDelegate {
func playableContentManager(_ contentManager:
MPPlayableContentManager, initiatePlaybackOfContentItemAt indexPath:
IndexPath, completionHandler: @escaping (Error?) -> Void) {
DispatchQueue.main.async {
UIApplication.shared.beginReceivingRemoteControlEvents()
self.infoCenter.nowPlayingInfo = self.setupNowPlayingInfo(for:
indexPath)
completionHandler(nil)
UIApplication.shared.endReceivingRemoteControlEvents()
}
}
}
This is the only code that works for me if I want the app to transition to the Now Playing screen. If I place any of the UIApplication methods anywhere else the app quits responding to row touches and doesn't enter the Now Playing screen.
However, I'm guessing because I'm invoking the endReceivingRemoteControlEvents(), I can't get the callback for the different events. The now playing info is set, I can see the play button in the UI but, when I press it, the callback doesn't execute.
private func setupPlaybackCommands() {
commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.addTarget { [unowned self] event in
if self.audioPlayer.rate == 0.0 {
self.play()
return .success
}
return .commandFailed
}
....
}
What am I doing wrong?
Could this have something to do with the fact that I'm testing on a simulator? Will this work on a real device?
If anyone can shed some light on how to correctly setup the CarPlay integration to enter the Now Playing screen and respond to events, please, share. I'm having a lot of trouble finding any usable code samples or examples.
I know I can but I haven't applied for a CarPlay entitlement because this project is for research purposes only and I highly doubt I'll get approved.