The Making of a CarPlay Audio Application | by Pierre-Yves Touzain | Apr, 2022

Photo by Li Yan on Unsplash

While working for Qobuz, a high-quality and high-fidelity music streaming service, I had the responsibility of remaking our CarPlay application who had some issues here and there. Qobuz, a french company for context, is taking a share of the audiophile American market and we were receiving negative feedback about our CarPlay application from US users, who really love to listen to music in their cars.

As we are targeting premium users with premium sounds, we obviously needed to deliver a premium experience, even in their cars. So we started to put work on fixing and improving our CarPlay application. Sadly, I had trouble finding examples of an audio app on the internet. Now that our new application is up and running, I thought I would add myself some content of my own about the making of a CarPlay audio application and include some feedback about the development process. So let’s dive in!

Lucky for me and to my pleasant surprise, Apple announced with iOS 14 back in 2020 a new framework, subtly named CarPlay, which introduced a new and more modern way to develop CarPlay applications. Great news! I can’t say I’m a big fan of the old way, which used the MediaPlayer framework. It’s not developer friendly, has a weird implementation, and I found myself multiple times obligated to produce hacks to solve my problems. 1/10, would not recommend.

But back in 2021, we were supporting iOS 12.0 and further, and as the new CarPlay kit is only available starting with iOS 14.0, I had no choice but to deal with both methods in order to give the CarPlay experience to all our users. Since I don’t think that, in 2022, it’s relevant to talk about the old way, I will mostly cover my work with CarPlay Kit, and talk quickly about the one with MediaPlayer.

But if you have to use this framework for older versions of iOS (tough luck) and have trouble, feel free to send me a message here on Medium, and I’ll see if I can help you :).

As I wanted to start on a good note, I decided to tackle the version using the CarPlay framework, which is anyway the one aiming to represent the future and obviously the most important version of the two. This is how things worked:

Entitlement

First of all, the entitlement. You need to head over here and provide information about your application. Apple will review it and notify you back positively if it meets their criteria.

Once it has been accepted, you have to create a new provisioning profile that contains this new entitlement and add it to your project.

Then, inside the Entitlement.plist file (create one if it doesn’t exist), wrap things up by placing the following key:

<key>com.apple.developer.carplay-audio</key>
<true/>

Setting up the scenes

Since the introduction of iOS 13, Apple is pushing a new way to handle the different screens of your app (which could be your main app, the CarPlay app, or your app duplicated in split view on iPad) and this is UIScene. And if like us at Qobuz you didn’t or don’t support yet this, well, you need to do a bit of work before moving forward on your fancy CarPlay application new generation.

In our case, we have two different scenes: One named AppSceneDelegate and the other CarplaySceneDelegate. The names speak for themself here. After creating those scenes, there is a new key to add inside Info.plist: UIApplicationSceneManifest. It contains a dictionary describing the scenes inside your application.

This is where you are going to specify first of all that your app will support multiple scenes, and then all sorts of properties, most notably the name of the class representing the entry point of your scene (So in our case, AppSceneDelegate and CarplaySceneDelegate). You can have a look below at how our scene manifest is represented, but if you want more information, check the Apple documentation about it here:

But wait, what make those classes I told you about UIScene? As per Apple documentation, a UIScene is a class that implements the UISceneDelegate, providing methods that are called during the application lifecycle. Seems familiar? Indeed, it is destined to replace the AppDelegate.

When filling the methods in AppSceneDelegateI actually transferred code from our AppDelegate since I make the scene take over starting iOS 13. Not much more to say for this actually, but for CarplaySceneDelegatethere is one last thing to do, and it is to make it conform to CPTemplateApplicationSceneDelegate. Well, actually there are no required methods to add, but still, two of them are used in our app today:

templateApplicationScene(_ , didConnect:)
templateApplicationScene(_ , didDisconnectInterfaceController:)

It is the entry and the exit point of our CarPlay application. Inside those methods, a simple call is made:

//Entry point
carplayTemplateManager.connect(interfaceController)
//Exit point
carplayTemplateManager.disconnect()

But what is this carplayTemplateManager? Well, it’s a property of CarplaySceneDelegate of type CarplayTemplateManager. This is the class handling all the code relating to our CarPlay application for the iOS versions 14 and above, and the next step of the development.

CarplayTemplateManager

