Рассказ о приложении CarPlay от Qobuz

Работая в Qobuz, высококачественном сервисе потоковой передачи музыки с высокой точностью воспроизведения, я отвечал за переделку нашего приложения CarPlay, у которого то здесь, то там возникали некоторые проблемы. Qobuz, французская компания для контекста, занимает долю рынка аудиофилов в Америке, и мы получили негативные отзывы о нашем приложении CarPlay от пользователей из США, которые очень любят слушать музыку в своих автомобилях.

Поскольку мы нацелены на премиальных пользователей с премиальными звуками, нам, очевидно, нужно было обеспечить премиальный опыт даже в их автомобилях. Поэтому мы начали работу над исправлением и улучшением нашего приложения CarPlay. К сожалению, мне не удалось найти примеры аудиоприложений в Интернете. Теперь, когда наше новое приложение запущено и работает, я решил добавить свой собственный контент о создании аудиоприложения CarPlay и включить некоторые отзывы о процессе разработки. Итак, давайте погрузимся!

Состояние комплекта разработки CarPlay в 2021 году

К счастью для меня и к моему приятному удивлению, Apple анонсировала с iOS 14 еще в 2020 году новую платформу с тонким названием CarPlay, которая представила новый и более современный способ разработки приложений CarPlay. Отличные новости! Не могу сказать, что я большой поклонник старого способа, в котором использовался фреймворк MediaPlayer. Это не удобно для разработчиков, имеет странную реализацию, и я неоднократно обнаруживал, что вынужден создавать хаки для решения моих проблем. 1/10, не рекомендую.

Но еще в 2021 году мы поддерживали iOS 12.0 и более поздние версии, а поскольку новый комплект CarPlay доступен только начиная с iOS 14.0, у меня не было другого выбора, кроме как использовать оба метода, чтобы предоставить возможность CarPlay всем нашим пользователям. Поскольку я не думаю, что в 2022 году уместно говорить о старом способе, я в основном расскажу о своей работе с CarPlay Kit и быстро расскажу о том, что с MediaPlayer.

Но если вам придется использовать этот фреймворк для более старых версий iOS (не повезло) и у вас возникнут проблемы, не стесняйтесь, отправьте мне сообщение здесь, на Medium, и я посмотрю, смогу ли я вам помочь :).

Комплект CarPlay, новый способ

Поскольку я хотел начать с хорошей ноты, я решил заняться версией, использующей инфраструктуру CarPlay, которая в любом случае нацелена на представление будущего и, очевидно, является самой важной версией из двух. Вот как все работало:

право

В первую очередь права. Вам нужно перейти сюда и предоставить информацию о вашем приложении. Apple рассмотрит его и сообщит вам, если он соответствует их критериям.

После того, как оно будет принято, вы должны создать новый профиль обеспечения, содержащий это новое право, и добавить его в свой проект.

Затем внутри файла Entitlement.plist (создайте его, если он не существует) заверните все, поместив следующий ключ:

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

Настройка сцен

С момента появления iOS 13 Apple продвигает новый способ обработки различных экранов вашего приложения (которым может быть ваше основное приложение, приложение CarPlay или ваше приложение, дублированное в разделенном представлении на iPad), и это UIScene. И если, как и мы в Qobuz, вы еще не поддерживали или не поддерживали это, что ж, вам нужно немного поработать, прежде чем продвигаться вперед в своем причудливом приложении CarPlay нового поколения.

В нашем случае у нас есть две разные сцены: одна с именем AppSceneDelegate, а другая с именем CarplaySceneDelegate. Имена здесь говорят сами за себя. После создания этих сцен внутри Info.plist нужно добавить новый ключ: UIApplicationSceneManifest. Он содержит словарь, описывающий сцены внутри вашего приложения.

Здесь вы в первую очередь указываете, что ваше приложение будет поддерживать несколько сцен, а затем всевозможные свойства, в первую очередь имя класса, представляющего точку входа вашей сцены (в нашем случае это AppSceneDelegate и CarplaySceneDelegate). . Вы можете посмотреть ниже, как представлен наш манифест сцены, но если вам нужна дополнительная информация, ознакомьтесь с документацией Apple об этом здесь:

Но подождите, что делают те занятия, о которых я вам говорил, UIScene? Согласно документации Apple, UIScene — это класс, который реализует UISceneDelegate, предоставляя методы, которые вызываются в течение жизненного цикла приложения. Кажется знакомым? Действительно, ему суждено заменить AppDelegate.

При заполнении методов в AppSceneDelegate я фактически перенес код из нашего AppDelegate, так как я заставляю сцену запускать iOS 13. На самом деле больше нечего сказать об этом, но для CarplaySceneDelegate осталось сделать одну последнюю вещь, и это сделать его соответствовать CPTemplateApplicationSceneDelegate. Ну, на самом деле обязательных методов для добавления нет, но тем не менее, в нашем приложении сегодня используются два из них:

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

Это точка входа и выхода нашего приложения CarPlay. Внутри этих методов делается простой вызов:

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

