Руководство по переходу на AndroidX Media3

Если в вашем приложении сейчас используются отдельные библиотеки com.google.android.exoplayer2 и androidx.media, перейдите на androidx.media3. Используйте скрипт переноса, чтобы перенести файлы сборки Gradle, исходные файлы Java и Kotlin, а также XML-файлы макета из ExoPlayer 2.19.1 в AndroidX Media3 1.1.1.

Обзор

Прежде чем переходить на новые API, ознакомьтесь с разделами ниже. В них рассказывается о преимуществах новых API, о том, какие API нужно перенести, и о требованиях к проекту приложения.

Зачем переходить на Jetpack Media3

  • Это новая библиотека для ExoPlayer, а com.google.android.exoplayer2 больше не поддерживается.
  • Доступ к Player API в разных компонентах и процессах с помощью MediaBrowser/MediaController.
  • Используйте расширенные возможности API MediaSession и MediaController.
  • Объявляйте о возможностях воспроизведения с помощью точного контроля доступа.
  • Упростите приложение, удалив MediaSessionConnector и PlayerNotificationManager.
  • Обратная совместимость с клиентскими API media-compat (MediaBrowserCompat/MediaControllerCompat/MediaMetadataCompat).

Какие API для работы с медиаконтентом нужно перенести в AndroidX Media3

  • ExoPlayer и его расширения
    Сюда входят все модули устаревшего проекта ExoPlayer, кроме модуля mediasession, поддержка которого прекращена. Приложения или модули, зависящие от пакетов в com.google.android.exoplayer2, можно перенести с помощью скрипта переноса.
  • MediaSessionConnector (в зависимости от пакетов androidx.media.* в androidx.media:media:1.4.3+)
    Удалите MediaSessionConnector и используйте androidx.media3.session.MediaSession.
  • MediaBrowserServiceCompat (в зависимости от пакетов androidx.media.* в androidx.media:media:1.4.3+)
    Перенесите подклассы androidx.media.MediaBrowserServiceCompat в androidx.media3.session.MediaLibraryService и код, использующий MediaBrowserCompat.MediaItem, в androidx.media3.common.MediaItem.
  • MediaBrowserCompat (в зависимости от пакетов android.support.v4.media.* в androidx.media:media:1.4.3+).
    Перенесите код клиента, использующий MediaBrowserCompat или MediaControllerCompat, чтобы использовать androidx.media3.session.MediaBrowser с androidx.media3.common.MediaItem.

Требования

  1. Убедитесь, что ваш проект находится под управлением системы контроля версий

    Убедитесь, что вы можете легко отменить изменения, внесенные инструментами переноса на основе скриптов. Если вы ещё не используете систему контроля версий, самое время начать. Если по какой-то причине вы не хотите этого делать, создайте резервную копию проекта перед началом переноса.

  2. Как обновить приложение

    • Рекомендуем обновить проект, чтобы использовать последнюю версию библиотеки ExoPlayer, и удалить все вызовы устаревших методов. Если вы планируете использовать скрипт для переноса, вам нужно сопоставить версию, на которую вы обновляетесь, с версией, обрабатываемой скриптом.

    • Увеличьте значение compileSdkVersion для приложения как минимум до 32.

    • Обновите Gradle и плагин Android Studio Gradle до последней версии, которая работает с обновленными зависимостями, указанными выше. Примеры:

      • Версия плагина Android Gradle: 7.1.0.
      • Версия Gradle: 7.4
    • Замените все операторы импорта с подстановочными знаками, в которых используется звездочка (*), на полные операторы импорта. Для этого удалите операторы импорта с подстановочными знаками и импортируйте полные операторы с помощью Android Studio (F2 > Alt + Ввод, F2 > Alt + Ввод и т. д.).

    • Перенесите данные из com.google.android.exoplayer2.PlayerView в com.google.android.exoplayer2.StyledPlayerView. Это необходимо, поскольку в AndroidX Media3 нет эквивалента com.google.android.exoplayer2.PlayerView.

Перенос ExoPlayer с поддержкой скриптов

