Como 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, 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 ser capazes de gerenciar 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 do botão de mídia no método onKeyDown(). Dependendo da versão em execução do Android, o sistema pode direcionar o evento para um controlador de mídia de duas maneiras:

  • Se você estiver executando o Android 5.0 (API de nível 21) ou versões posteriores, chame FLAG_HANDLES_MEDIA_BUTTONS MediaBrowserCompat.ConnectionCallback.onConnected. Isso 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), é preciso modificar o onKeyDown() para você mesmo gerenciar o evento. 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 de tecla 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 gerenciar o evento, o Android tentará encontrar uma sessão de mídia que possa fazer isso. Novamente, dependendo da versão do Android em execução, é possível pesquisar a sessão de mídia de duas maneiras:

  • Se você estiver executando o Android 8.0 (API de nível 26) ou versões posteriores, o sistema tentará encontrar o último app com um 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 enviará o evento para o receptor, que 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 descartará o evento e nada acontecerá. A lógica é mostrada no diagrama a seguir.

  • Quando executado em versões anteriores ao Android 8.0 (API de nível 26), o sistema tentará enviar o evento para uma sessão de mídia ativa. Se houver várias sessões de mídia ativas, o Android tentará escolher uma sessão que esteja se preparando para reproduzir, armazenar em buffer ou conectar, reproduzir ou pausar, em vez de uma sessão interrompida. Consulte Como processar botões de mídia em uma sessão de mídia ativa para ver mais detalhes. Se não houver uma sessão ativa, o Android 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 (API de nível 21) ou versões posteriores, o Android despacha automaticamente os eventos do botão de mídia para a sessão 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.

Quando executado em versões anteriores ao Android 5.0 (API de nível 21), o Android gerencia eventos do botão de mídia transmitindo um intent com a ação ACTION_MEDIA_BUTTON. O app precisa registrar um BroadcastReceiver para interceptar esses 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 os intents de entrada nas chamadas de método MediaSessionCompat.Callback apropriadas.

Um MediaButtonReceiver é um BroadcastReceiver de curta duração. Ele encaminha os intents de entrada para o serviço que está gerenciando a 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 o intent e gerar o callback para a 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 biblioteca MediaSession ou a Biblioteca de Suporte 24.0.0 a 25.1.1 do framework do Android, chame setMediaButtonReceiver para permitir que um botão de mídia reinicie uma sessão inativa.

Você pode desativar esse comportamento no Android 5.0 (API de nível 21) ou versões posteriores configurando 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 fornecer 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, substitua o método onMediaButtonEvent() de callback, extraia o KeyEvent usando intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT), gerencie o evento e retorne true.

Resumo

Para gerenciar adequadamente eventos do botão de mídia em todas as versões do Android, especifique FLAG_HANDLES_MEDIA_BUTTONS ao criar uma sessão de mídia.

Além disso, dependendo das versões do Android que você pretende oferecer, 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 a ele 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