As sessões de mídia oferecem uma maneira universal de interagir com um player de áudio ou vídeo. Na Media3, o player padrão é a classe ExoPlayer
, que implementa
a interface Player
. A conexão da sessão de mídia ao player permite que um app
avise a reprodução de mídia externamente e receba comandos de
fontes externas.
Os comandos podem ser originados de botões físicos, como o botão de reprodução em um fone de ouvido ou controle remoto de TV. Eles também podem vir de apps clientes que têm um controlador de mídia, como ao instruir "pausar" ao Google Assistente. A sessão de mídia delega esses comandos ao player do app de música.
Quando escolher uma sessão de mídia
Ao implementar MediaSession
, você permite que os usuários controlem a reprodução:
- Pelos fones de ouvido. Muitas vezes, há botões ou interações de toque que o usuário pode realizar nos fones de ouvido para reproduzir ou pausar mídia ou acessar a próxima faixa ou a anterior.
- Falando com o Google Assistente. Um padrão comum é dizer "Ok Google, pausar" para pausar qualquer mídia que esteja sendo reproduzida no dispositivo.
- Pelo relógio Wear OS. Isso facilita o acesso aos controles de reprodução mais comuns durante a reprodução no smartphone.
- Pelos Controles de mídia. Esse carrossel mostra os controles para cada sessão de mídia em execução.
- Na TV Permite ações com botões físicos de reprodução, controle de reprodução da plataforma e gerenciamento de energia. Por exemplo, se a TV, o soundbar ou o receptor A/V forem desligados ou a entrada for trocada, a reprodução precisará ser interrompida no app.
- E qualquer outro processo externo que precise influenciar a reprodução.
Isso é ótimo para muitos casos de uso. Especificamente, considere
usar MediaSession
quando:
- Você está transmitindo conteúdo de vídeo mais longo, como filmes ou TV ao vivo.
- Você está transmitindo conteúdo de áudio de formato longo, como podcasts ou playlists de música.
- Você está criando um app de TV.
No entanto, nem todos os casos de uso se encaixam bem com o MediaSession
. Use
apenas o Player
nos seguintes casos:
- Você está exibindo conteúdo de formato curto, em que o engajamento e a interação do usuário são cruciais.
- Não há um único vídeo ativo, por exemplo, o usuário está rolando por uma lista e vários vídeos são mostrados na tela ao mesmo tempo.
- Você está assistindo um vídeo único de introdução ou explicação, que espera que o usuário assista ativamente.
- Seu conteúdo é sensível à privacidade, e você não quer que processos externos acessem os metadados de mídia (por exemplo, modo de navegação anônima em um navegador)
Caso seu caso de uso não se enquadre em nenhum dos listados acima, considere
se o app pode continuar a reprodução quando o usuário não estiver interagindo
ativamente com o conteúdo. Se a resposta for sim, talvez seja melhor escolher MediaSession
. Se a resposta for não, use Player
.
Criar uma sessão de mídia
Uma sessão de mídia convive com o player que a gerencia. Você pode criar uma
sessão de mídia com um Context
e um objeto Player
. Crie e
inicialize uma sessão de mídia quando necessário, como o método de ciclo de vida onStart()
ou
onResume()
do Activity
ou Fragment
, ou o método onCreate()
do Service
que é proprietário da sessão de mídia e do player associado.
Para criar uma sessão de mídia, inicialize um Player
e forneça-o para
MediaSession.Builder
desta forma:
Kotlin
val player = ExoPlayer.Builder(context).build() val mediaSession = MediaSession.Builder(context, player).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context).build(); MediaSession mediaSession = new MediaSession.Builder(context, player).build();
Processamento automático de estado
A biblioteca Media3 atualiza automaticamente a sessão de mídia usando o estado do player. Assim, você não precisa processar manualmente o mapeamento do jogador para a sessão.
Isso é uma pausa da abordagem legada em que era necessário criar e manter
uma PlaybackStateCompat
independentemente do próprio player, por exemplo, para
indicar erros.
ID de sessão exclusivo
Por padrão, MediaSession.Builder
cria uma sessão com uma string vazia como
ID da sessão. Isso é suficiente se um app pretende criar apenas uma única
instância de sessão, que é o caso mais comum.
Se um app quiser gerenciar várias instâncias de sessão ao mesmo tempo, ele
precisa garantir que o ID de cada sessão seja exclusivo. O ID da sessão pode
ser definido ao criar a sessão com MediaSession.Builder.setId(String id)
.
Se você vir um IllegalStateException
fazendo uma falha no app com a mensagem
de erro IllegalStateException: Session ID must be unique. ID=
, é
provável que uma sessão tenha sido criada inesperadamente antes de uma instância criada
anteriormente com o mesmo ID ser liberada. Para evitar que as sessões vazem por causa de um erro de programação, esses casos são detectados e notificados com o lançamento de uma exceção.
Conceder controle a outros clientes
A sessão de mídia é a chave para controlar a reprodução. Ela permite encaminhar comandos de fontes externas para o player que executa o trabalho de reprodução da mídia. Essas fontes podem ser botões físicos, como o botão de reprodução em um fone de ouvido ou controle remoto da TV, ou comandos indiretos, como para instruir "pausar" ao Google Assistente. Da mesma forma, você pode conceder acesso ao sistema Android para facilitar os controles de notificação e da tela de bloqueio ou a um relógio Wear OS para que você possa controlar a reprodução no mostrador do relógio. Clientes externos podem usar um controlador de mídia para emitir comandos de reprodução para seu app de música. Eles são recebidos pela sessão de mídia, que delega comandos ao player de mídia.
Quando um controle está prestes a se conectar à sua sessão de mídia, o
método onConnect()
é chamado. É possível usar a ControllerInfo
fornecida para decidir se vai aceitar
ou rejeitar
a solicitação. Veja um exemplo de como aceitar uma solicitação de conexão na seção Declarar comandos disponíveis.
Após a conexão, um controlador pode enviar comandos de reprodução para a sessão. Em seguida,
a sessão delega esses comandos ao jogador. Os comandos de reprodução e
playlist definidos na interface Player
são processados automaticamente pela
sessão.
Outros métodos de callback permitem processar, por exemplo, solicitações de
comandos de reprodução personalizados e
modificação na playlist.
Esses callbacks incluem um objeto ControllerInfo
para que você possa modificar
como responde a cada solicitação por controlador.
Modificar a playlist
Uma sessão de mídia pode modificar diretamente a playlist do player, conforme explicado no
guia do ExoPlayer para playlists (link em inglês).
Os controladores também poderão modificar a playlist se
COMMAND_SET_MEDIA_ITEM
ou COMMAND_CHANGE_MEDIA_ITEMS
estiver disponível para o controlador.
Ao adicionar novos itens à playlist, o player normalmente exige instâncias MediaItem
com um
URI definido
para torná-las reproduzíveis. Por padrão, os itens recém-adicionados são encaminhados automaticamente
para métodos do player, como player.addMediaItem
, se tiverem um URI definido.
Caso queira personalizar as instâncias de MediaItem
adicionadas ao player, substitua
onAddMediaItems()
.
Essa etapa é necessária quando você quer oferecer suporte a controladores que solicitam mídia
sem um URI definido. Em vez disso, o MediaItem
normalmente tem
um ou mais dos seguintes campos definidos para descrever a mídia solicitada:
MediaItem.id
: um ID genérico que identifica a mídia.MediaItem.RequestMetadata.mediaUri
: um URI de solicitação que pode usar um esquema personalizado e não necessariamente reproduzível diretamente pelo jogador.MediaItem.RequestMetadata.searchQuery
: uma consulta de pesquisa textual, por exemplo, do Google Assistente.MediaItem.MediaMetadata
: metadados estruturados, como "título" ou "artista".
Para ter mais opções de personalização para playlists completamente novas, você também pode
substituir
onSetMediaItems()
,
que permite definir o item inicial e a posição na playlist. Por exemplo,
é possível expandir um único item solicitado para uma playlist inteira e instruir o
player a começar no índice do item solicitado originalmente. Um
exemplo de implementação de onSetMediaItems()
com esse recurso pode ser encontrado no app de demonstração da sessão.
Gerenciar layout e comandos personalizados
As seções a seguir descrevem como anunciar um layout personalizado de botões de comando personalizados para apps clientes e autorizar os controladores a enviar os comandos personalizados.
Definir o layout personalizado da sessão
Para indicar aos apps clientes quais controles de reprodução você quer mostrar para o
usuário, defina o layout personalizado da sessão
ao criar MediaSession
no método onCreate()
do
serviço.
Kotlin
override fun onCreate() { super.onCreate() val likeButton = CommandButton.Builder() .setDisplayName("Like") .setIconResId(R.drawable.like_icon) .setSessionCommand(SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING)) .build() val favoriteButton = CommandButton.Builder() .setDisplayName("Save to favorites") .setIconResId(R.drawable.favorite_icon) .setSessionCommand(SessionCommand(SAVE_TO_FAVORITES, Bundle())) .build() session = MediaSession.Builder(this, player) .setCallback(CustomMediaSessionCallback()) .setCustomLayout(ImmutableList.of(likeButton, favoriteButton)) .build() }
Java
@Override public void onCreate() { super.onCreate(); CommandButton likeButton = new CommandButton.Builder() .setDisplayName("Like") .setIconResId(R.drawable.like_icon) .setSessionCommand(new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING)) .build(); CommandButton favoriteButton = new CommandButton.Builder() .setDisplayName("Save to favorites") .setIconResId(R.drawable.favorite_icon) .setSessionCommand(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); Player player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player) .setCallback(new CustomMediaSessionCallback()) .setCustomLayout(ImmutableList.of(likeButton, favoriteButton)) .build(); }
Declarar comandos de player e personalizados disponíveis
Os aplicativos de mídia podem definir comandos personalizados que, por exemplo, podem ser usados em
um layout personalizado. Por exemplo, você pode implementar botões que permitem que o
usuário salve um item de mídia em uma lista de itens favoritos. O MediaController
envia comandos personalizados e o MediaSession.Callback
os recebe.
É possível definir quais comandos de sessão personalizada estão disponíveis para um
MediaController
quando ele se conectar à sua sessão de mídia. Para isso,
modifique MediaSession.Callback.onConnect()
. Configure e retorne
o conjunto de comandos disponíveis ao aceitar uma solicitação de conexão de um
MediaController
no método de callback onConnect
:
Kotlin
private inner class CustomMediaSessionCallback: MediaSession.Callback { // Configure commands available to the controller in onConnect() override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): MediaSession.ConnectionResult { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY)) .build() return AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build() } }
Java
class CustomMediaSessionCallback implements MediaSession.Callback { // Configure commands available to the controller in onConnect() @Override public ConnectionResult onConnect( MediaSession session, ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle())) .build(); return new AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build(); } }
Para receber solicitações de comandos personalizados de um MediaController
, substitua o
método onCustomCommand()
no Callback
.
Kotlin
private inner class CustomMediaSessionCallback: MediaSession.Callback { ... override fun onCustomCommand( session: MediaSession, controller: MediaSession.ControllerInfo, customCommand: SessionCommand, args: Bundle ): ListenableFuture<SessionResult> { if (customCommand.customAction == SAVE_TO_FAVORITES) { // Do custom logic here saveToFavorites(session.player.currentMediaItem) return Futures.immediateFuture( SessionResult(SessionResult.RESULT_SUCCESS) ) } ... } }
Java
class CustomMediaSessionCallback implements MediaSession.Callback { ... @Override public ListenableFuture<SessionResult> onCustomCommand( MediaSession session, ControllerInfo controller, SessionCommand customCommand, Bundle args ) { if(customCommand.customAction.equals(SAVE_TO_FAVORITES)) { // Do custom logic here saveToFavorites(session.getPlayer().getCurrentMediaItem()); return Futures.immediateFuture( new SessionResult(SessionResult.RESULT_SUCCESS) ); } ... } }
É possível rastrear qual controlador de mídia está fazendo uma solicitação usando a
propriedade packageName
do objeto MediaSession.ControllerInfo
que é
transmitido aos métodos Callback
. Isso permite que você personalize o comportamento do seu app
em resposta a um determinado comando se ele for do sistema, do seu
próprio app ou de outros apps clientes.
Atualizar o layout personalizado após uma interação do usuário
Depois de processar um comando personalizado ou qualquer outra interação com o player, atualize o layout mostrado na interface do controle. Um exemplo típico
é um botão de ativação que muda o ícone depois de acionar a ação associada
a ele. Para atualizar o layout, você pode usar
MediaSession.setCustomLayout
:
Kotlin
val removeFromFavoritesButton = CommandButton.Builder() .setDisplayName("Remove from favorites") .setIconResId(R.drawable.favorite_remove_icon) .setSessionCommand(SessionCommand(REMOVE_FROM_FAVORITES, Bundle())) .build() mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton))
Java
CommandButton removeFromFavoritesButton = new CommandButton.Builder() .setDisplayName("Remove from favorites") .setIconResId(R.drawable.favorite_remove_icon) .setSessionCommand(new SessionCommand(REMOVE_FROM_FAVORITES, new Bundle())) .build(); mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton));
Personalizar o comportamento do comando de reprodução
Para personalizar o comportamento de um comando definido na interface Player
, como
play()
ou seekToNext()
, envolva Player
em um ForwardingPlayer
.
Kotlin
val player = ExoPlayer.Builder(context).build() val forwardingPlayer = object : ForwardingPlayer(player) { override fun play() { // Add custom logic super.play() } override fun setPlayWhenReady(playWhenReady: Boolean) { // Add custom logic super.setPlayWhenReady(playWhenReady) } } val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context).build(); ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player) { @Override public void play() { // Add custom logic super.play(); } @Override public void setPlayWhenReady(boolean playWhenReady) { // Add custom logic super.setPlayWhenReady(playWhenReady); } }; MediaSession mediaSession = new MediaSession.Builder(context, forwardingPlayer).build();
Para saber mais sobre ForwardingPlayer
, consulte o guia do ExoPlayer sobre
Personalização (link em inglês).
Identificar o controlador solicitante de um comando de player
Quando uma chamada para um método Player
é originada por um MediaController
, você pode
identificar a fonte de origem com MediaSession.controllerForCurrentRequest
e adquirir o ControllerInfo
para a solicitação atual:
Kotlin
class CallerAwareForwardingPlayer(player: Player) : ForwardingPlayer(player) { override fun seekToNext() { Log.d( "caller", "seekToNext called from package ${session.controllerForCurrentRequest?.packageName}" ) super.seekToNext() } }
Java
public class CallerAwareForwardingPlayer extends ForwardingPlayer { public CallerAwareForwardingPlayer(Player player) { super(player); } @Override public void seekToNext() { Log.d( "caller", "seekToNext called from package: " + session.getControllerForCurrentRequest().getPackageName()); super.seekToNext(); } }
Responder aos botões de mídia
Os botões de mídia são botões de hardware encontrados em dispositivos Android e outros dispositivos
periféricos, como o botão de reproduzir/pausar em um fone de ouvido Bluetooth. A Media3 processa
os eventos do botão de mídia quando eles chegam à sessão e chama o
método Player
apropriado no player de sessão.
Um app pode substituir o comportamento padrão substituindo
MediaSession.Callback.onMediaButtonEvent(Intent)
. Nesse caso, o app
pode/precisa processar todas as especificações da API por conta própria.