Скрипт упрощает переход с com.google.android.exoplayer2 на новую структуру пакетов и модулей в androidx.media3. Скрипт выполняет некоторые проверки вашего проекта и выводит предупреждения, если проверка не удалась. В противном случае он применяет сопоставления переименованных классов и пакетов в ресурсах проекта Android Gradle, написанного на Java или Kotlin.

usage: ./media3-migration.sh [-p|-c|-d|-v]|[-m|-l [-x <path>] [-f] PROJECT_ROOT]
 PROJECT_ROOT: path to your project root (location of 'gradlew')
 -p: list package mappings and then exit
 -c: list class mappings (precedence over package mappings) and then exit
 -d: list dependency mappings and then exit
 -l: list files that will be considered for rewrite and then exit
 -x: exclude the path from the list of file to be changed: 'app/src/test'
 -m: migrate packages, classes and dependencies to AndroidX Media3
 -f: force the action even when validation fails
 -v: print the exoplayer2/media3 version strings of this script
 -h, --help: show this help text

Как использовать скрипт переноса

  1. Скачайте скрипт переноса из тега проекта ExoPlayer на GitHub, соответствующего версии, до которой вы обновили приложение.

    curl -o media3-migration.sh \
      "https://raw.githubusercontent.com/google/ExoPlayer/r2.19.1/media3-migration.sh"
    
  2. Сделайте скрипт исполняемым:

    chmod 744 media3-migration.sh
    
  3. Чтобы узнать о доступных параметрах, запустите скрипт с флагом --help.

  4. Запустите скрипт с параметром -l, чтобы получить список файлов, выбранных для переноса. Чтобы получить список без предупреждений, используйте параметр -f.

    ./media3-migration.sh -l -f /path/to/gradle/project/root
    
  5. Запустите скрипт с -m, чтобы сопоставить пакеты, классы и модули с Media3. Если запустить скрипт с параметром -m, изменения будут применены к выбранным файлам.

    • Остановить при ошибке проверки без внесения изменений
    ./media3-migration.sh -m /path/to/gradle/project/root
    
    • Принудительное выполнение

    Если скрипт обнаружит нарушение предварительных требований, перенос можно выполнить принудительно с помощью флага -f:

    ./media3-migration.sh -m -f /path/to/gradle/project/root
    
 # list files selected for migration when excluding paths
 ./media3-migration.sh -l -x "app/src/test/" -x "service/" /path/to/project/root
 # migrate the selected files
 ./media3-migration.sh -m -x "app/src/test/" -x "service/" /path/to/project/root

После запуска скрипта с параметром -m выполните следующие действия:

  1. Проверьте, как скрипт изменил ваш код. Используйте инструмент diff и исправьте потенциальные проблемы. Если вы считаете, что скрипт имеет общую проблему, которая была введена без передачи параметра -f, сообщите об ошибке.
  2. Скомпилируйте проект. Используйте ./gradlew clean build или в Android Studio выберите File > Sync Project with Gradle Files (Файл > Синхронизировать проект с файлами Gradle), затем Build > Clean project (Сборка > Очистить проект) и Build > Rebuild project (Сборка > Пересобрать проект). Следить за процессом можно на вкладке Build - Build Output (Сборка – Вывод сборки) в Android Studio.

Рекомендуемые дальнейшие действия:

  1. Устраните ошибки, связанные с использованием нестабильных API.
  2. Замените устаревшие вызовы API. Используйте рекомендуемый API. Наведите указатель на предупреждение в Android Studio и посмотрите в JavaDoc устаревшего символа, что нужно использовать вместо него.
  3. Отсортируйте операторы импорта. Откройте проект в Android Studio, нажмите правой кнопкой мыши на узел папки пакета в окне просмотра проекта и выберите Optimize imports (Оптимизировать импорт) для пакетов, содержащих измененные исходные файлы.

Замените MediaSessionConnector на androidx.media3.session.MediaSession

