Часто требуется воспроизводить медиаконтент, когда приложение не находится на переднем плане. Например, аудиоплеер обычно продолжает воспроизводить музыку, когда пользователь заблокировал устройство или использует другое приложение. Библиотека 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 или фрагменте, содержащем интерфейс проигрывателя, можно установить связь между интерфейсом и сеансом мультимедиа с помощью 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, значениями, доступными локально, поскольку сетевой доступ может быть ещё недоступен. Вы также можете добавить MediaConstants.EXTRAS_KEY_COMPLETION_STATUS и MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE в MediaMetadata.extras, чтобы указать позицию возобновления воспроизведения.
Расширенные настройки контроллера и обратная совместимость
Часто в интерфейсе приложения используется 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-файл.