Создание простого приложения медиапроигрывателя с помощью Media3 ExoPlayer

Jetpack Media3 определяет интерфейс Player, который описывает базовые функции для воспроизведения видео- и аудиофайлов. ExoPlayer – это реализация по умолчанию этого интерфейса в Media3. Мы рекомендуем использовать ExoPlayer, поскольку он предоставляет полный набор функций, охватывающих большинство сценариев воспроизведения, и может быть настроен для обработки любых дополнительных сценариев, которые могут у вас возникнуть. ExoPlayer также абстрагирует фрагментацию устройств и ОС, поэтому ваш код будет стабильно работать во всей экосистеме Android. ExoPlayer включает:

На этой странице описаны основные шаги по созданию приложения для воспроизведения. Более подробную информацию можно найти в руководствах по Media3 ExoPlayer.

Начало работы

Для начала добавьте зависимость от модулей ExoPlayer, UI и Common в Jetpack Media3:

Kotlin

implementation("androidx.media3:media3-exoplayer:1.11.1")
implementation("androidx.media3:media3-ui:1.11.1")
implementation("androidx.media3:media3-common:1.11.1")

Яркий

implementation "androidx.media3:media3-exoplayer:1.11.1"
implementation "androidx.media3:media3-ui:1.11.1"
implementation "androidx.media3:media3-common:1.11.1"

В зависимости от ваших потребностей вам могут потребоваться дополнительные модули из Media3, например exoplayer-dash для воспроизведения потоков в формате DASH.

Замените 1.11.1 на нужную версию библиотеки. Последнюю версию можно найти в примечаниях к выпуску.

Создание медиапроигрывателя

В Media3 можно использовать встроенную реализацию интерфейса Player – ExoPlayer – или создать собственную.

Создание ExoPlayer

Проще всего создать экземпляр ExoPlayer следующим образом:

Kotlin

val player = ExoPlayer.Builder(context).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

Вы можете создать медиапроигрыватель в методе жизненного цикла onCreate() объекта Activity, Fragment или Service, в котором он находится.

Builder включает ряд параметров настройки, которые могут вас заинтересовать, например:

Media3 предоставляет компонент пользовательского интерфейса PlayerView, который можно включить в файл макета приложения. Этот компонент включает в себя PlayerControlView для элементов управления воспроизведением, SubtitleView для показа субтитров и Surface для воспроизведения видео.

Подготовка проигрывателя

Добавьте мультимедийные объекты в плейлист для воспроизведения с помощью таких методов, как setMediaItem() и addMediaItem(). Затем вызовите метод prepare(), чтобы начать загрузку медиаконтента и получить необходимые ресурсы.

Не выполняйте эти действия, пока приложение не будет открыто на переднем плане. Если проигрыватель находится в Activity или Fragment, это означает, что он подготавливается в методе жизненного цикла onStart() на уровне API 24 и выше или в методе жизненного цикла onResume() на уровне API 23 и ниже. Если проигрыватель находится в Service, вы можете подготовить его в onCreate(). Пример реализации методов жизненного цикла можно найти в практической работе по ExoPlayer.

Управление проигрывателем

После подготовки проигрывателя вы можете управлять воспроизведением, вызывая методы проигрывателя, например:

  • play() и pause() – начать и приостановить воспроизведение.
  • seekTo() – перемотать до определенного момента в текущем мультимедийном объекте.
  • seekToNextMediaItem() и seekToPreviousMediaItem() – навигация по плейлисту.

Компоненты пользовательского интерфейса, такие как PlayerView или PlayerControlView, будут обновляться соответствующим образом при привязке к проигрывателю.

Освободить игрока

Для воспроизведения могут требоваться ресурсы, доступ к которым ограничен, например декодеры видео. Поэтому важно вызывать метод release(), чтобы освобождать ресурсы, когда проигрыватель больше не нужен.

Если проигрыватель находится в Activity или Fragment, освободите его в методе жизненного цикла onStop() на уровне API 24 и выше или в методе onPause() на уровне API 23 и ниже. Если игрок находится в Service, вы можете выпустить его в onDestroy(). Пример реализации методов жизненного цикла можно найти в практической работе по ExoPlayer.