В устаревшей версии MediaSessionCompat класс MediaSessionConnector отвечал за синхронизацию состояния проигрывателя с состоянием сеанса и получение команд от контроллеров, которые нужно было делегировать соответствующим методам проигрывателя. В AndroidX Media3 это делается напрямую с помощью MediaSession без коннектора.

  1. Удалите все ссылки на MediaSessionConnector и его использование. Если вы использовали автоматический скрипт для переноса классов и пакетов ExoPlayer, то, скорее всего, скрипт оставил ваш код в некомпилируемом состоянии из-за неразрешимой проблемы с MediaSessionConnector. Android Studio покажет вам неработающий код, когда вы попытаетесь собрать или запустить приложение.

  2. В файле build.gradle, где вы управляете зависимостями, добавьте зависимость реализации к модулю сеанса AndroidX Media3 и удалите устаревшую зависимость:

    implementation "androidx.media3:media3-session:1.11.1"
    
  3. Замените MediaSessionCompat на androidx.media3.session.MediaSession.

  4. На сайте с кодом, где вы создали устаревший MediaSessionCompat, используйте androidx.media3.session.MediaSession.Builder, чтобы создать MediaSession. Передайте проигрыватель, чтобы создать конструктор сеансов.

    Kotlin

    val player = ExoPlayer.Builder(context).build()
    mediaSession = MediaSession.Builder(context, player).setCallback(MySessionCallback()).build()

    Java

    ExoPlayer player = new ExoPlayer.Builder(context).build();
    mediaSession =
        new MediaSession.Builder(context, player).setCallback(new MySessionCallback()).build();

  5. При необходимости реализуйте MySessionCallback. Это необязательно. Если вы хотите разрешить контроллерам добавлять мультимедийные объекты в проигрыватель, реализуйте MediaSession.Callback.onAddMediaItems(). Он поддерживает различные текущие и устаревшие методы API, которые добавляют мультимедийные объекты в проигрыватель для воспроизведения с обратной совместимостью. Это касается методов MediaController.set/addMediaItems() контроллера Media3, а также методов TransportControls.prepareFrom*/playFrom* устаревшего API. Пример реализации onAddMediaItems можно найти в PlaybackService демонстрационного приложения.

  6. Освободите сеанс воспроизведения в том месте кода, где вы удалили сеанс до переноса:

    Kotlin

    mediaSession?.run {
      player.release()
      release()
      mediaSession = null
    }

    Java

    if (mediaSession != null) {
      mediaSession.getPlayer().release();
      mediaSession.release();
      mediaSession = null;
    }

Функции MediaSessionConnector в Media3

В таблице ниже показаны API Media3, которые обрабатывают функции, ранее реализованные в MediaSessionConnector.

MediaSessionConnectorAndroidX Media3
CustomActionProvider MediaSession.Callback.onCustomCommand()/ MediaSession.setMediaButtonPreferences()
PlaybackPreparer MediaSession.Callback.onAddMediaItems() (prepare() вызывается внутри)
QueueNavigator ForwardingSimpleBasePlayer
QueueEditor MediaSession.Callback.onAddMediaItems()
RatingCallback MediaSession.Callback.onSetRating()
PlayerNotificationManager DefaultMediaNotificationProvider/ MediaNotification.Provider

Перенести MediaBrowserService в MediaLibraryService

В AndroidX Media3 появился класс MediaLibraryService, который заменяет MediaBrowserServiceCompat. Документация JavaDoc для MediaLibraryService и его суперкласса MediaSessionService содержит хорошее введение в API и модель асинхронного программирования сервиса.

MediaLibraryService обратно совместим с MediaBrowserService. Клиентское приложение, использующее MediaBrowserCompat или MediaControllerCompat, продолжает работать без изменений кода при подключении к MediaLibraryService. Для клиента не имеет значения, использует ли ваше приложение MediaLibraryService или устаревший MediaBrowserServiceCompat.

