Руководство по переходу на 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 нужно перенести, и о требованиях к проекту приложения.

Зачем переходить на 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/Enter, F2 – Alt/Enter и т. д.).

    • Перенос данных из 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. Проверьте, как скрипт изменил ваш код. Используйте инструмент сравнения и исправьте возможные проблемы (если вы считаете, что скрипт имеет общую проблему, которая была введена без передачи параметра -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. Части кода, связанные с устаревшими классами, необходимо изменить или сопоставить с Media3 MediaItem.

    • Общая модель асинхронного программирования изменилась на 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 могут появиться ошибки линтера, связанные с использованием нестабильных API. Эти API безопасны в использовании, а ошибки линтера – побочный эффект новых гарантий совместимости двоичных файлов. Если вам не требуется строгая двоичная совместимость, эти ошибки можно безопасно подавить с помощью аннотации @OptIn.

Фон

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

Из-за этих критических изменений у пользователей библиотек ExoPlayer версий 1 и 2 возникли две проблемы:

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

Улучшения в Media3

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

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

Как устранять ошибки линтера, связанные с нестабильными API

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

Устаревшие API

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

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

Примеры кода и демонстрационные приложения

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