Como responder a 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, por exemplo, o botão de pausa/reprodução em um fone de ouvido Bluetooth. Quando um usuário pressiona um botão de mídia, o Android gera um KeyEvent, que contém um código de tecla que identifica o botão. Os códigos de tecla para KeyEvents de mídia são constantes que começam com KEYCODE_MEDIA (por exemplo, KEYCODE_MEDIA_PLAY).

Os apps precisam conseguir processar eventos de botão de mídia em três casos, nesta ordem de prioridade:

  • quando a atividade da IU do app estiver visível;
  • quando a atividade da IU estiver oculta, e a sessão de mídia do app estiver ativa;
  • quando a atividade da IU estiver oculta, e a sessão de mídia do app estiver inativa e precisar ser reiniciada.

Como processar botões de mídia em uma atividade em primeiro plano

A atividade em primeiro plano recebe o evento de tecla do botão de mídia no método onKeyDown(). Dependendo da versão em execução do Android, o sistema roteia o evento para um controlador de mídia de duas maneiras:

  • Se você estiver executando o Android 5.0 (nível 21 da API) ou mais recente, chame FLAG_HANDLES_MEDIA_BUTTONS MediaBrowserCompat.ConnectionCallback.onConnected. Isso vai chamar automaticamente o dispatchMediaButtonEvent() do controlador de mídia, que converte o código de tecla em um callback de sessão de mídia.
  • Em versões anteriores ao Android 5.0 (API de nível 21), é necessário modificar onKeyDown() para processar o evento por conta própria. Consulte Como processar botões de mídia em uma sessão de mídia ativa para ver mais detalhes. O snippet de código a seguir mostra como interceptar o código da chave e chamar dispatchMediaButtonEvent(). Lembre-se de retornar true para indicar que o evento foi processado:

    Kotlin

        fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                return super.onKeyDown(keyCode, event)
            }
            when (keyCode) {
                KeyEvent.KEYCODE_MEDIA_PLAY -> {
                    yourMediaController.dispatchMediaButtonEvent(event)
                    return true
                }
            }
            return super.onKeyDown(keyCode, event)
        }
        

    Java

        @Override
        boolean onKeyDown(int keyCode, KeyEvent event) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                  return super.onKeyDown(keyCode, event);
                }
                switch (keyCode) {
                  case KeyEvent.KEYCODE_MEDIA_PLAY:
                          yourMediaController.dispatchMediaButtonEvent(event);
                          return true;
                }
                return super.onKeyDown(keyCode, event);
        }
        

Como encontrar uma sessão de mídia

Se a atividade em primeiro plano não processar o evento, o Android vai tentar encontrar uma sessão de mídia que faça isso. Novamente, dependendo da versão em execução do Android, há duas maneiras de pesquisar uma sessão de mídia:

  • Se você estiver executando o Android 8.0 (nível 26 da API) ou versões mais recentes, o sistema vai tentar encontrar o último app com uma MediaSession que reproduziu áudio localmente. Se a sessão ainda estiver ativa, o Android enviará o evento diretamente para ela. Caso contrário, se a sessão não estiver ativa e tiver um receptor de botão de mídia, o Android vai enviar o evento ao receptor, que vai reiniciar a sessão para receber o evento. Consulte Como usar botões de mídia para reiniciar uma sessão de mídia inativa para ver detalhes. Se a sessão não tiver um receptor de botão de mídia, o sistema vai descartar o evento e nada acontecerá. A lógica é mostrada no diagrama a seguir:

  • Em versões anteriores ao Android 8.0 (API de nível 26), o sistema tenta enviar o evento para uma sessão de mídia ativa. Se houver várias sessões de mídia ativas, o Android vai tentar escolher uma sessão que esteja se preparando para reproduzir (armazenando em buffer/conectando), em reprodução ou pausada, em vez de uma que foi interrompida. Consulte Como processar botões de mídia em uma sessão de mídia ativa para saber mais. Se não houver uma sessão ativa, o Android vai tentar enviar o evento para a sessão ativa mais recente. Consulte Como usar botões de mídia para reiniciar uma sessão de mídia inativa para ver detalhes. A lógica é mostrada no diagrama a seguir.

Como processar botões de mídia em uma sessão de mídia ativa

No Android 5.0 (nível 21 da API) e versões mais recentes, o Android envia automaticamente eventos do botão de mídia para a sessão de mídia ativa chamando onMediaButtonEvent(). Por padrão, esse chamado converte o KeyEvent no método de callback de sessão de mídia apropriado que corresponde ao código de tecla.