Управление воспроизведением с помощью сеанса мультимедиа

В Android сеансы мультимедиа обеспечивают стандартизированный способ взаимодействия с медиапроигрывателем за пределами процесса. Подключив сеанс воспроизведения к проигрывателю, вы сможете передавать информацию о воспроизведении медиаконтента на внешние устройства и получать от них команды, например для интеграции с системными элементами управления мультимедиа на мобильных устройствах и устройствах с большим экраном.

Чтобы использовать сеансы мультимедиа, добавьте зависимость от модуля Media3 Session:

Kotlin

implementation("androidx.media3:media3-session:1.11.1")

Яркий

implementation "androidx.media3:media3-session:1.11.1"

Как создать сеанс воспроизведения медиаконтента

Чтобы создать объект MediaSession, инициализируйте проигрыватель следующим образом:

Kotlin

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

Java

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

Media3 автоматически синхронизирует состояние Player с состоянием MediaSession. Это работает с любой реализацией Player, включая ExoPlayer, CastPlayer и пользовательские реализации.

Предоставление управления другим клиентам

Клиентские приложения могут использовать контроллер мультимедиа для управления воспроизведением сеанса мультимедиа. Чтобы получать эти запросы, задайте объект callback при создании MediaSession.

Когда контроллер собирается подключиться к сеансу воспроизведения медиаконтента, вызывается метод onConnect(). Вы можете использовать предоставленный ControllerInfo, чтобы решить, принять или отклонить запрос. Пример можно посмотреть в демоприложении Media3 Session.

После подключения контроллер может отправлять команды воспроизведения в сеанс. Затем сеанс передает эти команды проигрывателю. Команды воспроизведения и плейлиста, определенные в интерфейсе Player, автоматически обрабатываются сеансом.

Другие методы обратного вызова позволяют обрабатывать, например, запросы на пользовательские команды воспроизведения и изменение плейлиста. Эти обратные вызовы также включают объект ControllerInfo, чтобы вы могли определять контроль доступа для каждого запроса.

Воспроизведение медиаконтента в фоновом режиме

Чтобы воспроизведение медиаконтента продолжалось, когда приложение неактивно, например чтобы пользователь мог слушать музыку, аудиокниги или подкасты, даже если приложение не открыто, Player и MediaSession должны быть инкапсулированы в активную службу. Для этого в Media3 есть интерфейс MediaSessionService.

Как внедрять правило MediaSessionService

Создайте класс, расширяющий MediaSessionService, и создайте экземпляр MediaSession в методе жизненного цикла onCreate().

Kotlin

class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null

    // Create your Player and MediaSession in the onCreate lifecycle event
    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
    }

    // Remember to release the player and media session in onDestroy
    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }
}

Java

public class PlaybackService extends MediaSessionService {
    private MediaSession mediaSession = null;

    @Override
    public void onCreate() {
        super.onCreate();
        ExoPlayer player = new ExoPlayer.Builder(this).build();
        mediaSession = new MediaSession.Builder(this, player).build();
    }

    @Override
    public void onDestroy() {
        mediaSession.getPlayer().release();
        mediaSession.release();
        mediaSession = null;
        super.onDestroy();
    }
}

В манифесте объявите класс Service с фильтром интентов MediaSessionService и запросите разрешение FOREGROUND_SERVICE на запуск активной службы:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

Наконец, в созданном классе переопределите метод onGetSession(), чтобы управлять доступом клиентов к сеансу воспроизведения. Чтобы принять запрос на подключение, верните MediaSession, а чтобы отклонить – null.

Kotlin

// This example always accepts the connection request
override fun onGetSession(
    controllerInfo: MediaSession.ControllerInfo
): MediaSession? = mediaSession

Java

@Override
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
  // This example always accepts the connection request
  return mediaSession;
}

Подключение к интерфейсу

Теперь, когда сеанс воспроизведения медиаконтента находится в Service отдельно от Activity или Fragment, где размещен интерфейс проигрывателя, вы можете использовать MediaController, чтобы связать их. В методе onStart() объекта Activity или Fragment с вашим интерфейсом создайте SessionToken для MediaSession, а затем используйте SessionToken, чтобы создать MediaController. Создание MediaController происходит асинхронно.

