Cómo responder a los botones de medios

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 deben poder manejar 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(). Dependiendo de la versión de Android que se esté ejecutando, hay dos formas en las que el sistema enruta el evento a un controlador multimedia:

  • Si ejecutas Android 5.0 (API nivel 21) o versiones posteriores, 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 a una devolución de llamada de sesión multimedia.
  • En versiones previas a Android 5.0 (API nivel 21), debes modificar onKeyDown() para manejar 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 manejó 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 maneja el evento, Android intentará encontrar una sesión multimedia que pueda manejarlo. De nuevo, dependiendo de la versión de Android que se esté ejecutando, hay dos formas de buscar una sesión multimedia:

  • Si ejecutas Android 8.0 (API nivel 26) o versiones posteriores, el sistema intenta buscar 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 de medios, el sistema descarta el evento del botón de medios y no ocurre nada. La lógica se muestra en el siguiente diagrama:

  • En versiones previas a Android 8.0 (API nivel 26), el sistema intenta enviar el evento a una sesión multimedia activa. Si hay varias sesiones activas, Android intenta elegir una que se esté preparando para reproducir contenido (que esté almacenándolo en el búfer o estableciendo conexión), lo esté reproduciendo o lo tenga pausado, en lugar de una que esté detenida. (Obtén más información en Cómo manejar botones de medios en una sesión multimedia activa). Si no hay ninguna 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 (API nivel 21) y versiones posteriores, Android llama a onMediaButtonEvent() para enviar automáticamente eventos de botones de medios a tu sesión multimedia. 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 previas a Android 5.0 (API nivel 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 ese propósito. Es una clase de conveniencia en la biblioteca media-compat de Android que maneja ACTION_MEDIA_BUTTON y convierte los intents entrantes en las llamadas al método MediaSessionCompat.Callback apropiadas.

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 de medios en sistemas previos 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 utilizas el objeto MediaSession del marco de trabajo de Android o la biblioteca de compatibilidad 24.0.0 a 25.1.1, debes llamar a setMediaButtonReceiver para permitir que un botón de medios reinicie una sesión multimedia inactiva.

Puedes inhabilitar este comportamiento en Android 5.0 (API nivel 21) y versiones posteriores si configuras un receptor de botón de medios 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 brindar una experiencia coherente de botones de medios en todas las apps, deberías utilizar el comportamiento predeterminado. Usa otro método solamente cuando tengas un propósito específico. Si un botón de medios necesita un manejo personalizado, anula el método onMediaButtonEvent() de tu devolución de llamada, extrae el KeyEvent usando intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT), maneja el evento por tu cuenta y muestra true.

Resumen

Para manejar de forma apropiada los eventos de botones de medios en todas las versiones de Android, tendrás que especificar FLAG_HANDLES_MEDIA_BUTTONS cuando crees una sesión multimedia.

Además, en función de las versiones de Android que planees admitir, también deberás cumplir los siguientes requisitos:

Cuando uses Android 5.0 o versiones posteriores:

  • Llama a MediaControllerCompat.setMediaController() desde la devolución de llamada onConnected() del controlador multimedia.
  • A fin de permitir que un botón de medios 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.