Cómo responder a los botones multimedia

Los botones de medios son botones de hardware que se encuentran en dispositivos Android y otros dispositivos periféricos, por ejemplo, el botón de pausa/reproducción en auriculares Bluetooth. Cuando un usuario presiona un botón de medios, Android genera un KeyEvent, que contiene un código de tecla que identifica el botón. Los códigos de tecla para los KeyEvents del botón de medios son constantes que comienzan con KEYCODE_MEDIA (por ejemplo, KEYCODE_MEDIA_PLAY).

Las apps deberían poder controlar los eventos de botones de medios en tres casos y en el siguiente orden de prioridad:

  • Cuando la actividad de la IU de la app es visible
  • Cuando la actividad de la IU está oculta y la sesión multimedia de la app está activa
  • Cuando la actividad de IU está oculta y la sesión multimedia de la app está inactiva y debe reiniciarse

Cómo manejar los botones de medios en una actividad en primer plano

La actividad en primer plano recibe el evento de tecla del botón de medios en su método onKeyDown(). Según la versión de Android que se esté ejecutando, hay dos formas en que el sistema enruta el evento a un controlador multimedia:

  • Si ejecutas Android 5.0 (nivel de API 21) o una versión posterior, llama a FLAG_HANDLES_MEDIA_BUTTONS MediaBrowserCompat.ConnectionCallback.onConnected. Esto llamará automáticamente al dispatchMediaButtonEvent() de tu controlador multimedia, que convierte el código de tecla en una devolución de llamada de sesión multimedia.
  • En versiones previas a Android 5.0 (nivel de API 21), debes modificar onKeyDown() para controlar el evento por tu cuenta. (Obtén más información en Cómo manejar los botones de medios en una sesión multimedia activa). En el siguiente fragmento de código, se muestra cómo interceptar el código de tecla y llamar a dispatchMediaButtonEvent(). Asegúrate de mostrar true para indicar que se controló el evento:

    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);
        }
        

Cómo encontrar una sesión multimedia

Si la actividad en primer plano no controla el evento, Android intentará encontrar una sesión multimedia que pueda controlarlo. Una vez más, según la versión de Android que se ejecute, hay dos maneras de buscar una sesión multimedia:

  • Si ejecutas Android 8.0 (nivel de API 26) o una versión posterior, el sistema intentará encontrar la última app con una MediaSession que reprodujo audio de forma local. Si la sesión aún está activa, Android le envía el evento directamente. De lo contrario, si la sesión no está activa y tiene un receptor de botón de medios, Android envía el evento al receptor, que reiniciará la sesión para que pueda recibir el evento. (Obtén más información en Cómo usar los botones de medios para reiniciar una sesión multimedia inactiva). Si la sesión no tiene un receptor de botón multimedia, el sistema descarta el evento del botón multimedia y no sucederá nada. La lógica se muestra en el siguiente diagrama:

  • En versiones anteriores a Android 8.0 (nivel de API 26), el sistema intenta enviar el evento a una sesión multimedia activa. Si hay varias sesiones multimedia activas, Android intentará elegir una que se esté preparando para reproducir (almacenando en búfer/conectándose), reproduciendo o pausando, en lugar de una que esté detenida. (Obtén más información en Cómo controlar los botones de medios en una sesión multimedia activa). Si no hay una sesión activa, Android intenta enviar el evento a la sesión activa más reciente. (Obtén más información en Cómo usar los botones de medios para reiniciar una sesión multimedia inactiva). La lógica se muestra en el siguiente diagrama:

Cómo manejar botones de medios en una sesión multimedia activa

En Android 5.0 (nivel de API 21) y versiones posteriores, Android llama a onMediaButtonEvent() para enviar automáticamente eventos de botones de medios a tu sesión multimedia activa. De forma predeterminada, esta devolución de llamada convierte el KeyEvent en el método apropiado de devolución de llamada de la sesión multimedia que coincide con el código de tecla.

En versiones anteriores a Android 5.0 (nivel de API 21), Android maneja los eventos de botones de medios transmitiendo un intent con la acción ACTION_MEDIA_BUTTON. Tu app debe registrar un BroadcastReceiver para interceptar estos intents. La clase MediaButtonReceiver se diseñó específicamente para este propósito. Es una clase de conveniencia de la biblioteca de compatibilidad multimedia de Android que controla ACTION_MEDIA_BUTTON y traduce los intents entrantes en las llamadas al método MediaSessionCompat.Callback correspondientes.

Un MediaButtonReceiver es un BroadcastReceiver de corta duración. Reenvía los intents entrantes al servicio que administra tu sesión multimedia. Si quieres usar botones multimedia en sistemas anteriores a Android 5.0, debes incluir el MediaButtonReceiver en tu manifiesto con un filtro de intents MEDIA_BUTTON.:

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

El BroadcastReceiver reenvía el intent a tu servicio. Para analizar el intent y generar la devolución de llamada a la sesión multimedia, incluye el método MediaButtonReceiver.handleIntent() en el onStartCommand() de tu servicio. Así, el código de tecla se convierte en el método de devolución de llamada de sesión adecuado.

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);
 }

Cómo usar los botones de medios para reiniciar una sesión multimedia inactiva

Si Android puede identificar la última sesión multimedia activa, intentará reiniciar la sesión enviando un intent ACTION_MEDIA_BUTTON a un componente registrado en el manifiesto (como un servicio o BroadcastReceiver).

De este modo, tu app puede reiniciar la reproducción mientras su IU no está visible, como sucede con la mayoría de las apps de audio.

Este comportamiento se habilita automáticamente cuando usas MediaSessionCompat. Si usas el MediaSession del framework de Android o la biblioteca de compatibilidad 24.0.0 a 25.1.1, debes llamar a setMediaButtonReceiver para permitir que un botón multimedia reinicie una sesión multimedia inactiva.

Puedes inhabilitar este comportamiento en Android 5.0 (nivel de API 21) y versiones posteriores configurando un receptor de botón multimedia 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);

Cómo personalizar los controladores de botones de medios

El comportamiento predeterminado para onMediaButtonEvent() extrae el código de tecla y utiliza el estado actual de la sesión multimedia y la lista de acciones admitidas para determinar a qué método llamar. Por ejemplo, KEYCODE_MEDIA_PLAY invoca a onPlay().

Para proporcionar una experiencia coherente de botones de medios en todas las apps, debes usar el comportamiento predeterminado y solo desviarte para un fin específico. Si un botón multimedia necesita un control personalizado, anula el método onMediaButtonEvent() de la devolución de llamada, extrae KeyEvent con intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT), controla el evento por tu cuenta y muestra true.

Resumen

Para controlar correctamente los eventos de botones de medios en todas las versiones de Android, debes especificar FLAG_HANDLES_MEDIA_BUTTONS cuando crees una sesión multimedia.

Además, según las versiones de Android que planees admitir, también deberás cumplir con estos requisitos:

Cuando uses Android 5.0 o versiones posteriores:

  • Llama a MediaControllerCompat.setMediaController() desde la devolución de llamada onConnected() del controlador multimedia.
  • Para permitir que un botón multimedia reinicie una sesión inactiva, llama a setMediaButtonReceiver() y pásale un PendingIntent para crear dinámicamente un MediaButtonReceiver.

Cuando uses sistemas previos a Android 5.0:

  • Anula el objeto onKeyDown() de la actividad para manejar botones de medios.
  • Crea un MediaButtonReceiver de forma estática agregándolo al manifiesto de la app.