Muitas vezes, é recomendá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 a reprodução em segundo plano.
Usar um MediaSessionService
Para ativar a reprodução em segundo plano, você precisa conter os atributos Player
e
MediaSession
em um Serviço separado.
Isso permite que o dispositivo continue a veicular mídia mesmo quando o app não está em
primeiro plano.
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 nela.
O uso de MediaSessionService
permite que clientes externos, como o Google
Assistente, controles de mídia do sistema ou dispositivos complementares, como o Wear OS, descubram
seu serviço, se conectem a ele e controlem a reprodução, tudo sem acessar a
atividade da IU 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
Você precisa implementar três 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
.- O
onTaskRemoved(Intent)
é chamado quando o usuário dispensa o app do tarefas recentes. Se a reprodução estiver em andamento, o app poderá optar por manter o serviço em primeiro plano. Se o player estiver pausado, o serviço não estará em primeiro plano e precisará ser interrompido. onDestroy()
é chamado quando o serviço está sendo interrompido. Todos os recursos incluindo player e sessão, precisam ser liberados.
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() } // The user dismissed the app from the recent tasks override fun onTaskRemoved(rootIntent: Intent?) { val player = mediaSession?.player!! if (!player.playWhenReady || player.mediaItemCount == 0 || player.playbackState == Player.STATE_ENDED) { // Stop the service if not playing, continue playing in the background // otherwise. stopSelf() } } // 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(); } // The user dismissed the app from the recent tasks @Override public void onTaskRemoved(@Nullable Intent rootIntent) { Player player = mediaSession.getPlayer(); if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0 || player.getPlaybackState() == Player.STATE_ENDED) { // Stop the service if not playing, continue playing in the background // otherwise. stopSelf(); } } // 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, um app pode parar o serviço em qualquer caso quando o usuário dispensar o app:
Kotlin
override fun onTaskRemoved(rootIntent: Intent?) { val player = mediaSession.player if (player.playWhenReady) { // Make sure the service is not in foreground. player.pause() } stopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { Player player = mediaSession.getPlayer(); if (player.getPlayWhenReady()) { // Make sure the service is not in foreground. player.pause(); } stopSelf(); }
Conceder 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 de permissão para executar um serviço em primeiro plano. Adicione o
permissão FOREGROUND_SERVICE
para o manifesto e, se você direcionar ao nível 34 da API e
acima também FOREGROUND_SERVICE_MEDIA_PLAYBACK
:
<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
.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
É necessário definir uma
foregroundServiceType
que inclua mediaPlayback
quando o app estiver em execução em um dispositivo com o Android
10 (nível 29 da API) ou mais recente.
Controlar a reprodução usando um MediaController
Na Atividade ou Fragmento que contém a interface do seu player, você pode estabelecer um link
entre a interface e a sessão de mídia usando um MediaController
. Sua interface usa
o controlador de mídia para enviar comandos da interface de usuário para o player dentro do
sessão. Consulte o guia
Criar um MediaController
para saber como criar e usar um MediaController
.
Processar comandos da interface
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
criaria uma
MediaNotification
que mostra o título, o artista e a capa do álbum do
item de mídia atual sendo reproduzido junto com os controles de mídia com base no seu
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();
Os apps podem personalizar os botões de comando dos controles de mídia do Android. Saiba mais sobre como personalizar os controles de mídia do Android.
Personalização de notificações
Para personalizar a notificação, crie uma
MediaNotification.Provider
com DefaultMediaNotificationProvider.Builder
ou criando uma implementação personalizada da interface do provedor. Adicione seu
provedor ao seu MediaSessionService
com
setMediaNotificationProvider
.
Retomada da reprodução
Botões de mídia são botões de hardware encontrados em dispositivos Android e outros periféricos dispositivos, como o botão de reproduzir ou pausar em um fone de ouvido Bluetooth. O Media3 processa as entradas de botão de mídia para você quando o serviço está em execução.
Declarar o receptor do botão de mídia Media3
O Media3 inclui uma API para permitir que os usuários retomem
a reprodução depois que um app é encerrado e mesmo depois que o dispositivo é
reiniciado. Por padrão, a retomada da reprodução está desativada. Isso significa que o usuário
não poderá retomar a reprodução quando o serviço não estiver em execução. Para ativar, comece
declarando 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,
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, o onPlaybackResumption()
é um bom lugar para configurar o player
com esses parâmetros antes que a Media3 prepare o player e inicie a reprodução quando
o retorno de chamada é 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
reproduzir e exibir a lista de reprodução. Ao mesmo tempo, a sessão é exposta
para clientes externos, como os 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 (link em inglês) Media3 da sessão.
é um exemplo de app que implementa esse tipo de cenário.
Esses clientes externos podem usar APIs como a MediaControllerCompat
das
Biblioteca AndroidX ou android.media.session.MediaController
do Android
de análise de dados em nuvem. A Media3 é totalmente compatível com versões anteriores da biblioteca legada e
Fornece interoperabilidade com a API de framework do Android.
Usar o controle de notificação de mídia
É importante entender que esses controladores legados ou de framework leem os
mesmos valores de PlaybackState.getActions()
e
PlaybackState.getCustomActions()
do framework. Para determinar as ações personalizadas
a sessão do framework, um app poderá usar o controlador de notificação de mídia
e definir os comandos disponíveis e o layout personalizado. O serviço conecta a mídia
à sua sessão, e a sessão usará o
ConnectionResult
retornado pelo onConnect()
do callback para configurar
e ações personalizadas da sessão do framework.
Em um cenário exclusivo para dispositivos móveis, um app pode fornecer uma implementação de
MediaSession.Callback.onConnect()
para definir comandos disponíveis e
layout personalizado especificamente para a sessão do framework 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 layout and available commands to configure the legacy/framework session. return AcceptedResultBuilder(session) .setCustomLayout( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward)) ) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands with default custom layout 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 layout and available commands to configure the legacy/framework session. return new AcceptedResultBuilder(session) .setCustomLayout( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward))) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands without default custom layout 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 with default custom layout 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 without default custom layout 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.