Схема компонентов приложения с сервисом, действием и внешними приложениями.
Рисунок 1. Обзор компонентов мультимедийного приложения
  1. Чтобы обеспечить обратную совместимость, необходимо зарегистрировать оба интерфейса в файле AndroidManifest.xml. Таким образом, клиент находит ваш сервис по требуемому интерфейсу сервиса:

    <service android:name=".MusicService" android:exported="true">
        <intent-filter>
            <action android:name="androidx.media3.session.MediaLibraryService"/>
            <action android:name="android.media.browse.MediaBrowserService" />
        </intent-filter>
    </service>
    
  2. В файле build.gradle, где вы управляете зависимостями, добавьте зависимость реализации к модулю сеанса AndroidX Media3 и удалите устаревшую зависимость:

    implementation "androidx.media3:media3-session:1.11.1"
    
  3. Измените сервис, чтобы он наследовал от MediaLibraryService, а не от MediaBrowserService. Как уже говорилось, MediaLibraryService совместим с устаревшим MediaBrowserService. Соответственно, более широкий API, который сервис предлагает клиентам, остается прежним. Поэтому, скорее всего, в приложении можно будет сохранить большую часть логики, необходимой для реализации MediaBrowserService, и адаптировать ее для нового MediaLibraryService.

    Основные отличия от устаревшего метода MediaBrowserServiceCompat:

    • Реализуйте методы жизненного цикла сервиса. Методы, которые необходимо переопределить в самом сервисе, – это onCreate/onDestroy. В них приложение выделяет и освобождает сеанс библиотеки, проигрыватель и другие ресурсы. Помимо стандартных методов жизненного цикла сервиса, приложение должно переопределить onGetSession(MediaSession.ControllerInfo), чтобы вернуть MediaLibrarySession, созданный в onCreate.

    • Реализуйте MediaLibraryService.MediaLibrarySessionCallback. Чтобы создать сеанс, требуется MediaLibraryService.MediaLibrarySessionCallback, который реализует фактические методы API домена. Поэтому вместо переопределения методов API устаревшего сервиса вы будете переопределять методы MediaLibrarySession.Callback.

      Затем обратный вызов используется для создания MediaLibrarySession:

      Kotlin

      mediaLibrarySession = MediaLibrarySession.Builder(context, player, MySessionCallback()).build()

      Java

      mediaLibrarySession =
          new MediaLibrarySession.Builder(context, player, new MySessionCallback()).build();

      Полный API MediaLibrarySessionCallback можно найти в документации по API.

    • Реализуйте MediaSession.Callback.onAddMediaItems(). Обратный вызов onAddMediaItems(MediaSession, ControllerInfo, List<MediaItem>) обслуживает различные текущие и устаревшие методы API, которые добавляют мультимедийные объекты в проигрыватель для воспроизведения с обратной совместимостью. Это касается методов MediaController.set/addMediaItems() контроллера Media3, а также методов TransportControls.prepareFrom*/playFrom* устаревшего API. Пример реализации обратного вызова можно найти в PlaybackService демонстрационного приложения.

    • В AndroidX Media3 вместо MediaBrowserCompat.MediaItem и MediaMetadataCompat используется androidx.media3.common.MediaItem. Части кода, связанные с устаревшими классами, необходимо изменить или сопоставить с MediaItem Media3.

    • Общая модель асинхронного программирования изменена на Futures в отличие от отсоединяемого подхода Result в MediaBrowserServiceCompat. Реализация сервиса может возвращать асинхронный объект ListenableFuture вместо отсоединения результата или возвращать немедленный объект Future, чтобы напрямую возвращать значение.

Удаление PlayerNotificationManager

MediaLibraryService автоматически поддерживает уведомления мультимедиа, а PlayerNotificationManager можно удалить при использовании MediaLibraryService или MediaSessionService.

Приложение может настроить уведомление, задав собственное значение MediaNotification.Provider в onCreate(), которое заменит DefaultMediaNotificationProvider. Затем MediaLibraryService запускает службу в активном режиме.

Переопределив MediaLibraryService.updateNotification(), приложение может полностью взять на себя управление публикацией уведомлений и запуском/остановкой службы в активном режиме.

Перенос кода клиента с помощью MediaBrowser

В AndroidX Media3 MediaBrowser реализует интерфейсы MediaController/Player и может использоваться для управления воспроизведением медиаконтента, а также для просмотра медиатеки. Если в старой версии вам приходилось создавать MediaBrowserCompat и MediaControllerCompat, то в Media3 вы можете использовать только MediaBrowser.

Можно создать MediaBrowser и дождаться подключения к сервису:

Kotlin

