Часто бывает нужно воспроизводить медиаконтент, когда приложение не находится на переднем плане. Например, музыкальный проигрыватель обычно продолжает воспроизводить музыку, когда пользователь заблокировал свое устройство или использует другое приложение. Библиотека Media3 предоставляет ряд интерфейсов, позволяющих поддерживать фоновое воспроизведение.
Используйте MediaSessionService
Чтобы включить фоновое воспроизведение, необходимо поместить Player и MediaSession в отдельный сервис.
Это позволяет устройству продолжать воспроизведение медиаконтента, даже когда приложение не находится на переднем плане.
MediaSessionService позволяет медиасеансу выполняться отдельно от действия приложения.Если вы размещаете проигрыватель в сервисе, используйте MediaSessionService.
Для этого создайте класс, расширяющий MediaSessionService, и создайте в нем сеанс воспроизведения.
Использование MediaSessionService позволяет внешним клиентам, таким как Google Ассистент, системные элементы управления мультимедиа, кнопки мультимедиа на периферийных устройствах или дополнительные устройства, например Wear OS, обнаруживать ваш сервис, подключаться к нему и управлять воспроизведением, не обращаясь к пользовательскому интерфейсу вашего приложения. К одному MediaSessionService может быть одновременно подключено несколько клиентских приложений, каждое со своим MediaController.
Реализация жизненного цикла сервиса
Вам нужно реализовать два метода жизненного цикла сервиса:
onCreate()вызывается, когда первый контроллер собирается подключиться, а сервис создается и запускается. Это лучшее место для созданияPlayerиMediaSession.onDestroy()вызывается при остановке сервиса. Все ресурсы, включая проигрыватель и сеанс, должны быть освобождены.
Вы можете переопределить onTaskRemoved(Intent), чтобы настроить, что происходит, когда пользователь удаляет приложение из списка недавних задач. По умолчанию сервис продолжает работать, если воспроизведение продолжается, и останавливается в противном случае.
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() } override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
class PlaybackService extends MediaSessionService { private MediaSession mediaSession = null; // Create your Player and MediaSession in the onCreate lifecycle event @Override public void onCreate() { super.onCreate(); ExoPlayer player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player).build(); } // Remember to release the player and media session in onDestroy @Override public void onDestroy() { mediaSession.getPlayer().release(); mediaSession.release(); mediaSession = null; super.onDestroy(); } @Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { return mediaSession; } }
В качестве альтернативы фоновому воспроизведению вы можете останавливать сервис в любом случае, когда пользователь закрывает приложение:
Kotlin
@OptIn(UnstableApi::class) override fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
Java
@OptIn(markerClass = UnstableApi.class) @Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
Для любой другой реализации onTaskRemoved вручную вы можете использовать isPlaybackOngoing(), чтобы проверить, считается ли воспроизведение продолжающимся и запущена ли активная служба.
Предоставьте доступ к сеансу воспроизведения медиаконтента.
Переопределите метод onGetSession(), чтобы предоставить другим клиентам доступ к сеансу воспроизведения, созданному вместе с сервисом.
Kotlin
class PlaybackService : MediaSessionService() { // [...] lifecycle methods omitted override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
class PlaybackService extends MediaSessionService { // [...] lifecycle methods omitted @Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { return mediaSession; } }
Объявите сервис в манифесте
Чтобы запустить активную службу воспроизведения, приложению требуются разрешения FOREGROUND_SERVICE и FOREGROUND_SERVICE_MEDIA_PLAYBACK.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
В манифесте также нужно объявить класс Service с фильтром интентов MediaSessionService и элементом foregroundServiceType, который включает mediaPlayback.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
Управление воспроизведением с помощью MediaController
В Activity или Fragment, содержащем интерфейс проигрывателя, вы можете установить связь между интерфейсом и сеансом мультимедиа с помощью MediaController. Ваш интерфейс использует контроллер мультимедиа для отправки команд из вашего интерфейса в проигрыватель в рамках сеанса. Подробные сведения о создании и использовании MediaController вы найдете в руководстве Создание MediaController.
Обработка команд MediaController
MediaSession получает команды от контроллера через MediaSession.Callback. При инициализации MediaSession создается реализация MediaSession.Callback по умолчанию, которая автоматически обрабатывает все команды, отправляемые MediaController в проигрыватель.
Уведомление
MediaSessionService автоматически создает MediaNotification, который подходит для большинства случаев. По умолчанию публикуется уведомление MediaStyle, которое обновляется по мере поступления информации о сеансе воспроизведения и содержит элементы управления. MediaNotification знает о вашем сеансе и может использоваться для управления воспроизведением в любых других приложениях, подключенных к тому же сеансу.
Например, если в приложении для потоковой передачи музыки используется MediaSessionService, будет создано уведомление MediaNotification, в котором отображаются название, исполнитель и обложка альбома для воспроизводимого мультимедийного объекта, а также элементы управления воспроизведением на основе конфигурации MediaSession.
Обязательные метаданные можно указать в медиафайле или объявить как часть мультимедийного объекта, как показано в следующем фрагменте кода:
Kotlin
val mediaItem = MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build() ) .build() mediaController.setMediaItem(mediaItem) mediaController.prepare() mediaController.play()
Java
MediaItem mediaItem = new MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( new MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build()) .build(); mediaController.setMediaItem(mediaItem); mediaController.prepare(); mediaController.play();
Жизненный цикл уведомлений
Уведомление создается, как только в плейлисте Player появляется MediaItem экземпляров.
Все уведомления обновляются автоматически в зависимости от состояния Player и MediaSession.
Пока активная служба работает, уведомление удалить нельзя. Чтобы немедленно удалить уведомление, вызовите Player.release() или очистите плейлист с помощью Player.clearMediaItems().
Если проигрыватель приостановлен, остановлен или не работает более 10 минут без дальнейших действий пользователя, сервис автоматически переходит из состояния активной службы, чтобы система могла его уничтожить. Вы можете реализовать возобновление воспроизведения, чтобы пользователь мог перезапустить жизненный цикл сервиса и возобновить воспроизведение позже.
Настройка уведомлений
Метаданные воспроизводимого контента можно настроить, изменив MediaItem.MediaMetadata. Если вы хотите обновить метаданные существующего объекта, используйте Player.replaceMediaItem. Это позволит обновить метаданные, не прерывая воспроизведение.
Вы также можете настроить некоторые кнопки, которые показываются в уведомлении, задав собственные настройки кнопок мультимедиа для элементов управления мультимедиа Android. Подробнее о том, как настроить элементы управления мультимедиа в Android…
Чтобы настроить само уведомление, создайте MediaNotification.Provider с помощью DefaultMediaNotificationProvider.Builder или создайте собственную реализацию интерфейса поставщика. Добавьте поставщика в MediaSessionService с помощью setMediaNotificationProvider.
Возобновление воспроизведения
После завершения работы MediaSessionService и даже после перезагрузки устройства можно предложить пользователям возобновить воспроизведение с того места, где они остановились. По умолчанию эта функция отключена. Это означает, что пользователь не может возобновить воспроизведение, если сервис не запущен. Чтобы включить эту функцию, необходимо объявить получателя кнопки мультимедиа и реализовать метод onPlaybackResumption.
Объявите получатель медиакнопки Media3
Сначала объявите MediaButtonReceiver в манифесте:
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
Как реализовать обратный вызов для возобновления воспроизведения
Когда устройство Bluetooth или функция возобновления в системном интерфейсе Android запрашивает возобновление воспроизведения, вызывается метод обратного вызова onPlaybackResumption().
Kotlin
override fun onPlaybackResumption( mediaSession: MediaSession, controller: MediaSession.ControllerInfo, isForPlayback: Boolean, ): ListenableFuture<MediaSession.MediaItemsWithStartPosition> { val settableFuture = SettableFuture.create<MediaSession.MediaItemsWithStartPosition>() settableFuture.addListener( { // Your app is responsible for storing the playlist, metadata (like title // and artwork) of the current item and the start position to use here. val resumptionPlaylist = restorePlaylist() settableFuture.set(resumptionPlaylist) }, MoreExecutors.directExecutor(), ) return settableFuture }
Java
@Override public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption( MediaSession mediaSession, ControllerInfo controller, boolean isForPlayback) { SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create(); settableFuture.addListener( () -> { // Your app is responsible for storing the playlist, metadata (like title // and artwork) of the current item and the start position to use here. MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist(); settableFuture.set(resumptionPlaylist); }, MoreExecutors.directExecutor()); return settableFuture; }
Если вы сохранили другие параметры, такие как скорость воспроизведения, режим повтора или режим воспроизведения в случайном порядке, onPlaybackResumption() – хорошее место для настройки проигрывателя с этими параметрами до того, как Media3 подготовит проигрыватель и начнет воспроизведение после завершения обратного вызова.
Этот метод вызывается во время загрузки, чтобы создать уведомление о возобновлении работы системного интерфейса Android после перезагрузки устройства, если для isForPlayback задано значение false. Для расширенного уведомления рекомендуется заполнить поля MediaMetadata, такие как title и artworkData или artworkUri текущего элемента, локально доступными значениями, поскольку доступ к сети может быть ещё невозможен. Вы также можете добавить в MediaMetadata.extras параметры MediaConstants.EXTRAS_KEY_COMPLETION_STATUS и MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE, чтобы указать позицию, с которой нужно возобновить воспроизведение.
Расширенная настройка контроллера и обратная совместимость
Обычно для управления воспроизведением и показа плейлиста в интерфейсе приложения используется MediaController. В то же время сеанс доступен внешним клиентам, таким как элементы управления мультимедиа Android и Ассистент на мобильном устройстве или телевизоре, Wear OS для часов и Android Auto в автомобилях. Пример приложения, в котором реализован такой сценарий, – демонстрационное приложение для сеансов Media3.
Эти внешние клиенты могут использовать API, такие как MediaControllerCompat устаревшей библиотеки AndroidX или android.media.session.MediaController платформы Android. Media3 полностью совместима с устаревшей библиотекой и обеспечивает совместимость с API платформы Android.
Определение доверенных контроллеров
Любое приложение может попытаться подключиться к вашему сеансу или библиотеке мультимедиа. Если вы хотите ограничить доступ к системным контроллерам, контроллерам с разрешением на управление медиаконтентом и вашему приложению, используйте ControllerInfo.isTrusted() для базовой проверки доступа. Вы также можете указать более конкретные контроллеры, например контроллер уведомлений мультимедиа или контроллеры Android Auto, как описано в следующих разделах.
Как использовать контроллер уведомлений мультимедиа
Важно понимать, что эти устаревшие контроллеры и контроллеры платформы имеют одно и то же состояние, а видимость нельзя настроить по контроллеру (например, доступные PlaybackState.getActions() и PlaybackState.getCustomActions()). Вы можете использовать контроллер уведомлений мультимедиа, чтобы настроить состояние, заданное в сеансе мультимедиа платформы, для совместимости с этими устаревшими контроллерами и контроллерами платформы.
Например, приложение может предоставить реализацию MediaSession.Callback.onConnect(), чтобы задать доступные команды и настройки медиакнопок специально для сеанса платформы, как показано ниже.
Kotlin
override fun onConnectAsync( session: MediaSession, controller: MediaSession.ControllerInfo, ): ListenableFuture<ConnectionResult> { if (session.isMediaNotificationController(controller)) { val playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build() // Custom button preferences and commands to configure the platform session. return immediateFuture( AcceptedResultBuilder(session, controller) .setMediaButtonPreferences(listOf(seekBackButton, seekForwardButton)) .setAvailablePlayerCommands(playerCommands) .build() ) } // Default commands with default button preferences for all other controllers. return immediateFuture(AcceptedResultBuilder(session, controller).build()) }
Java
@Override public ListenableFuture<ConnectionResult> onConnectAsync( MediaSession session, MediaSession.ControllerInfo controller) { if (session.isMediaNotificationController(controller)) { Player.Commands playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS .buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build(); // Custom button preferences and commands to configure the platform session. return immediateFuture( new AcceptedResultBuilder(session, controller) .setMediaButtonPreferences(ImmutableList.of(seekBackButton, seekForwardButton)) .setAvailablePlayerCommands(playerCommands) .build()); } // Default commands with default button preferences for all other controllers. return immediateFuture(new AcceptedResultBuilder(session, controller).build()); }
Разрешите Android Auto отправлять специальные команды
При использовании MediaLibraryService и для поддержки Android Auto в мобильном приложении контроллеру Android Auto требуются соответствующие доступные команды, иначе Media3 будет отклонять входящие пользовательские команды от этого контроллера:
Kotlin
override fun onConnectAsync( session: MediaSession, controller: MediaSession.ControllerInfo, ): ListenableFuture<ConnectionResult> { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon().add(customCommand).build() if (session.isMediaNotificationController(controller)) { // ... See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return immediateFuture( AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build() ) } // Default commands for all other controllers. return immediateFuture(AcceptedResultBuilder(session, controller).build()) }
Java
@Override public ListenableFuture<ConnectionResult> onConnectAsync( MediaSession session, MediaSession.ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon().add(customCommand).build(); if (session.isMediaNotificationController(controller)) { // ... See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return immediateFuture( new AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build()); } // Default commands for all other controllers. return immediateFuture(new AcceptedResultBuilder(session, controller).build()); }
В демонстрационном приложении есть автомобильный модуль, который показывает, как поддерживается Automotive OS. Для этого требуется отдельный APK-файл.