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
. Conectar a sessão de mídia ao player permite que um app
anuncia a reprodução de mídia externamente e receba comandos de
fontes externas.
Os comandos podem se originar de botões físicos, como o botão "Assistir" em um fone de ouvido ou controle remoto de TV. Eles também podem ser de apps clientes que têm um controlador de mídia, como ao instruir o Google Assistente a "pausar". 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 tocar ou pausar mídia ou ir para a próxima faixa ou a anterior.
- Falando com o Google Assistente. Um padrão comum é dizer "Ok Google, pause" para pausar qualquer mídia em reprodução no dispositivo.
- No relógio Wear OS. Isso facilita o acesso aos controles de mídia mais comuns durante a reprodução no smartphone.
- Com os 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, a soundbar ou o receptor de A/V for desligado ou se a entrada for alterada, a reprodução será interrompida no app.
- E quaisquer outros processos externos que precisem influenciar a reprodução.
Isso é ótimo para muitos casos de uso. Especificamente, considere
o uso de 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ê estiver criando um app de TV.
No entanto, nem todos os casos de uso são adequados para MediaSession
. Use apenas
Player
nos seguintes casos:
- Você está mostrando conteúdo em formato curto, em que o engajamento e a interação do usuário são cruciais.
- Não há um único vídeo ativo, como o usuário está rolando por uma lista e vários vídeos são mostrados na tela ao mesmo tempo.
- Você está reproduzindo um vídeo único de introdução ou explicação e espera que o usuário assista ativamente.
- Seu conteúdo confide a privacidade, e você não quer que processos externos acessem os metadados de mídia (por exemplo, no modo de navegação anônima em um navegador).
Se o caso de uso não se encaixar 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, escolha MediaSession
. Se a resposta for não, é recomendável usar
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 objeto Context
e um Player
. Crie e
inicialize uma sessão de mídia quando ela for necessária. Por exemplo, o método de ciclo de vida onStart()
ou
onResume()
do Activity
ou Fragment
ou o método onCreate()
do Service
que tem a sessão de mídia e o player associado.
Para criar uma sessão de mídia, inicialize uma Player
e forneça-a 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. Dessa forma, não é necessário processar manualmente o mapeamento de jogador para sessão.
Essa é uma pausa da abordagem legada, em que era necessário criar e manter
um PlaybackStateCompat
independente do próprio player, por exemplo, para
indicar erros.
ID exclusivo da sessão
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
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
precisará 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ê notar uma falha IllegalStateException
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 o vazamento de sessões por um
erro de programação, esses casos são detectados e notificados com a
geração de uma exceção.
Conceder controle a outros clientes
A sessão de mídia é essencial para controlar a reprodução. Ele permite encaminhar comandos de fontes externas para o player que executa a mídia. Essas origens podem ser botões físicos, como o botão "Assistir" em um fone de ouvido ou controle remoto da TV, ou comandos indiretos, como instruir o Google Assistente a "pausar" . Da mesma forma, você pode conceder acesso ao sistema Android para facilitar os controles de notificação e 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, por fim, delega comandos ao player de mídia.
Quando um controle está prestes a se conectar à sessão de mídia, o método
onConnect()
é chamado. É possível usar o ControllerInfo
fornecido para decidir se quer 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 para o 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 da playlist.
Da mesma forma, esses callbacks incluem um objeto ControllerInfo
para que você possa modificar
a forma de responder a cada solicitação em cada 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.
Os controladores também poderão modificar a playlist se
COMMAND_SET_MEDIA_ITEM
ou COMMAND_CHANGE_MEDIA_ITEMS
estiverem disponíveis.
Ao adicionar novos itens à playlist, o player normalmente exige instâncias de MediaItem
com um
URI definido
para que possam ser tocadas. Por padrão, os itens recém-adicionados são encaminhados automaticamente
para os métodos do jogador, como player.addMediaItem
, se eles tiverem um URI definido.
Se você quiser personalizar as instâncias de MediaItem
adicionadas ao jogador, é possível
substituir
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 reproduzido 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,
você pode expandir um único item solicitado para uma playlist inteira e instruir o
player a iniciar 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 comandos 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 ao
usuário, defina o layout personalizado da sessão
ao criar a 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 personalizados e de player disponíveis
Os aplicativos de mídia podem definir comandos personalizados que, por exemplo, podem ser usados em
um layout personalizado. Por exemplo, implemente botões que permitam 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.
Você pode definir quais comandos de sessão personalizada estão disponíveis para um
MediaController
quando ele se conecta à sessão de mídia. Para fazer isso,
substitua 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 comando personalizado de um MediaController
, modifique 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 é
transmitida aos métodos Callback
. Isso permite que você personalize o comportamento
do app em resposta a um determinado comando, caso ele seja originado 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, recomendamos
atualizar o layout mostrado na interface do controle. Um exemplo típico
é um botão ativar que muda de í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()
, una o 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 em
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 origem 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
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 para você quando eles chegam à sessão e chama o
método Player
adequado no player de sessão.
Um app pode substituir o comportamento padrão substituindo
MediaSession.Callback.onMediaButtonEvent(Intent)
. Nesse caso, o app
pode ou precisa processar todas as especificidades da API por conta própria.