Создание приложений с функциями воспроизведения мультимедиа связана с рядом трудностей, которые усложняют разработку. Однако в этом году появилась возможность использовать Jetpack Media3 — решение, которое полностью меняет процесс взаимодействия с мультимедиа. Об этой библиотеке и ее возможностях расскажет Android-разработчик CleverPumpkin Сергей Смирнов.
![](https://habrastorage.org/getpro/habr/upload_files/6e7/6ab/0ff/6e76ab0ff35028875ac212632106932b.png)
Чтобы воспроизвести мультимедиа файл, необходимы две составляющие: сам медиаплеер, который проигрывает аудио и видео, а также юзер-интерфейс, отображающий некоторые метаданные (например, название, продолжительность аудиотрека или видео и текущее состояние воспроизведения).
Кроме того, пользовательский интерфейс позволяет взаимодействовать с плеером через кнопки — приложение получает команды «Воспроизведение», «Пауза», «Навигация по времени» и выводит уведомления при изменении состояния.
Проблема состоит в том, что в этой архитектуре система Android и другие приложения не получают информации о воспроизведении мультимедиа. Как следствие, пользователи лишаются удобных способов просмотра и управления плеером вне нашего приложения.
![](https://habrastorage.org/getpro/habr/upload_files/67c/e29/356/67ce293561179295ac06f0c3c5d0f7a1.png)
Для того чтобы решить эту проблему, необходимо подключить плеер к сеансу мультимедиа — тогда система получит данные о том, что сейчас воспроизводится файл, и предоставит возможность управления. При этом заходить в само приложение не потребуется.
Благодаря этому становятся доступны многие полезные интеграции. Например, можно управлять воспроизведением файла через умные часы или воспользоваться кнопками на наушниках. В дальнейшем планируется реализация управления из других приложений, а также посредством голосового помощника.
Таким образом, подобная архитектура позволяет более полно использовать возможности как самого плеера, так и всей системы в целом.
![](https://habrastorage.org/getpro/habr/upload_files/fc5/d71/6c6/fc5d716c67a5d84c010973475949c057.png)
Libraries
Главный вопрос, с которыми сталкиваются разработчики в этой сфере — это выбор подходящей библиотеки для реализации функции воспроизведения и управления мультимедиа.
Решение этого вопроса — Jetpack Media3. Но чтобы лучше понять её преимущества, для начала сделаем обзор некоторых библиотек, которые использовались ранее.
Androidx.media2. В ней реализованы модули, отвечающие за воспроизведение, компоненты интерфейса, контроль сеанса мультимедиа, а также общие структуры данных и функции.
ExoPlayer. Это библиотека для операций воспроизведения мультимедиа. Её используют в сотнях тысяч приложений, включая разработанный нами Kassir.ru — здесь с его помощью проигрываются сторис и видео в баннерах.
В этих API есть модули, которые практически дублируют друг друга и выполняют в целом одинаковые функции.
Также существуют и другие библиотеки, включая оригинальную compat-библиотеку androidx.media.
Множество схожих библиотек, их возможности и недостатки, различные условия совместимости — всё это затрудняет выбор библиотеки для использования.
![](https://habrastorage.org/getpro/habr/upload_files/31f/595/bef/31f595bef5e6ee5dcd5060e73cc99079.png)
Для удобства разработчиков в Jetpack Media3 решили объединить эти библиотеки.
Был создан один общий модуль.
ExoPlayer был стандартизирован, при этом сохранился его обширный набор вспомогательных модулей.
Был реализован один модуль с компонентами пользовательского интерфейса и вида плеера и ещё один модуль с API для работы с медиасессиями системы.
Результатом стал androidx.media3, который предоставляет связанный набор библиотек для различных вариантов использования мультимедиа.
![](https://habrastorage.org/getpro/habr/upload_files/5dd/06c/b50/5dd06cb5098f3d0fde2914418f5d0eb9.png)
Foreground playback
Давайте опишем главные юзкейсы и выделим преимущества Jetpack Media3 по сравнению с прошлыми версиями.
Рассмотрим вариант воспроизведения контента, при котором приложение расположено на переднем плане. Здесь можно использовать архитектуру, когда UI, плеер и медиасеанс находятся в одной activity. В этом случае медиасеанс позволяет поддерживать события мультимедийных клавиш и элементы управления отображением «картинка в картинке».
Раньше в этом подходе возникали сложности, так как с предыдущими API сеанс мультимедиа не мог напрямую взаимодействовать с плеером. Требовался объект-коннектор, который бы переводил команды и обратные вызовы между этими компонентами. Чтобы реализовать весь набор команд, которые может получать сеанс мультимедиа, и для обработки различных состояний плеера требовалось писать много кода. Из-за этого появлялись дополнительные сложности, и повышался риск возникновения ошибок.
![](https://habrastorage.org/getpro/habr/upload_files/fa1/fc4/9b6/fa1fc49b622165c69881a7b5dd315dd0.png)
Для решения этого вопроса в Media3 отказались от коннектора. Media3 предоставляет ExoPlayer в качестве своего стандартного плеера — и именно в нём реализуется интерфейс Player.
Поэтому в Google обновили MediaSession и виджеты UI, чтобы они воспринимали тот же Player-интерфейс, связав их напрямую друг с другом.
![](https://habrastorage.org/getpro/habr/upload_files/742/d05/733/742d057336f160b598734396130010dd.png)
Background playback
В случае фонового воспроизведения всё немного сложнее. Архитектура разделена между сервисом, содержащим проигрыватель, и activity для пользовательского интерфейса.
Сервис запускает сеанс мультимедиа, который используется для объявления воспроизведения и передачи команд проигрывателю. И внутри activity мы создаем медиаконтроллер, который служит для связи с сеансом мультимедиа.
Как уже говорилось ранее, плеер не может взаимодействовать напрямую с сеансом — требуется коннектор. Также он необходим для медиаконтроллера и нашего UI. В результате возникает та же проблема, что и с проигрыванием на переднем плане —֫ соединители усложняют код, что приводит к сбоям.
![](https://habrastorage.org/getpro/habr/upload_files/e12/66c/ea5/e1266cea504f6be4fa3749651bf85672.png)
Чтобы решить этот вопрос, был реализован общий интерфейс Player. Теперь ExoPlayer напрямую совместим с сеансом мультимедиа, а медиаконтроллер напрямую совместим с UI. В результате таких изменений получается код, который легче поддерживать и который меньше подвержен ошибкам.
![](https://habrastorage.org/getpro/habr/upload_files/4a8/e74/bcb/4a8e74bcba1e4d1ae2ed53ce67154824.png)
PlayerService
А теперь давайте рассмотрим, как создать приложение с воспроизведением аудио- и видеоконтента в фоне.
В первую очередь создаём экземпляр ExoPlayer. Затем запускаем медиасеанс и передаем ему нашу реализацию плеера.
Media3 будет автоматически обновлять медиасеанс в зависимости от состояния проигрывателя. Соединительные слои в этом случае не потребуются.
![](https://habrastorage.org/getpro/habr/upload_files/553/d9a/dc5/553d9adc57498e18ae92e9c583617f35.png)
Переходим к Activity, где мы собираемся отобразить пользовательский интерфейс.
Устанавливаем ссылку на сеанс воспроизведения в методе OnStart() жизненного цикла Activity.
Формируем токен для сеанса, к которому мы хотим подключиться.
Создаем MediaController, который асинхронно подключается к медиасеансу.
Также можно подключить Listener на установку соединения.
Поскольку MediaController — это всего лишь реализация интерфейса Player, мы можем передать его непосредственно в playerView.
После настройки UI обновляется так же, как и при воспроизведении на переднем плане, когда пользовательский интерфейс и плеер находятся в одной и той же Activity. Это работает даже в том случае, если ваш сервис и плеер запущены в отдельном процессе.
![](https://habrastorage.org/getpro/habr/upload_files/961/741/098/9617410989bff4ecb0f40da8caae1cb5.png)
Working with other apps
В завершение коснёмся вопроса, как наше приложение может работать с другими приложениями в системе Android.
Существуют два основных варианта.
В первом случае мы открываем доступ к медиасеансу, чтобы другие приложения и устройства могли управлять нашим медиаплеером и воспроизведением. Для пользователя такой вариант может быть наиболее удобен.
Во втором случае мы передаём контент воспроизведения — другие приложения могут использовать собственный юзер-интерфейс. Это важно, например, для Android Auto, который предоставляет собственный, удобный для водителя UI для нашего контента.