Asistente de Google te permite usar comandos por voz para controlar muchos dispositivos, como Google Home, tu teléfono y mucho más Tiene una capacidad integrada para comprender comandos de medios ("reproducir contenido de Beyoncé") y admitir controles multimedia (como pausar, omitir, avanzar y me gusta).
El Asistente se comunica con las apps multimedia de Android a través de un archivo multimedia sesión. Puede usar intents o servicios para Inicia la app y comienza la reproducción. Para obtener los mejores resultados, la app debería 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 sesión multimedia para que el Asistente opere los controles de transporte una vez iniciada la reproducción.
Ten en cuenta que, si bien el Asistente solo usa las acciones mencionadas en esta sección, el
una práctica recomendada es implementar todas las APIs de preparación y reproducción para garantizar
compatibilidad con otras aplicaciones. Para cualquier acción que no admitas,
las devoluciones de llamada de la sesión multimedia pueden simplemente mostrar un error con
ERROR_CODE_NOT_SUPPORTED
Para habilitar los controles multimedia y de transporte, configura estas marcas en el directorio de tu app.
Objeto MediaSession
:
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 y, luego, implementar el
las devoluciones de llamada de la sesión multimedia correspondiente. Declara tus acciones admitidas en
setActions()
El Universal Android Music Player es un buen ejemplo de cómo organizar 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 | Callback |
---|---|
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 | Callback |
---|---|
ACTION_PREPARE |
onPrepare() |
ACTION_PREPARE_FROM_SEARCH |
onPrepareFromSearch() |
ACTION_PREPARE_FROM_URI (*) |
onPrepareFromUri() |
* Las acciones basadas en el URI de Asistente de Google solo funcionan para empresas. que proporcionan URI a Google. Para obtener más información sobre cómo describir tu contenido multimedia a Google consulta Acciones multimedia.
Con la implementación de las APIs de preparación, la latencia de reproducción después de un comando puede reducirse. Las apps de música que quieran mejorar la latencia de la reproducción pueden usar el tiempo adicional para comenzar a almacenar contenido en caché y preparar la reproducción de contenido multimedia.
Cómo analizar búsquedas
Cuando un usuario busca un elemento multimedia específico, como “Reproducir jazz en
[nombre de tu aplicación]" o "Escuchar [título de la canción]", las
onPrepareFromSearch()
o
onPlayFromSearch()
método de devolución de llamada recibe un parámetro de consulta y un paquete de extras.
Tu app debe seguir estos pasos para analizar la búsqueda por voz e iniciar la reproducción pasos:
- Usar el paquete de extras y la cadena de búsqueda que se muestra en 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.
La onPlayFromSearch()
toma un parámetro extras con información más detallada de la voz
búsqueda. Estos extras te ayudan a encontrar el contenido de audio en tu app para reproducirlo.
Si los resultados de la búsqueda no pueden proporcionar estos datos, puedes implementar una lógica
para analizar la búsqueda sin procesar y reproducir las pistas correspondientes según el
para cada búsqueda.
El SO Android Automotive y Android Auto admiten los siguientes extras:
En el siguiente fragmento de código, se muestra cómo anular la onPlayFromSearch()
en tu MediaSession.Callback
para analizar la búsqueda por voz e iniciar 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 } }
Obtén un ejemplo más detallado sobre cómo implementar la búsqueda por voz para reproducir audio contenido en tu app, consulta el Universal Android Music Player muestra.
Cómo manejar las búsquedas vacías
Si es onPrepare()
, onPlay()
, onPrepareFromSearch()
o onPlayFromSearch()
sin una búsqueda, tu app de música debería reproducir la "actual"
medios de comunicación. Si no hay contenido multimedia actual, la app debería intentar reproducir algo, como
como una canción de la playlist más reciente o en una fila aleatoria. El asistente usa
estas APIs cuando un usuario solicita "Reproducir música en [nombre de tu app]" sin
información adicional.
Cuando un usuario dice "Reproducir música en [nombre de tu app]", cuando usas el SO Android Automotive o
Android Auto llama al onPlayFromSearch()
de tu app para iniciar y reproducir audio
. Sin embargo, como el usuario no dijo el nombre del elemento multimedia, onPlayFromSearch()
recibe un parámetro de consulta vacío. En estos casos, tu app debería
responder reproduciendo un audio de forma inmediata, como una canción del último
lista de reproducción o una fila aleatoria.
Cómo declarar compatibilidad heredada con las acciones de voz
En la mayoría de los casos, controlar las acciones de reproducción descritas anteriormente le brinda a la app la funcionalidad de reproducción que necesita. Sin embargo, algunos sistemas requieren que tu app contienen un filtro de intents para la búsqueda. Debes declarar la compatibilidad con este intent. filtrar los archivos de manifiesto de tu app.
Incluye este código en el archivo de manifiesto para una aplicación para teléfonos:
<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
Una vez que se active la sesión multimedia de tu app, el Asistente podrá emitir comandos por voz para controlar la reproducción y actualizar los metadatos multimedia. Para que esto funcione, código debería habilitar las siguientes acciones e implementar la interfaz devoluciones de llamada:
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 |
Nota:
- 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
.
Es probable que las acciones de voz que admitas varíen según el tipo de contenido.
Tipo de contenido | Acciones requeridas |
---|---|
Música |
Debe admitirse: Reproducir, Pausar, Detener, Ir al siguiente y Ir al anterior Recomendamos especialmente compatibilidad con: Seek To |
Podcast |
Debe admitirse: Reproducir, Pausar, Detener y Buscar hasta. Compatibilidad con las siguientes recomendaciones: Ir al siguiente y al anterior |
Audiolibro | Debe admitirse: Reproducir, Pausar, Detener y Buscar hasta. |
Radio | Debe admitirse: Reproducir, Pausar y Detener. |
Noticias | Debe admitirse: Reproducir, Pausar, Detener, Ir al siguiente y Ir al anterior |
Video |
Compatibilidad con: Reproducir, Pausar, Detener, Buscar hasta, Retroceder y Avanzar rápido Recomendamos especialmente compatibilidad con las siguientes opciones: Ir al siguiente y Ir al anterior |
Debes admitir tantas acciones mencionadas anteriormente como tus ofertas de productos. permitir, pero aun así responder correctamente a cualquier otra acción. Por ejemplo, si solo los usuarios premium tienen la posibilidad de volver al elemento anterior, puedes aumentar un error si un usuario de nivel gratuito le pide al Asistente que regrese al elemento anterior. Consulta la sección de manejo de errores para obtener más orientación.
Ejemplos de consultas por voz que puedes probar
En la siguiente tabla, se describen algunos ejemplos de consultas que deberías usar prueba tu implementación:
Devolución de llamada de MediaSession | La frase "Hey Google" | |
---|---|---|
onPlay() |
"Reproduce". "Reanudar" |
|
onPlayFromSearch()
onPlayFromUri() |
Música |
"Reproduce música o canciones en (nombre de la app)". Esta consulta está vacía. "Reproduce (canción | artista | álbum | género | playlist) en (nombre de la app)". |
Radio | "Reproduce (frecuencia | estación) en (nombre de la app)". | |
Audiolibro |
"Lee mi audiolibro en (nombre de la app)". "Lee (audiolibro) en (nombre de la app)". |
|
Podcasts | "Reproducir (podcast) en (nombre de la app)". | |
onPause() |
"Pausar" | |
onStop() |
"Detener" | |
onSkipToNext() |
“Siguiente (canción | episodio | pista)”. | |
onSkipToPrevious() |
“Anterior (canción | episodio | pista)”. | |
onSeekTo() |
"Reiniciar". "Avanza ## segundos". "Retrocede ## minutos". |
|
N/A (mantén tu
MediaMetadata
actual) |
"¿Qué está sonando?" |
Errores
Asistente controla los errores de una sesión multimedia cuando ocurren y, luego, informa
a los usuarios. Asegúrate de que tu sesión multimedia actualice el estado del transporte y
código de error en su PlaybackState
correctamente, como se describe en Cómo trabajar con un
sesión multimedia. El Asistente
reconoce todos los códigos de error que devuelve
getErrorCode()
Casos que suelen manejarse de forma inadecuada
Estos son algunos ejemplos de casos de error que debes verificar correctamente:
- El usuario debe acceder
- Establece el código de error
PlaybackState
enERROR_CODE_AUTHENTICATION_EXPIRED
. - Establece el mensaje de error
PlaybackState
. - Si es necesario para la reproducción, establece el estado
PlaybackState
enSTATE_ERROR
. De lo contrario, conservará el resto dePlaybackState
tal como está.
- Establece el código de error
- El usuario solicita una acción no disponible
- Configura correctamente el código de error
PlaybackState
. Por ejemplo, establece el DePlaybackState
aERROR_CODE_NOT_SUPPORTED
si la acción no es compatible, o bienERROR_CODE_PREMIUM_ACCOUNT_REQUIRED
si la acción está protegida con el acceso. - Establece el mensaje de error
PlaybackState
. - Conserva el resto de
PlaybackState
tal como está.
- Configura correctamente el código de error
- El usuario solicita contenido que no está disponible en la app
- Configura correctamente el código de error
PlaybackState
. Por ejemplo, usaERROR_CODE_NOT_AVAILABLE_IN_REGION
- Establece el mensaje de error
PlaybackState
. - Establece el estado
PlaybackSate
enSTATE_ERROR
para interrumpir la reproducción. de lo contrario, conservará el resto dePlaybackState
tal como está.
- Configura correctamente el código de error
- El usuario solicita contenido para el que no está disponible una concordancia exacta. Por ejemplo, un
usuario de nivel gratuito que pide contenido disponible solo para los usuarios de nivel premium.
- Te recomendamos que no devuelvas un error y que, en su lugar, debes priorizar encontrar algo similar para jugar. El Asistente se encargará de hablar más respuesta de voz relevante antes de iniciar la reproducción.
Reproducción con un intent
El Asistente puede iniciar una app de audio o video, y comenzar la reproducción enviando un con un vínculo directo.
El intent y su vínculo directo pueden provenir de distintas fuentes:
- Cuando el Asistente iniciar una aplicación móvil, puede usar la Búsqueda de Google para recuperar contenido con lenguaje de marcado que proporciona una acción de reproducción con un vínculo.
- Cuando el Asistente inicie una app para TV, la app deberá incluir una
Proveedor de búsqueda de TV
para exponer URIs para contenido multimedia. El Asistente envía una consulta a
el proveedor de contenido que debe devolver una intent que contenga un URI para el vínculo directo y
una acción opcional.
Si la consulta devuelve una acción en el intent,
el Asistente envía esa acción y el URI de vuelta a tu app.
Si el proveedor no especificó
una acción, 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. Tu app deberá iniciar la reproducción cuando
recibe un intent con EXTRA_START_PLAYBACK
.
Cómo manejar los intents cuando hay una actividad en curso
Los usuarios pueden pedirle a Asistente que reproduzca contenido mientras se sigue reproduciendo la app contenido de una solicitud anterior. Esto significa que tu app puede recibir intents nuevos 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 iniciar la reproducción, es posible que el Asistente agregue
marcas
al intent que envía a tu app. En particular, puede agregar
FLAG_ACTIVITY_CLEAR_TOP
o
FLAG_ACTIVITY_NEW_TASK
o ambos. Aunque el código
no necesita controlar 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 URI nuevo
mientras se reproduce el URI anterior. Recomendamos probar cómo responde tu app en este caso. Puedes usar el comando adb
de línea para simular la situación (la constante 0x14000000
es el operador booleano OR bit a bit 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
media browser service
que permita conexiones desde el Asistente,
Asistente puede iniciar la app comunicándose con la
media session
El servicio de exploración multimedia nunca debe iniciar una actividad.
El Asistente iniciará tu actividad en función del PendingIntent
que definas
con setSessionActivity().
Asegúrate de configurar el MediaSession.Token cuando inicializa el servicio del navegador multimedia. Recuerda establecer las acciones de reproducción compatibles. en todo momento, incluso durante la inicialización. Asistente espera tu contenido multimedia app para configurar las acciones de reproducción antes de que el Asistente envíe la primera reproducción kubectl.
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 tu sesión multimedia de la app.
En el siguiente diagrama, se muestra el orden de las llamadas que generan Asistente y las las devoluciones de llamada de la sesión multimedia correspondiente. (Las devoluciones de llamada de preparación se envían solo si tu app los admite). Todas las llamadas son asíncronas. El Asistente no espera una respuesta de tu aplicación.
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 de ACTION_PREPARE_*
, Asistente llama a la acción PREPARE
antes de iniciar el anuncio.
Cómo establecer conexión con un MediaBrowserService
Para usar un servicio para iniciar tu app, el Asistente debe poder conectarse al MediaBrowserService de la app y
recupera su MediaSession.Token. Las solicitudes de conexión se controlan en la red
onGetRoot()
. 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 Reproductor de música universal 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>