It’s time to build our application for real now! As the name suggests, building a CarPlay application is based on templates. The base class is CPTemplateand in our case, we use CPTabBarTemplatewhich is giving a similar experience as the UITabBarController

Pardon my French, but you can clearly see the three tabs we have on our CarPlay application

The basics are that you set up a root template, which in that case is CPTabBarTemplate, and then create other templates and items for every menu and sub-menus. Our base code for the root template initialization looks like this:

var tabTemplates = [CPTemplate]()
tabTemplates.append(myQobuzTemplate())
tabTemplates.append(localLibraryTemplate())
tabTemplates.append(discoverTemplate())
self.carplayInterfaceController!
.setRootTemplate(CPTabBarTemplate(templates: tabTemplates),
animated: true, completion: nil)

This is, in my opinion, developer-friendly code. Very easy to set up, very easy to read and understand, and it produces a result straight away. Love it.

For each tab, a CPListTemplate is created. This template is populated with CPListItem. I took a bit of our code creating the My Qobuz Template just to show you the general idea (bear in my mind that I, for clarity purposes, also modified a bit of it too, the real method being a bit longer)

The interesting part is definitely the property handler in CPListItem. The block of code assigned to it will be executed the moment the user tap on the list item. If an asynchronous operation is made inside, CarPlay kit will automatically put a wheel spinner on the item, waiting for the operation to complete.

In the case above, there is no asynchronous operation but the creation of another template, which is pushed on the interface using the method pushTemplate(:). It for sure reminds you of how you interact with a UINavigationController. Which makes it even more friendly.

Here is one of our methods that creates a CPListItem. In this one, there is an asynchronous operation, loading albums from our web service. When the success block is hit, an array of CPListItemeach one representing an album, is created, assigned to a CPListTemplate and then pushed on carplayInterfaceController. The last thing to note is completion(). It’s a parameter provided by the handler property. Calling it lets the handler know that you are done with the operation, and that it can move forward in its execution.

One last thing: on our track CPListItem handler, instead of pushing a custom template, we do this:

self.pushNowPlayingTemplate()

NowPlayingTemplate is a default template provided by the CarPlay kit and it’s configurable with buttons (random play, repeat, etc), the artwork of the track played, a track queue, and everything needed to have a working player. The template is standard, which means that apart from maybe some buttons we have more or less the same player look as Spotify or Apple Music.

And that’s it. The rest of the code is just more CPListTemplate and more CPListItem creation put together and voila, you got a CarPlay audio app with a clean UI and good UX.

As I said, I will not cover the details of my dev adventure using the MediaPlayer framework as the codebase of this version is destined to finish in the trash as soon as possible. Still some small details to note here that can help you.

Limits

Don’t forget to specify the limits of your CarPlay app. You have to give a limit for the number of items on a screen (contentLimitItemCount) and the depth of the navigation tree (contentLimitTreeDepth). Apple advises no more than five in tree depth, but they also specify that cars manufacturers have control of those numbers and can have their own algorithm depending on various parameters like the speed of the car, etc.

About contentLimitItemCount, I set 200 as an arbitrary number after having performances issues. That’s the kind of hack I was talking about.

var contentLimitItemCount: Int = 200
var contentLimitTreeDepth: Int = 5

Copying the New Way

Having started with CarPlay Kit, I took inspiration from their API to help me during the development of the old version. So I ended up having my own version of basic items, as shown below:

class CarplayContentItem: MPContentItem {
var subItems: [CarplayContentItem] = []
var handler: (@escaping (Error?) -> Void)->() = { _ in }
}

It makes the implementation kind of similar thanks to this piece of code. It’s just not working as smoothly. I had a lot of performances issues, and I had to improve things here and there, which makes me lose a lot of time for a minority of users. A bit frustrating, but at the end, all of our users are covered so that was worth it (I’m still going to throw this code in the nearest trash as soon as possible).

Developing this feature was definitely fun for most of it. It was also incredibly rewarding and even talked about in some news articles (like this one), so I definitely take it. I’ve also been able to use it myself while doing a road trip in the USA (I don’t own a car in France for context), which also adds some satisfaction to the mix :).

I hope you’ll find some part of this article useful for the development of your CarPlay audio app (or any other kind of CarPlay app).

You can check the development guide from Apple here. It helps me, especially in setting up the basics.

Happy coding, and safe driving, everyone!

Leave a Comment