Muitas vezes, é desejável reproduzir mídia enquanto um app não está em primeiro plano. Por exemplo, um player de música geralmente continua tocando música quando o usuário bloqueou o dispositivo ou está usando outro app. A biblioteca Media3 oferece uma série de interfaces que permitem oferecer suporte à reprodução em segundo plano.
Usar um MediaSessionService
Para ativar a reprodução em segundo plano, inclua Player
e
MediaSession
em um serviço separado.
Isso permite que o dispositivo continue oferecendo mídia mesmo quando o app não está em
primeiro plano.

MediaSessionService
permite que a sessão de mídia
seja executada separadamente da atividade do appAo hospedar um jogador em um serviço, use um MediaSessionService
.
Para fazer isso, crie uma classe que estenda MediaSessionService
e crie sua
sessão de mídia nela.
O uso de MediaSessionService
permite que clientes externos, como o Google
Assistente, controles de mídia do sistema, botões de mídia em dispositivos periféricos ou
dispositivos complementares, como o Wear OS, descubram seu serviço, se conectem a ele e
controlem a reprodução sem acessar a atividade da IU do app. Na verdade,
pode haver vários apps clientes conectados ao mesmo MediaSessionService
ao
mesmo tempo, cada um com o próprio MediaController
.
Implementar o ciclo de vida do serviço
É necessário implementar dois métodos de ciclo de vida do serviço:
onCreate()
é chamado quando o primeiro controlador está prestes a se conectar e o serviço é instanciado e iniciado. É o melhor lugar para criarPlayer
eMediaSession
.onDestroy()
é chamado quando o serviço está sendo interrompido. Todos os recursos, incluindo o player e a sessão, precisam ser liberados.
Você pode substituir onTaskRemoved(Intent)
para personalizar o que acontece
quando o usuário dispensa o app das tarefas recentes. Por padrão, o serviço
é mantido em execução se a reprodução estiver em andamento e será interrompido caso contrário.
Kotlin
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() } }
Java
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(); } }
Como alternativa para manter a reprodução em segundo plano, você pode parar o serviço em qualquer caso quando o usuário dispensar o app:
Kotlin
override fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
Para qualquer outra implementação manual de onTaskRemoved
, use
isPlaybackOngoing()
para verificar se a reprodução está em andamento e se o
serviço em primeiro plano foi iniciado.
Conceder acesso à sessão de mídia
Modifique o método onGetSession()
para dar a outros clientes acesso à sua sessão
de mídia criada quando o serviço foi criado.
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // [...] lifecycle methods omitted override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
public class PlaybackService extends MediaSessionService { private MediaSession mediaSession = null; // [...] lifecycle methods omitted @Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { return mediaSession; } }
Declarar o serviço no manifesto
Um app precisa das permissões FOREGROUND_SERVICE
e FOREGROUND_SERVICE_MEDIA_PLAYBACK
para executar um serviço em primeiro plano de reprodução:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
Você também precisa declarar a classe Service
no manifesto com um filtro de intent
de MediaSessionService
e um foregroundServiceType
que inclua
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>
Controlar a reprodução usando uma MediaController
Na atividade ou no fragmento que contém a interface do player, é possível estabelecer um link
entre a interface e a sessão de mídia usando um MediaController
. A interface usa
o controlador de mídia para enviar comandos da interface ao player na
sessão. Consulte o guia
Criar um MediaController
para saber como criar e usar um MediaController
.
Processar comandos MediaController
O MediaSession
recebe comandos do controlador pelo
MediaSession.Callback
. A inicialização de uma MediaSession
cria uma implementação
padrão de MediaSession.Callback
que processa automaticamente todos
os comandos que uma MediaController
envia para o player.
Notificação
Um MediaSessionService
cria automaticamente um MediaNotification
para você, que
funciona na maioria dos casos. Por padrão, a notificação publicada é uma
notificação MediaStyle
que fica atualizada com as informações
mais recentes da sua sessão de mídia e mostra os controles de reprodução. O
MediaNotification
está ciente da sua sessão e pode ser usado para controlar a reprodução
de outros apps conectados à mesma sessão.
Por exemplo, um app de streaming de música que usa um MediaSessionService
cria um
MediaNotification
que mostra o título, o artista e a capa do álbum do
item de mídia atual que está sendo reproduzido, além dos controles de reprodução com base na
configuração da MediaSession
.
Os metadados necessários podem ser fornecidos na mídia ou declarados como parte do item de mídia, como no snippet abaixo:
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();
Ciclo de vida da notificação
A notificação é criada assim que o Player
tem instâncias de MediaItem
na playlist.
Todas as atualizações de notificação acontecem automaticamente com base no estado Player
e
MediaSession
.
A notificação não pode ser removida enquanto o serviço em primeiro plano está em execução. Para
remover a notificação imediatamente, chame Player.release()
ou limpe
a playlist usando Player.clearMediaItems()
.
Se o player for pausado, interrompido ou falhar por mais de 10 minutos sem outras interações do usuário, o serviço será automaticamente transferido para fora do estado de serviço em primeiro plano para que possa ser destruído pelo sistema. É possível implementar a retomada da reprodução para permitir que um usuário reinicie o ciclo de vida do serviço e retome a reprodução mais tarde.
Personalização de notificações
Os metadados sobre o item em reprodução podem ser personalizados modificando
o MediaItem.MediaMetadata
. Se você quiser atualizar os metadados de um item
existente, use Player.replaceMediaItem
para atualizar os metadados sem
interromper a reprodução.
Também é possível personalizar alguns dos botões mostrados na notificação definindo preferências personalizadas para os controles de mídia do Android. Saiba como personalizar os controles de mídia do Android.
Para personalizar ainda mais a notificação, crie um
MediaNotification.Provider
com DefaultMediaNotificationProvider.Builder
ou criando uma implementação personalizada da interface do provedor. Adicione o
provedor ao MediaSessionService
com
setMediaNotificationProvider
.
Retomada da reprodução
Depois que o MediaSessionService
é encerrado e mesmo depois que o dispositivo
é reinicializado, é possível oferecer a retomada da reprodução para permitir que os usuários
reiniciem o serviço e retomem a reprodução de onde pararam. Por padrão,
a retomada da reprodução está desativada. Isso significa que o usuário não pode retomar a reprodução
quando o serviço não está em execução. Para ativar esse recurso, você precisa declarar
um receptor de botão de mídia e implementar o método onPlaybackResumption
.
Declarar o receptor do botão de mídia Media3
Comece declarando o MediaButtonReceiver
no manifesto:
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
Implementar o callback de retomada de reprodução
Quando a retomada da reprodução é solicitada por um dispositivo Bluetooth ou pelo
recurso de retomada da interface do sistema Android,
o método de callback onPlaybackResumption()
é chamado.
Kotlin
override fun onPlaybackResumption( mediaSession: MediaSession, controller: ControllerInfo ): ListenableFuture<MediaItemsWithStartPosition> { val settable = SettableFuture.create<MediaItemsWithStartPosition>() scope.launch { // Your app is responsible for storing the playlist and the start position // to use here val resumptionPlaylist = restorePlaylist() settable.set(resumptionPlaylist) } return settable }
Java
@Override public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption( MediaSession mediaSession, ControllerInfo controller ) { SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create(); settableFuture.addListener(() -> { // Your app is responsible for storing the playlist and the start position // to use here MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist(); settableFuture.set(resumptionPlaylist); }, MoreExecutors.directExecutor()); return settableFuture; }
Se você armazenou outros parâmetros, como velocidade de reprodução, modo de repetição ou
modo de ordem aleatória, onPlaybackResumption()
é um bom lugar para configurar o player
com esses parâmetros antes que o Media3 prepare o player e inicie a reprodução quando
o callback for concluído.
Configuração avançada do controlador e compatibilidade com versões anteriores
Um cenário comum é usar um MediaController
na interface do app para controlar
a reprodução e mostrar a playlist. Ao mesmo tempo, a sessão é exposta
a clientes externos, como controles de mídia do Android e o Google Assistente em dispositivos móveis ou TVs,
Wear OS para relógios e Android Auto em carros. O app de demonstração de sessão do Media3
é um exemplo de app que implementa esse cenário.
Esses clientes externos podem usar APIs como MediaControllerCompat
da biblioteca
AndroidX legada ou android.media.session.MediaController
da plataforma
Android. O Media3 é totalmente compatível com a biblioteca legada e
oferece interoperabilidade com a API da plataforma Android.
Usar o controle de notificação de mídia
É importante entender que esses controladores legados e de plataforma compartilham
o mesmo estado, e a visibilidade não pode ser personalizada por controlador (por exemplo, o
PlaybackState.getActions()
e o PlaybackState.getCustomActions()
disponíveis).
Você pode usar o controlador de notificação de mídia para
configurar o estado definido na sessão de mídia da plataforma para compatibilidade com esses
controladores legados e de plataforma.
Por exemplo, um app pode fornecer uma implementação de
MediaSession.Callback.onConnect()
para definir comandos disponíveis e
preferências do botão de mídia especificamente para a sessão da plataforma da seguinte maneira:
Kotlin
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() }
Java
@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(); }
Autorizar o Android Auto a enviar comandos personalizados
Ao usar um MediaLibraryService
e oferecer suporte ao Android Auto com o app para dispositivos móveis, o controlador do Android Auto
precisa de comandos disponíveis adequados. Caso contrário, o Media3 negará
os comandos personalizados recebidos desse controlador:
Kotlin
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() }
Java
@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(); }
O app de demonstração da sessão tem um módulo automotivo, que demonstra suporte ao Automotive OS que requer um APK separado.