Muitas vezes, é recomendável reproduzir mídia enquanto um app não está em primeiro plano. Para exemplo, um player de música geralmente continua reproduzindo música quando o usuário está bloqueado dispositivo ou está usando outro app. A biblioteca Media3 oferece uma série de que permitem a reprodução em segundo plano.
Usar uma 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 exibindo mídia mesmo que o app não esteja no
primeiro plano.
Ao hospedar um jogador em um serviço, use um MediaSessionService
.
Para fazer isso, crie uma classe que estenda MediaSessionService
` e crie o
uma sessão de mídia nele.
O uso do MediaSessionService
possibilita que clientes externos como o Google
Assistente, controles de mídia do sistema ou dispositivos complementares, como o Wear OS, para descobrir seu conteúdo
seu serviço, conectar-se a ele e controlar a reprodução, tudo sem acessar seu
para a atividade da interface do usuário. Na verdade, pode haver vários aplicativos clientes conectados
ao mesmo MediaSessionService
ao mesmo tempo, cada app com um
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 controle 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á na primeiro plano e precisa ser interrompida. 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 de manter a reprodução contínua em segundo plano, o app pode interromper o serviço em qualquer caso quando o usuário dispensar o aplicativo:
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 permitir que outros clientes acessem sua mídia
sessão criada com o serviço.
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 método
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" />
Também é necessário 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>
Você deve definir um
foregroundServiceType
que inclui mediaPlayback
quando o app está sendo executado em um dispositivo com Android
10 (nível 29 da API) e mais recentes.
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 a
Criar um MediaController
guia para conferir detalhes sobre como criar e usar um MediaController
.
Processar comandos da interface
O MediaSession
recebe comandos do controle usando o
MediaSession.Callback
. A inicialização de um MediaSession
cria um
implementação de MediaSession.Callback
que lida automaticamente com todos
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
sempre atualizado com as informações mais recentes
da sessão de mídia e exibe os controles de mídia. O MediaNotification
reconhece sua sessão e pode ser usado para controlar a reprodução para outros apps
que estão 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 neste snippet:
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 a mídia do Android controles de segurança.
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 de 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. Mídia3 processa as entradas dos botões de mídia para você quando o serviço está em execução.
Declarar o receptor do botão de mídia Media3
A Media3 inclui uma API para permitir que os usuários retomem
a reprodução após o encerramento de um aplicativo e mesmo após o término do
reiniciada. Por padrão, a retomada da reprodução fica 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 controle 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 controlador de notificações de mídia
É importante entender que esses controladores legados ou de framework leem o
dos mesmos valores do framework PlaybackState.getActions()
e
PlaybackState.getCustomActions()
. 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 do
MediaSession.Callback.onConnect()
para definir os comandos disponíveis e
layout personalizado especificamente para a sessão de 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 controle do Android Auto
requer 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 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 módulo automotivo, que demonstra o suporte para o Automotive OS que exige um APK separado.