Em versões anteriores, o Android processa eventos do botão de mídia transmitindo uma intent com a ação ACTION_MEDIA_BUTTON. O app precisa registrar um BroadcastReceiver para interceptar essas intents. A classe MediaButtonReceiver foi criada especificamente para essa finalidade. É uma classe de conveniência na biblioteca media-compat do Android que processa ACTION_MEDIA_BUTTON e converte as intents recebidas nas chamadas de método MediaSessionCompat.Callback apropriadas.

Um MediaButtonReceiver é um BroadcastReceiver de curta duração. Ele encaminha as intents recebidas para o serviço que está gerenciando sua sessão de mídia. Se você quiser usar botões de mídia em sistemas anteriores ao Android 5.0, inclua o MediaButtonReceiver no manifesto com um filtro de intent MEDIA_BUTTON:

<receiver android:name="android.support.v4.media.session.MediaButtonReceiver" >
   <intent-filter>
     <action android:name="android.intent.action.MEDIA_BUTTON" />
   </intent-filter>
 </receiver>

O BroadcastReceiver encaminha o intent para o serviço. Para analisar a intent e gerar o callback para sua sessão de mídia, inclua o método MediaButtonReceiver.handleIntent() no onStartCommand() do serviço. Isso converte o código de tecla em um método apropriado de callback de sessão.

Kotlin

private val mediaSessionCompat: MediaSessionCompat = ...

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    MediaButtonReceiver.handleIntent(mediaSessionCompat, intent)
    return super.onStartCommand(intent, flags, startId)
}

Java

private MediaSessionCompat mediaSessionCompat = ...;

 public int onStartCommand(Intent intent, int flags, int startId) {
   MediaButtonReceiver.handleIntent(mediaSessionCompat, intent);
   return super.onStartCommand(intent, flags, startId);
 }

Como usar botões de mídia para reiniciar uma sessão de mídia inativa

Se o Android puder identificar a última sessão de mídia ativa, ele tentará reiniciar a sessão enviando um Intent ACTION_MEDIA_BUTTON para um componente registrado pelo manifesto (como um serviço ou BroadcastReceiver).

Isso permite que o app reinicie a reprodução enquanto a IU não estiver visível, como é o caso da maioria dos apps de áudio.

Esse comportamento é ativado automaticamente quando você usa MediaSessionCompat. Se você usar a MediaSession do framework do Android ou a Biblioteca de Suporte 24.0.0 a 25.1.1, chame setMediaButtonReceiver para permitir que um botão de mídia reinicie uma sessão de mídia inativa.

Você pode desativar esse comportamento no Android 5.0 (API de nível 21) e versões mais recentes definindo um receptor de botão de mídia nulo:

Kotlin

// Create a MediaSessionCompat
mediaSession = MediaSessionCompat(context, LOG_TAG)
mediaSession.setMediaButtonReceiver(null)

Java

// Create a MediaSessionCompat
mediaSession = new MediaSessionCompat(context, LOG_TAG);
mediaSession.setMediaButtonReceiver(null);

Como personalizar gerenciadores de botão de mídia

O comportamento padrão de onMediaButtonEvent() extrai o código da tecla e usa o estado atual da sessão de mídia e a lista de ações compatíveis para determinar qual método chamar. Por exemplo, KEYCODE_MEDIA_PLAY invoca onPlay().

Para oferecer uma experiência de botão de mídia consistente em todos os apps, use o comportamento padrão e desvie apenas para uma finalidade específica. Se um botão de mídia precisar de gerenciamento personalizado, modifique o método onMediaButtonEvent() do callback, extraia o KeyEvent usando intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT), processe o evento e retorne true.

Resumo

Para processar corretamente os eventos do botão de mídia em todas as versões do Android, é necessário especificar FLAG_HANDLES_MEDIA_BUTTONS ao criar uma sessão de mídia.

Além disso, dependendo das versões do Android para as quais você pretende oferecer suporte, também é necessário atender a estes requisitos:

Ao executar no Android 5.0 ou versões posteriores:

  • Chame MediaControllerCompat.setMediaController() do callback do controlador de mídia onConnected()
  • Para permitir que um botão de mídia reinicie uma sessão inativa, crie dinamicamente um MediaButtonReceiver chamando setMediaButtonReceiver() e transmitindo um PendingIntent.

Ao executar em sistemas anteriores ao Android 5.0:

  • Substitua o onKeyDown() da atividade para gerenciar botões de mídia
  • Crie estaticamente um MediaButtonReceiver adicionando-o ao manifesto do app