Создание приложений с функциями воспроизведения мультимедиа связана с рядом трудностей, которые усложняют разработку. Однако в этом году появилась возможность использовать Jetpack Media3 — решение, которое полностью меняет процесс взаимодействия с мультимедиа. Об этой библиотеке и ее возможностях расскажет Android-разработчик CleverPumpkin Сергей Смирнов.
Чтобы воспроизвести мультимедиа файл, необходимы две составляющие: сам медиаплеер, который проигрывает аудио и видео, а также юзер-интерфейс, отображающий некоторые метаданные (например, название, продолжительность аудиотрека или видео и текущее состояние воспроизведения).
Кроме того, пользовательский интерфейс позволяет взаимодействовать с плеером через кнопки — приложение получает команды «Воспроизведение», «Пауза», «Навигация по времени» и выводит уведомления при изменении состояния.
Проблема состоит в том, что в этой архитектуре система Android и другие приложения не получают информации о воспроизведении мультимедиа. Как следствие, пользователи лишаются удобных способов просмотра и управления плеером вне нашего приложения.
Для того чтобы решить эту проблему, необходимо подключить плеер к сеансу мультимедиа — тогда система получит данные о том, что сейчас воспроизводится файл, и предоставит возможность управления. При этом заходить в само приложение не потребуется.
Благодаря этому становятся доступны многие полезные интеграции. Например, можно управлять воспроизведением файла через умные часы или воспользоваться кнопками на наушниках. В дальнейшем планируется реализация управления из других приложений, а также посредством голосового помощника.
Таким образом, подобная архитектура позволяет более полно использовать возможности как самого плеера, так и всей системы в целом.
Libraries
Главный вопрос, с которыми сталкиваются разработчики в этой сфере — это выбор подходящей библиотеки для реализации функции воспроизведения и управления мультимедиа.
Решение этого вопроса — Jetpack Media3. Но чтобы лучше понять её преимущества, для начала сделаем обзор некоторых библиотек, которые использовались ранее.
Androidx.media2. В ней реализованы модули, отвечающие за воспроизведение, компоненты интерфейса, контроль сеанса мультимедиа, а также общие структуры данных и функции.
ExoPlayer. Это библиотека для операций воспроизведения мультимедиа. Её используют в сотнях тысяч приложений, включая разработанный нами Kassir.ru — здесь с его помощью проигрываются сторис и видео в баннерах.
В этих API есть модули, которые практически дублируют друг друга и выполняют в целом одинаковые функции.
Также существуют и другие библиотеки, включая оригинальную compat-библиотеку androidx.media.
Множество схожих библиотек, их возможности и недостатки, различные условия совместимости — всё это затрудняет выбор библиотеки для использования.
Для удобства разработчиков в Jetpack Media3 решили объединить эти библиотеки.
Был создан один общий модуль.
ExoPlayer был стандартизирован, при этом сохранился его обширный набор вспомогательных модулей.
Был реализован один модуль с компонентами пользовательского интерфейса и вида плеера и ещё один модуль с API для работы с медиасессиями системы.
Результатом стал androidx.media3, который предоставляет связанный набор библиотек для различных вариантов использования мультимедиа.
Foreground playback
Давайте опишем главные юзкейсы и выделим преимущества Jetpack Media3 по сравнению с прошлыми версиями.
Рассмотрим вариант воспроизведения контента, при котором приложение расположено на переднем плане. Здесь можно использовать архитектуру, когда UI, плеер и медиасеанс находятся в одной activity. В этом случае медиасеанс позволяет поддерживать события мультимедийных клавиш и элементы управления отображением «картинка в картинке».
Раньше в этом подходе возникали сложности, так как с предыдущими API сеанс мультимедиа не мог напрямую взаимодействовать с плеером. Требовался объект-коннектор, который бы переводил команды и обратные вызовы между этими компонентами. Чтобы реализовать весь набор команд, которые может получать сеанс мультимедиа, и для обработки различных состояний плеера требовалось писать много кода. Из-за этого появлялись дополнительные сложности, и повышался риск возникновения ошибок.
Для решения этого вопроса в Media3 отказались от коннектора. Media3 предоставляет ExoPlayer в качестве своего стандартного плеера — и именно в нём реализуется интерфейс Player.
Поэтому в Google обновили MediaSession и виджеты UI, чтобы они воспринимали тот же Player-интерфейс, связав их напрямую друг с другом.
Background playback
В случае фонового воспроизведения всё немного сложнее. Архитектура разделена между сервисом, содержащим проигрыватель, и activity для пользовательского интерфейса.
Сервис запускает сеанс мультимедиа, который используется для объявления воспроизведения и передачи команд проигрывателю. И внутри activity мы создаем медиаконтроллер, который служит для связи с сеансом мультимедиа.
Как уже говорилось ранее, плеер не может взаимодействовать напрямую с сеансом — требуется коннектор. Также он необходим для медиаконтроллера и нашего UI. В результате возникает та же проблема, что и с проигрыванием на переднем плане —֫ соединители усложняют код, что приводит к сбоям.
Чтобы решить этот вопрос, был реализован общий интерфейс Player. Теперь ExoPlayer напрямую совместим с сеансом мультимедиа, а медиаконтроллер напрямую совместим с UI. В результате таких изменений получается код, который легче поддерживать и который меньше подвержен ошибкам.
PlayerService
А теперь давайте рассмотрим, как создать приложение с воспроизведением аудио- и видеоконтента в фоне.
В первую очередь создаём экземпляр ExoPlayer. Затем запускаем медиасеанс и передаем ему нашу реализацию плеера.
Media3 будет автоматически обновлять медиасеанс в зависимости от состояния проигрывателя. Соединительные слои в этом случае не потребуются.
Переходим к Activity, где мы собираемся отобразить пользовательский интерфейс.
Устанавливаем ссылку на сеанс воспроизведения в методе OnStart() жизненного цикла Activity.
Формируем токен для сеанса, к которому мы хотим подключиться.
Создаем MediaController, который асинхронно подключается к медиасеансу.
Также можно подключить Listener на установку соединения.
Поскольку MediaController — это всего лишь реализация интерфейса Player, мы можем передать его непосредственно в playerView.
После настройки UI обновляется так же, как и при воспроизведении на переднем плане, когда пользовательский интерфейс и плеер находятся в одной и той же Activity. Это работает даже в том случае, если ваш сервис и плеер запущены в отдельном процессе.
Working with other apps
В завершение коснёмся вопроса, как наше приложение может работать с другими приложениями в системе Android.
Существуют два основных варианта.
В первом случае мы открываем доступ к медиасеансу, чтобы другие приложения и устройства могли управлять нашим медиаплеером и воспроизведением. Для пользователя такой вариант может быть наиболее удобен.
Во втором случае мы передаём контент воспроизведения — другие приложения могут использовать собственный юзер-интерфейс. Это важно, например, для Android Auto, который предоставляет собственный, удобный для водителя UI для нашего контента.