El Asistente de Google te permite usar comandos de voz para controlar muchos dispositivos, como Google Home, tu teléfono, etc. Tiene integrada la capacidad de entender comandos de contenido multimedia ("Reproduce algo de Beyoncé") y admite los controles correspondientes (como pausar, omitir, avanzar o Me gusta).
El Asistente se comunica con las apps de contenido multimedia de Android mediante una sesión multimedia. Puedes usar intents o servicios para iniciar tu app y comenzar la reproducción. A fin de obtener los mejores resultados, la app debe implementar todas las funciones que se describen en esta página.
.Cómo usar una sesión multimedia
Cada app de audio y video debe implementar una sesión multimedia para que el Asistente pueda manejar los controles de transporte una vez que se inicia la reproducción.
Para habilitar los controles de contenido multimedia y de transporte, configura estas marcas en el objeto MediaSession
de tu app:
Kotlin
session.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS )
Java
session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
La sesión multimedia de tu app debe declarar las acciones que admite e implementar las devoluciones de llamada de sesión multimedia correspondientes. Declara las acciones compatibles en setActions()
.
El proyecto de muestra del Universal Music Player es un ejemplo útil de cómo configurar una sesión multimedia.
Acciones de reproducción
Para iniciar la reproducción desde un servicio, una sesión multimedia debe tener las siguientes acciones PLAY
y las devoluciones de llamada correspondientes:
Acción | Devolución de llamada |
---|---|
ACTION_PLAY |
onPlay() |
ACTION_PLAY_FROM_SEARCH |
onPlayFromSearch() |
ACTION_PLAY_FROM_URI (*) |
onPlayFromUri() |
Tu sesión también debe implementar estas acciones PREPARE
y las devoluciones de llamada correspondientes:
Acción | Devolución de llamada |
---|---|
ACTION_PREPARE |
onPrepare() |
ACTION_PREPARE_FROM_SEARCH |
onPrepareFromSearch() |
ACTION_PREPARE_FROM_URI (*) |
onPrepareFromUri() |
(*) Las acciones basadas en URI del Asistente de Google solo funcionan para empresas que proporcionan URI a Google. Si deseas obtener más información sobre cómo describir tu contenido multimedia a Google, consulta Acciones multimedia.
Si se implementan las API de preparación, se puede reducir la latencia de la reproducción después de un comando de voz. Las apps de contenido multimedia que deseen mejorar la latencia de la reproducción pueden usar el tiempo adicional para comenzar a almacenar el contenido en caché y preparar la reproducción multimedia.
Ten en cuenta que, si bien el Asistente solo usa las acciones que se detallan en esta sección, la práctica recomendada es implementar todas las API de preparación y reproducción para garantizar la compatibilidad con otras aplicaciones.
Cómo analizar búsquedas
Cuando un usuario busca un elemento multimedia específico, como "Reproducir jazz en [nombre de tu app]" o "Escuchar [título de la canción]", los métodos de devolución de llamada onPrepareFromSearch()
o onPlayFromSearch()
reciben un parámetro de búsqueda y un paquete de extras.
Tu app debe seguir los pasos a continuación para analizar la búsqueda por voz e iniciar la reproducción:
- Debe usar el paquete de extras y la string de búsqueda que muestra la búsqueda por voz para filtrar los resultados.
- Crea una cola de reproducción a partir de los resultados.
- Debe reproducir el elemento multimedia más relevante de los resultados.
El método onPlayFromSearch()
toma un parámetro de extras con información más detallada de la búsqueda por voz. Estos extras te ayudan a encontrar el contenido de audio en tu app para reproducirlo.
Si los resultados de la búsqueda no logran proporcionar estos datos, puedes implementar la lógica para analizar los datos de búsqueda sin procesar y reproducir las pistas correspondientes.
El SO Android Automotive y Android Auto admiten los siguientes extras:
En el siguiente fragmento de código, se muestra cómo anular el método onPlayFromSearch()
en tu implementación de MediaSession.Callback
para analizar la búsqueda por voz y comenzar la reproducción:
Kotlin
override fun onPlayFromSearch(query: String?, extras: Bundle?) { if (query.isNullOrEmpty()) { // The user provided generic string e.g. 'Play music' // Build appropriate playlist queue } else { // Build a queue based on songs that match "query" or "extras" param val mediaFocus: String? = extras?.getString(MediaStore.EXTRA_MEDIA_FOCUS) if (mediaFocus == MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE) { isArtistFocus = true artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST) } else if (mediaFocus == MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE) { isAlbumFocus = true album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM) } // Implement additional "extras" param filtering } // Implement your logic to retrieve the queue var result: String? = when { isArtistFocus -> artist?.also { searchMusicByArtist(it) } isAlbumFocus -> album?.also { searchMusicByAlbum(it) } else -> null } result = result ?: run { // No focus found, search by query for song title query?.also { searchMusicBySongTitle(it) } } if (result?.isNotEmpty() == true) { // Immediately start playing from the beginning of the search results // Implement your logic to start playing music playMusic(result) } else { // Handle no queue found. Stop playing if the app // is currently playing a song } }
Java
@Override public void onPlayFromSearch(String query, Bundle extras) { if (TextUtils.isEmpty(query)) { // The user provided generic string e.g. 'Play music' // Build appropriate playlist queue } else { // Build a queue based on songs that match "query" or "extras" param String mediaFocus = extras.getString(MediaStore.EXTRA_MEDIA_FOCUS); if (TextUtils.equals(mediaFocus, MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) { isArtistFocus = true; artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST); } else if (TextUtils.equals(mediaFocus, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) { isAlbumFocus = true; album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM); } // Implement additional "extras" param filtering } // Implement your logic to retrieve the queue if (isArtistFocus) { result = searchMusicByArtist(artist); } else if (isAlbumFocus) { result = searchMusicByAlbum(album); } if (result == null) { // No focus found, search by query for song title result = searchMusicBySongTitle(query); } if (result != null && !result.isEmpty()) { // Immediately start playing from the beginning of the search results // Implement your logic to start playing music playMusic(result); } else { // Handle no queue found. Stop playing if the app // is currently playing a song } }
Consulta el ejemplo del Universal Music Player, que incluye más detalles sobre cómo implementar la búsqueda por voz para reproducir contenido de audio en tu app.
Cómo manejar las búsquedas vacías
Si se llama a onPrepare()
, onPlay()
, onPrepareFromSearch()
o onPlayFromSearch()
sin una búsqueda, tu app de contenido multimedia debe reproducir el contenido "actual". En caso de que no haya, la app debería intentar reproducir algún tipo de contenido, como una canción de la playlist más reciente o una cola aleatoria. El Asistente usa estas API cuando un usuario dice "Reproducir música en [nombre de tu app]" sin brindar información adicional.
Cuando un usuario dice "Reproducir música en [nombre de tu app]", el SO Android Automotive o Android Auto intentan iniciar la app y reproducir audio llamando al método onPlayFromSearch()
de la app. Sin embargo, como el usuario no indicó el nombre de un elemento multimedia, el método onPlayFromSearch()
recibe un parámetro de búsqueda vacío. En estos casos, la app debe responder de inmediato reproduciendo audio, por ejemplo, una canción de la playlist más reciente o de una cola aleatoria.
Cómo declarar compatibilidad heredada con las acciones de voz
En la mayoría de los casos, el manejo de las acciones de reproducción descritas anteriormente le brinda a tu app todas las funciones de reproducción que necesita. Sin embargo, algunos sistemas requieren que incluya un filtro de intents para la búsqueda. Debes declarar la compatibilidad con este filtro de intents en los archivos de manifiesto de tu app.
Incluye este código en el archivo de manifiesto para una app de teléfono y también para un módulo del SO Android Automotive (si existe uno):
<activity>
<intent-filter>
<action android:name=
"android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<category android:name=
"android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Controles de transporte
Después de que la sesión multimedia de tu app esté activa, el Asistente podrá enviar comandos por voz para controlar la reproducción y actualizar los metadatos del contenido multimedia. Para que esto funcione, el código debería habilitar las siguientes acciones e implementar las devoluciones de llamada correspondientes:
Acción | Devolución de llamada | Descripción |
---|---|---|
ACTION_SKIP_TO_NEXT |
onSkipToNext() |
Siguiente video |
ACTION_SKIP_TO_PREVIOUS |
onSkipToPrevious() |
Canción anterior |
ACTION_PAUSE, ACTION_PLAY_PAUSE |
onPause() |
Pausar |
ACTION_STOP |
onStop() |
Detener |
ACTION_PLAY |
onPlay() |
Reanudar |
ACTION_SEEK_TO |
onSeekTo() |
Retroceder 30 segundos |
ACTION_SET_RATING |
onSetRating(android.support.v4.media.RatingCompat) |
Me gusta/No me gusta |
ACTION_SET_CAPTIONING_ENABLED |
onSetCaptioningEnabled(boolean) |
Activar o desactivar los subtítulos |
Ten en cuenta lo siguiente:
- Para que los comandos de saltar funcionen,
PlaybackState
necesita estar actualizado con respecto a los parámetrosstate, position, playback speed, and update time
. La app debe llamar asetPlaybackState()
cuando el estado cambia. - La app de contenido multimedia también debe mantener actualizados los metadatos de la sesión multimedia. Esto admite preguntas como "¿qué canción se está reproduciendo?". La app debe llamar a
setMetadata()
cuando cambien los campos aplicables (como el título de la pista, el artista y el nombre). - Se debe configurar
MediaSession.setRatingType()
para indicar el tipo de calificación que admite la app, y esta debe implementaronSetRating()
. Si la app no admite calificaciones, debe establecer el tipo de calificación enRATING_NONE
.
Errores
El Asistente maneja los errores de una sesión multimedia cuando se producen y los informa a los usuarios.
Asegúrate de que tu sesión multimedia actualice correctamente el estado del transporte y el código de error en su PlaybackState
, según se describe en Cómo trabajar con una sesión multimedia.
El Asistente reconoce todos los códigos de error que muestra getErrorCode()
.
Reproducción con un intent
El Asistente puede iniciar una app de audio o video y comenzar la reproducción enviando un intent con un vínculo directo.
El intent y su vínculo directo pueden provenir de distintas fuentes:
- Cuando el Asistente inicia una app para dispositivos móviles, puede usar la Búsqueda de Google a fin de recuperar contenido marcado que proporciona una acción de visualización con un vínculo.
- Cuando el Asistente inicia una app de TV, tu aplicación debe incluir un proveedor de búsqueda de TV a fin de exponer los URI para contenido multimedia. El Asistente envía una consulta al proveedor de contenido, el cual debe mostrar un intent que contenga un URI para el vínculo directo y una acción opcional.
Si la consulta muestra una acción en el intent, el Asistente envía esa acción y el URI a la app. Si el proveedor no especificó una acción, el Asistente agregará
ACTION_VIEW
al intent.
El Asistente agrega el EXTRA_START_PLAYBACK
adicional con el valor true
al intent que envía a tu app. Esta debería iniciar la reproducción cuando reciba un intent con EXTRA_START_PLAYBACK
.
Cómo manejar los intents cuando hay una actividad en curso
Los usuarios pueden pedirle al Asistente que reproduzca algo mientras tu app aún reproduce contenido de una solicitud anterior. Esto significa que la app puede recibir nuevos intents para iniciar la reproducción mientras su actividad de reproducción ya está iniciada y activa.
Las actividades que admiten intents con vínculos directos deben anular onNewIntent()
para manejar nuevas solicitudes.
Al comenzar la reproducción, el Asistente puede agregar marcas adicionales al intent que envía a tu app. En particular, puede agregar FLAG_ACTIVITY_CLEAR_TOP
o FLAG_ACTIVITY_NEW_TASK
, o ambas. Aunque tu código no necesita manejar estas marcas, el sistema Android responde a ellas.
Esto podría afectar el comportamiento de la app cuando llega una segunda solicitud de reproducción con un nuevo URI mientras el URI anterior todavía se está reproduciendo. Recomendamos probar cómo responde tu app en este caso. Puedes usar la herramienta de línea de comandos adb
para simular la situación (la constante 0x14000000
es el OR bit a bit booleano de las dos marcas):
adb shell 'am start -a android.intent.action.VIEW --ez android.intent.extra.START_PLAYBACK true -d <first_uri>' -f 0x14000000
adb shell 'am start -a android.intent.action.VIEW --ez android.intent.extra.START_PLAYBACK true -d <second_uri>' -f 0x14000000
Reproducción desde un servicio
Si tu app tiene un objeto media browser service
que permite conexiones desde el Asistente, este puede comunicarse con el objeto media session
del servicio para iniciar la aplicación.
El servicio de exploración multimedia nunca debe iniciar una actividad.
El Asistente iniciará tu actividad en función del objeto PendingIntent
que definas con setSessionActivity().
Asegúrate de configurar MediaSession.Token cuando inicialices el servicio de exploración multimedia. No olvides configurar las acciones de reproducción compatibles en todo momento, incluso durante la inicialización. El Asistente espera que tu app de contenido multimedia configure las acciones de reproducción antes de que el Asistente envíe el primer comando de reproducción.
Para comenzar desde un servicio, el Asistente implementa las API del cliente del navegador multimedia. Realiza llamadas de TransportControls que activan devoluciones de llamada de acción REPRODUCIR en la sesión multimedia de tu app.
En el siguiente diagrama, se muestran el orden de las llamadas que generó el Asistente y las devoluciones de llamada correspondientes de la sesión multimedia. (Las devoluciones de llamada de preparación se envían solo si tu app las admite). Todas las llamadas son asíncronas. El Asistente no espera ninguna respuesta de tu app.
Cuando un usuario envía un comando por voz para que se reproduzca, el Asistente responde con un breve anuncio. Apenas se completa el anuncio, el Asistente envía una acción REPRODUCIR. No espera ningún estado de reproducción específico.
Si tu app admite las acciones ACTIONPREPARE*, el Asistente llama a la acción PREPARE
antes de iniciar el anuncio.
Cómo establecer conexión con un MediaBrowserService
A fin de utilizar un servicio para iniciar tu app, el Asistente debe poder conectarse al MediaBrowserService de la aplicación y recuperar su MediaSession.Token. Las solicitudes de conexión se manejan en el método onGetRoot()
del servicio. Existen dos formas de manejar las solicitudes:
- Cómo aceptar todas las solicitudes de conexión
- Aceptar las solicitudes de conexión solo de la app del Asistente
Cómo aceptar todas las solicitudes de conexión
Debes mostrar un BrowserRoot para permitir que el Asistente envíe comandos a tu sesión multimedia. La forma más sencilla es permitir que todas las apps de MediaBrowser se conecten a tu MediaBrowserService. Debes mostrar un valor de BrowserRoot no nulo. A continuación, se detalla el código aplicable del Universal Music Player:
Kotlin
override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): BrowserRoot? { // To ensure you are not allowing any arbitrary app to browse your app's contents, you // need to check the origin: if (!packageValidator.isCallerAllowed(this, clientPackageName, clientUid)) { // If the request comes from an untrusted package, return an empty browser root. // If you return null, then the media browser will not be able to connect and // no further calls will be made to other media browsing methods. Log.i(TAG, "OnGetRoot: Browsing NOT ALLOWED for unknown caller. Returning empty " + "browser root so all apps can use MediaController. $clientPackageName") return MediaBrowserServiceCompat.BrowserRoot(MEDIA_ID_EMPTY_ROOT, null) } // Return browser roots for browsing... }
Java
@Override public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { // To ensure you are not allowing any arbitrary app to browse your app's contents, you // need to check the origin: if (!packageValidator.isCallerAllowed(this, clientPackageName, clientUid)) { // If the request comes from an untrusted package, return an empty browser root. // If you return null, then the media browser will not be able to connect and // no further calls will be made to other media browsing methods. LogHelper.i(TAG, "OnGetRoot: Browsing NOT ALLOWED for unknown caller. " + "Returning empty browser root so all apps can use MediaController." + clientPackageName); return new MediaBrowserServiceCompat.BrowserRoot(MEDIA_ID_EMPTY_ROOT, null); } // Return browser roots for browsing... }
Cómo aceptar el paquete y la firma de la app del Asistente
Puedes permitir de forma explícita que el Asistente se conecte a tu servicio de exploración multimedia si verificas el nombre y la firma del paquete. Tu app recibirá el nombre del paquete en el método onGetRoot de tu MediaBrowserService. Debes mostrar un BrowserRoot para permitir que el Asistente envíe comandos a tu sesión multimedia. El ejemplo del Universal Music Player mantiene una lista de nombres y firmas conocidos de paquetes. A continuación, se detallan los nombres y las firmas de los paquetes que usa el Asistente de Google.
<signature name="Google" package="com.google.android.googlequicksearchbox">
<key release="false">19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00</key>
<key release="true">f0:fd:6c:5b:41:0f:25:cb:25:c3:b5:33:46:c8:97:2f:ae:30:f8:ee:74:11:df:91:04:80:ad:6b:2d:60:db:83</key>
</signature>
<signature name="Google Assistant on Android Automotive OS" package="com.google.android.carassistant">
<key release="false">17:E2:81:11:06:2F:97:A8:60:79:7A:83:70:5B:F8:2C:7C:C0:29:35:56:6D:46:22:BC:4E:CF:EE:1B:EB:F8:15</key>
<key release="true">74:B6:FB:F7:10:E8:D9:0D:44:D3:40:12:58:89:B4:23:06:A6:2C:43:79:D0:E5:A6:62:20:E3:A6:8A:BF:90:E2</key>
</signature>