Руководство по переходу на 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).

Media 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. Проверьте, как скрипт изменил ваш код. Используйте инструмент 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 и устаревшие API после завершения первоначальной миграции.

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

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

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

Устаревшие API

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

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

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

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