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 aldispatchMediaButtonEvent()
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 mostrartrue
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 llamadaonConnected()
del controlador multimedia. - A fin de permitir que un botón de medios reinicie una sesión inactiva, llama a
setMediaButtonReceiver()
y pásale unPendingIntent
para crear dinámicamente unMediaButtonReceiver
.
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.