scope.launch {
  val sessionToken = SessionToken(context, ComponentName(context, "MusicService"))
  browser =
    MediaBrowser.Builder(context, sessionToken)
      .setListener(BrowserListener())
      .buildAsync()
      .await()
}

Java

SessionToken sessionToken =
    new SessionToken(context, new ComponentName(context, "MusicService"));
ListenableFuture<MediaBrowser> browserFuture =
    new MediaBrowser.Builder(context, sessionToken)
        .setListener(new BrowserListener())
        .buildAsync();

Чтобы узнать, как создать MediaController для управления воспроизведением в фоновом режиме, прочитайте статью Управление воспроизведением в сеансе мультимедиа.

Дальнейшие действия и удаление

Ошибки нестабильного API

После перехода на Media3 вы можете увидеть ошибки lint, связанные с использованием нестабильных API. Эти API безопасны для использования, а ошибки линтинга – побочный продукт наших новых гарантий бинарной совместимости. Если вам не требуется строгая двоичная совместимость, эти ошибки можно безопасно подавить с помощью аннотации @OptIn.

Фон

Ни ExoPlayer v1, ни ExoPlayer v2 не гарантируют строгую двоичную совместимость библиотеки между последующими версиями. API ExoPlayer имеет очень большую поверхность, чтобы приложения могли настраивать практически каждый аспект воспроизведения. В последующих версиях ExoPlayer иногда переименовывались символы или вносились другие критические изменения, например добавлялись новые обязательные методы в интерфейсы. В большинстве случаев мы смягчали последствия, добавляя новый символ и одновременно прекращая поддержку старого в нескольких версиях, чтобы у разработчиков было время перенести свои данные, но это не всегда было возможно.

Эти критические изменения привели к двум проблемам для пользователей библиотек ExoPlayer версий 1 и 2:

  1. Обновление версии ExoPlayer может привести к тому, что код перестанет компилироваться.
  2. Если приложение зависело от ExoPlayer как напрямую, так и через промежуточную библиотеку, нужно было убедиться, что обе зависимости имеют одну и ту же версию. В противном случае несовместимость двоичных файлов могла привести к сбоям во время выполнения.

Улучшения в Media3

Media3 гарантирует бинарную совместимость для подмножества API. Части, которые не гарантируют двоичную совместимость, отмечены как @UnstableApi. Чтобы четко обозначить различие, при использовании символов нестабильного API генерируется ошибка lint, если они не аннотированы с помощью @OptIn.

После перехода с ExoPlayer v2 на Media3 вы можете увидеть много ошибок линтинга нестабильного API. Из-за этого может показаться, что Media3 менее стабильна, чем ExoPlayer версии 2. На самом деле это не так: Нестабильные части Media3 API имеют тот же уровень стабильности, что и весь ExoPlayer v2 API, а гарантии стабильности Media3 API в ExoPlayer v2 вообще не предусмотрены. Разница лишь в том, что теперь ошибка линтинга предупреждает вас о разных уровнях стабильности.

Как устранить ошибки линтинга нестабильного API

Подробнее о том, как аннотировать использование нестабильных API в Java и Kotlin с помощью @OptIn, рассказывается в разделе об устранении неполадок с этими ошибками.

Устаревшие API

Вызовы устаревших API в Android Studio могут быть зачеркнуты. Рекомендуем заменить такие вызовы подходящей альтернативой. Наведите указатель мыши на символ, чтобы увидеть JavaDoc, в котором указано, какой API использовать вместо этого.

Скриншот: как показать JavaDoc с альтернативой устаревшему методу
Рисунок 3. Подсказка JavaDoc в Android Studio предлагает альтернативу для любого устаревшего символа.

Примеры кода и демо-приложения

  • Демонстрационное приложение AndroidX Media3 (для мобильных устройств и Wear OS)
    • Специальные действия
    • Уведомление системного интерфейса, MediaButton/BT
    • Управление воспроизведением с помощью Google Ассистента
  • UAMP: Android Media Player (ветка media3) (мобильные устройства, AutomotiveOS)
    • Уведомление системного интерфейса, MediaButton/BT, возобновление воспроизведения
    • Управление воспроизведением с помощью Google Ассистента или Wear OS
    • AutomotiveOS: пользовательская команда и вход