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 quando o usuário bloqueia o dispositivo ou usa 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, coloque o Player
e o
MediaSession
em um Service separado.
Isso permite que o dispositivo continue veiculando 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 app.Ao 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 dentro dela.
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 interface do app. Na verdade,
pode haver vários apps clientes conectados ao mesmo MediaSessionService
ao
mesmo tempo, cada app 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 player e 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
continua em execução se a reprodução estiver em andamento e é 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 a 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 é considerada contínua e se o
serviço em primeiro plano foi iniciado.
Fornecer acesso à sessão de mídia
Substitua 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" />
Também é necessário 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 um 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 detalhes sobre como criar e usar um MediaController
.
Processar comandos MediaController
O MediaSession
recebe comandos do controlador pelo
MediaSession.Callback
. A inicialização de um MediaSession
cria uma implementação padrão de MediaSession.Callback
que processa automaticamente todos os comandos que um MediaController
envia ao player.
Notificação
Um MediaSessionService
cria automaticamente um MediaNotification
para você que
deve funcionar 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 controles de reprodução. O
MediaNotification
reconhece sua sessão e pode ser usado para controlar a reprodução
de qualquer outro app conectado à mesma sessão.
Por exemplo, um app de streaming de música que usa um MediaSessionService
criaria um
MediaNotification
que mostra o título, o artista e a arte do álbum do
item de mídia atual sendo reproduzido junto com os controles de reprodução com base na sua
configuração MediaSession
.
Os metadados obrigatórios podem ser fornecidos na mídia ou declarados como parte do item de mídia, como no snippet a seguir:
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 das notificações
A notificação é criada assim que o Player
tem MediaItem
instâncias 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 estiver 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 mais interações do usuário, o serviço será automaticamente transferido do estado de serviço em primeiro plano para que possa ser destruído pelo sistema. Você pode 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 em um momento posterior.
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.
Você também pode personalizar alguns dos botões mostrados na notificação definindo preferências personalizadas de botões de mídia 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 seu provedor ao MediaSessionService
com setMediaNotificationProvider
.
Retomada da reprodução
Depois que o MediaSessionService
for encerrado e mesmo após a reinicialização do dispositivo, é possível oferecer a retomada da reprodução para que os usuários reiniciem o serviço e retomem a reprodução de onde pararam. Por padrão, a retomada da reprodução fica 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, declare
um receptor de botão de mídia e implemente o método onPlaybackResumption
.
Declarar o receptor do botão de mídia do 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, metadata (like title // and artwork) of the current item 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, 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; }
Se você armazenou outros parâmetros, como velocidade de reprodução, modo de repetição ou
modo aleatório, 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.
Esse método é chamado durante a inicialização para criar a notificação de retomada da interface do sistema Android
após uma reinicialização do dispositivo. Para uma notificação avançada, é recomendável preencher campos MediaMetadata
, como title
e artworkData
ou artworkUri
do item atual, com valores disponíveis localmente, já que o acesso à rede ainda pode não estar disponível. Também é possível adicionar
MediaConstants.EXTRAS_KEY_COMPLETION_STATUS
e
MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE
ao MediaMetadata.extras
para indicar a posição de reprodução da retomada.
Configuração avançada de 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 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. A Media3 é totalmente compatível com versões anteriores da biblioteca legada e oferece interoperabilidade com a API da plataforma Android.
Usar o controlador de notificações 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, os PlaybackState.getActions()
e PlaybackState.getCustomActions()
disponíveis). Você pode usar o controlador de notificações de mídia para configurar o conjunto de estados 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 de botões 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 para oferecer suporte ao Android Auto com o app para dispositivos móveis, o controlador do Android Auto
precisa dos comandos disponíveis adequados. Caso contrário, a Media3 negaria
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 de sessão tem um módulo automotivo, que demonstra a compatibilidade com o SO do Android Automotive, que exige um APK separado.