Kotlin

override fun onStart() {
  val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
  val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
  controllerFuture.addListener(
    {
        // Call controllerFuture.get() to retrieve the MediaController.
        // MediaController implements the Player interface, so it can be
        // attached to the PlayerView UI component.
        playerView.setPlayer(controllerFuture.get())
      },
    MoreExecutors.directExecutor()
  )
}

Java

@Override
public void onStart() {
  SessionToken sessionToken =
    new SessionToken(this, new ComponentName(this, PlaybackService.class));
  ListenableFuture<MediaController> controllerFuture =
    new MediaController.Builder(this, sessionToken).buildAsync();
  controllerFuture.addListener(() -> {
    // Call controllerFuture.get() to retrieve the MediaController.
    // MediaController implements the Player interface, so it can be
    // attached to the PlayerView UI component.
    playerView.setPlayer(controllerFuture.get());
  }, MoreExecutors.directExecutor())
}

MediaController реализует интерфейс Player, поэтому вы можете использовать те же методы, например play() и pause(), для управления воспроизведением. Как и в случае с другими компонентами, не забудьте освободить MediaController, когда он больше не нужен, в методе жизненного цикла onStop() объекта Activity, вызвав MediaController.releaseFuture().

Публикация уведомления

Активные службы должны показывать уведомление, пока они работают. MediaSessionService автоматически создаст для вас уведомление MediaStyle в виде MediaNotification. Чтобы создать собственное уведомление, создайте объект MediaNotification.Provider с помощью DefaultMediaNotificationProvider.Builder или создайте собственную реализацию интерфейса поставщика. Добавьте поставщика в MediaSession с помощью setMediaNotificationProvider.

Реклама библиотеки контента

MediaLibraryService расширяет возможности MediaSessionService, позволяя клиентским приложениям просматривать медиаконтент, предоставляемый вашим приложением. Клиентские приложения реализуют MediaBrowser для взаимодействия с вашим MediaLibraryService.

Реализация MediaLibraryService похожа на реализацию MediaSessionService, но в onGetSession() нужно возвращать MediaLibrarySession вместо MediaSession. По сравнению с MediaSession.Callback в MediaLibrarySession.Callback включены дополнительные методы, которые позволяют клиенту браузера перемещаться по контенту, предлагаемому вашим библиотечным сервисом.

Как и в случае с MediaSessionService, объявите MediaLibraryService в манифесте и запросите разрешение FOREGROUND_SERVICE, чтобы запустить активную службу.

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

В примере выше фильтр интентов включает как MediaLibraryService, так и устаревший MediaBrowserService (для обратной совместимости). Дополнительный фильтр интентов позволяет клиентским приложениям, использующим API MediaBrowserCompat, распознавать ваш Service.

MediaLibrarySession позволяет представить библиотеку контента в виде древовидной структуры с одним корневым элементом MediaItem. Каждый узел MediaItem в дереве может иметь любое количество дочерних узлов MediaItem. Вы можете показывать разные корневые элементы или разные деревья в зависимости от запроса клиентского приложения. Например, дерево, которое вы возвращаете клиенту, ищущему список рекомендуемых мультимедийных объектов, может содержать только корневой узел MediaItem и один уровень дочерних узлов MediaItem, тогда как дерево, которое вы возвращаете другому клиентскому приложению, может представлять более полную библиотеку контента.

Создание MediaLibrarySession

MediaLibrarySession расширяет API MediaSession, добавляя API для просмотра контента. По сравнению с обратным вызовом MediaSession обратный вызов MediaLibrarySession добавляет такие методы, как:

  • onGetLibraryRoot() – когда клиент запрашивает корневой каталог MediaItem дерева контента.
  • onGetChildren() – когда клиент запрашивает дочерние элементы MediaItem в дереве контента.
  • onGetSearchResult() – когда клиент запрашивает результаты поиска из дерева контента по заданному запросу.

Соответствующие методы обратного вызова будут включать объект LibraryParams с дополнительными сигналами о типе дерева контента, который интересует клиентское приложение.