Но что это за carplayTemplateManager? Ну, это свойство CarplaySceneDelegate типа CarplayTemplateManager. Это класс, обрабатывающий весь код, относящийся к нашему приложению CarPlay для iOS версии 14 и выше, и следующий этап разработки.

CarplayTemplateManager

Пришло время создать наше приложение по-настоящему! Как следует из названия, создание приложения CarPlay основано на шаблонах. Базовый класс — CPTemplate, и в нашем случае мы используем CPTabBarTemplate, что дает такой же опыт, как и UITabBarController.

Суть в том, что вы настраиваете корневой шаблон, в данном случае это CPTabBarTemplate, а затем создаете другие шаблоны и элементы для каждого меню и подменю. Наш базовый код для инициализации корневого шаблона выглядит так:

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

На мой взгляд, это удобный для разработчиков код. Очень легко настроить, очень легко читать и понимать, и это сразу дает результат. Любить это.

Для каждой вкладки создается CPListTemplate. Этот шаблон заполнен CPListItem. Я взял немного нашего кода, создающего шаблон My Qobuz, просто чтобы показать вам общую идею (имейте в виду, что я, для ясности, также немного изменил его, реальный метод немного длиннее)

Интересная часть — это, безусловно, обработчик свойств в CPListItem. Назначенный ему блок кода будет выполнен в тот момент, когда пользователь коснется элемента списка. Если внутри выполняется асинхронная операция, комплект CarPlay автоматически поставит на предмет вращающееся колесо, ожидая завершения операции.

В приведенном выше случае нет асинхронной операции, а создается другой шаблон, который проталкивается в интерфейс с помощью метода pushTemplate(:). Это наверняка напоминает вам, как вы взаимодействуете с UINavigationController. Что делает его еще более дружелюбным.

Вот один из наших методов, который создает файл CPListItem. В этом есть асинхронная операция загрузки альбомов из нашего веб-сервиса. При попадании в блок успеха создается массив из CPListItem, каждый из которых представляет альбом, назначается CPListTemplate, а затем помещается в carplayInterfaceController. Последнее, что нужно отметить, это completion(). Это параметр, предоставляемый свойством обработчика. Его вызов позволяет обработчику узнать, что вы завершили операцию и что он может продолжить ее выполнение.

И последнее: в нашем обработчике дорожки CPListItem вместо отправки пользовательского шаблона мы делаем следующее:

self.pushNowPlayingTemplate()

NowPlayingTemplate — это шаблон по умолчанию, предоставляемый комплектом CarPlay, и его можно настроить с помощью кнопок (воспроизведение в случайном порядке, повтор и т. д.), обложки воспроизводимого трека, очереди треков и всего, что необходимо для работы плеера. Шаблон стандартный, а это значит, что помимо, может быть, некоторых кнопок, у нас есть более или менее такой же внешний вид плеера, как у Spotify или Apple Music.

Вот и все. Остальной код представляет собой просто более CPListTemplate и более CPListItem творений, собранных воедино, и вуаля, вы получили аудиоприложение CarPlay с чистым пользовательским интерфейсом и хорошим UX.

Коротко о MediaPlayer, по-старому

Как я уже сказал, я не буду описывать подробности моего приключения с использованием фреймворка MediaPlayer, так как кодовая база этой версии обречена на то, чтобы как можно скорее оказаться в мусорной корзине. Здесь следует отметить еще несколько мелких деталей, которые могут вам помочь.

Ограничения

Не забудьте указать ограничения вашего приложения CarPlay. Вы должны указать ограничение на количество элементов на экране (contentLimitItemCount) и глубину дерева навигации (contentLimitTreeDepth). Apple рекомендует не более пяти в глубину дерева, но они также указывают, что производители автомобилей контролируют эти числа и могут реализовать свой собственный алгоритм в зависимости от различных параметров, таких как скорость автомобиля и т. д.

Что касается contentLimitItemCount, я установил 200 как произвольное число после проблем с производительностью. Именно о таком хаке я и говорил.

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

Копирование по-новому

Начав с CarPlay Kit, я черпал вдохновение из их API, который помогал мне при разработке старой версии. В итоге у меня появилась собственная версия основных предметов, как показано ниже:

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

Это делает реализацию похожей благодаря этому фрагменту кода. Просто работает не так гладко. У меня было много проблем с производительностью, и мне приходилось что-то улучшать тут и там, из-за чего я терял много времени для меньшинства пользователей. Немного расстраивает, но, в конце концов, все наши пользователи защищены, так что это того стоило (я все равно собираюсь выкинуть этот код в ближайшую помойку как можно скорее).

Заключение

Разработка этой функции определенно доставляла удовольствие большинству из них. Это было также невероятно полезно и даже упоминалось в некоторых новостных статьях (например, эта), так что я определенно принимаю это. Я также смог использовать его сам во время поездки по США (у меня нет машины во Франции для контекста), что также добавляет некоторого удовольствия :).

Я надеюсь, что вы найдете какую-то часть этой статьи полезной для разработки вашего аудиоприложения CarPlay (или любого другого приложения CarPlay).

Вы можете ознакомиться с руководством по разработке от Apple здесь. Это помогает мне, особенно в настройке основ.

Всем удачного кодирования и безопасного вождения!