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

MediaSessionService
позволяет запускать медиасеанс отдельно от активности приложения. При размещении проигрывателя внутри сервиса следует использовать MediaSessionService
. Для этого создайте класс, расширяющий MediaSessionService
, и создайте внутри него медиасеанс.
Использование MediaSessionService
позволяет внешним клиентам, таким как Google Assistant, системные элементы управления мультимедиа, кнопки управления мультимедиа на периферийных устройствах или сопутствующие устройства, такие как Wear OS, обнаруживать ваш сервис, подключаться к нему и управлять воспроизведением, не обращаясь к пользовательскому интерфейсу вашего приложения. Фактически, к одному и тому же MediaSessionService
может быть подключено несколько клиентских приложений одновременно, каждое из которых будет иметь свой собственный MediaController
.
Реализация жизненного цикла услуги
Вам необходимо реализовать два метода жизненного цикла вашей услуги:
-
onCreate()
вызывается перед подключением первого контроллера, а также перед созданием и запуском экземпляра службы. Это лучшее место для созданияPlayer
иMediaSession
. -
onDestroy()
вызывается при остановке службы. Все ресурсы, включая проигрыватель и сессию, должны быть освобождены.
При желании вы можете переопределить onTaskRemoved(Intent)
, чтобы настроить действие, которое происходит при закрытии приложения пользователем из списка недавних задач. По умолчанию служба продолжает работать, если воспроизведение продолжается, и останавливается в противном случае.
Котлин
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // Create your player and media session 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() } }
Ява
public 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 fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
Ява
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
Для любой другой ручной реализации onTaskRemoved
вы можете использовать isPlaybackOngoing()
для проверки того, считается ли воспроизведение текущим и запущена ли служба переднего плана.
Предоставить доступ к медиа-сессии
Переопределите метод onGetSession()
, чтобы предоставить другим клиентам доступ к вашему медиасеансу, созданному при создании службы.
Котлин
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // [...] lifecycle methods omitted override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Ява
public class PlaybackService extends MediaSessionService { private MediaSession mediaSession = null; // [...] 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
.
Необходимые метаданные могут быть предоставлены в медиа-файле или объявлены как часть медиа-элемента, как в следующем фрагменте:
Котлин
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()
Ява
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 Media. Подробнее о настройке элементов управления Android Media читайте здесь .
Для дальнейшей настройки уведомления создайте 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()
.
Котлин
override fun onPlaybackResumption( mediaSession: MediaSession, controller: ControllerInfo ): ListenableFuture<MediaItemsWithStartPosition> { val settable = SettableFuture.create<MediaItemsWithStartPosition>() scope.launch { // 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() settable.set(resumptionPlaylist) } return settable }
Ява
@Override public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption( MediaSession mediaSession, ControllerInfo controller ) { 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 после перезагрузки устройства. Для создания расширенного уведомления рекомендуется заполнить поля MediaMetadata
, такие как title
и artworkData
или artworkUri
текущего элемента, локально доступными значениями, поскольку сетевой доступ может быть ещё недоступен. Вы также можете добавить MediaConstants.EXTRAS_KEY_COMPLETION_STATUS
и MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE
в MediaMetadata.extras
, чтобы указать позицию возобновления воспроизведения.
Расширенная конфигурация контроллера и обратная совместимость
Распространенный сценарий — использование MediaController
в пользовательском интерфейсе приложения для управления воспроизведением и отображения плейлиста. При этом сеанс доступен внешним клиентам, таким как элементы управления мультимедиа Android и Assistant на мобильном устройстве или телевизоре, Wear OS для часов и Android Auto в автомобилях. Демонстрационное приложение Media3 session — пример приложения, реализующего такой сценарий.
Эти внешние клиенты могут использовать API, такие как MediaControllerCompat
устаревшей библиотеки AndroidX или android.media.session.MediaController
платформы Android. Media3 полностью обратно совместим с устаревшей библиотекой и обеспечивает взаимодействие с API платформы Android.
Используйте контроллер уведомлений мультимедиа
Важно понимать, что эти устаревшие и платформенные контроллеры имеют одинаковое состояние, и видимость не может быть настроена контроллером (например, доступны PlaybackState.getActions()
и PlaybackState.getCustomActions()
). Вы можете использовать контроллер уведомлений о медиа-событиях для настройки состояния, заданного в медиа-сеансе платформы, для совместимости с этими устаревшими и платформенными контроллерами.
Например, приложение может предоставить реализацию MediaSession.Callback.onConnect()
для установки доступных команд и настроек кнопок мультимедиа специально для сеанса платформы следующим образом:
Котлин
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { if (session.isMediaNotificationController(controller)) { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build() 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 AcceptedResultBuilder(session) .setMediaButtonPreferences( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward)) ) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands with default button preferences for all other controllers. return AcceptedResultBuilder(session).build() }
Ява
@Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { if (session.isMediaNotificationController(controller)) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build(); 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 new AcceptedResultBuilder(session) .setMediaButtonPreferences( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward))) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands with default button preferences for all other controllers. return new AcceptedResultBuilder(session).build(); }
Разрешите Android Auto отправлять пользовательские команды
При использовании MediaLibraryService
и для поддержки Android Auto с помощью мобильного приложения контроллеру Android Auto требуются соответствующие доступные команды, в противном случае Media3 будет отклонять входящие пользовательские команды от этого контроллера:
Котлин
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { val sessionCommands = ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build() if (session.isMediaNotificationController(controller)) { // [...] See above. } else if (session.isAutoCompanionController(controller)) { // Available session commands to accept incoming custom commands from Auto. return AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands for all other controllers. return AcceptedResultBuilder(session).build() }
Ява
@Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build(); if (session.isMediaNotificationController(controller)) { // [...] See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return new AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands for all other controllers. return new AcceptedResultBuilder(session).build(); }
Демонстрационное приложение сеанса имеет автомобильный модуль , демонстрирующий поддержку автомобильной ОС, для которой требуется отдельный APK.