Asistente de Google y apps de contenido multimedia

Asistente de Google te permite usar comandos por voz para controlar muchos dispositivos, como Google Home, tu teléfono y muchos más. Tiene una capacidad integrada para comprender comandos multimedia ("tocar algo de Beyoncé") y admite controles multimedia (como pausar, omitir, adelantar y Me gusta).

Asistente se comunica con las apps multimedia de Android a través de una sesión multimedia. Puede usar intents o servicios para iniciar la app y comenzar la reproducción. Para obtener los mejores resultados, tu 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 Asistente pueda operar los controles de transporte una vez que se inicie la reproducción.

Ten en cuenta que, si bien Asistente solo usa las acciones enumeradas en esta sección, la práctica recomendada es implementar todas las APIs de preparación y reproducción para garantizar la compatibilidad con otras aplicaciones. Para cualquier acción que no admitas, las devoluciones de llamada de la sesión multimedia simplemente pueden mostrar un error mediante ERROR_CODE_NOT_SUPPORTED.

Para habilitar los controles de contenido multimedia y 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 admitidas en setActions().

El proyecto de muestra del Universal Android Music Player es un buen ejemplo 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 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 URI del Asistente de Google solo funcionan para las empresas que proporcionan URI a Google. Si quieres obtener más información para describir tu contenido multimedia a Google, consulta Acciones multimedia.

Si implementas las APIs de preparación, se puede reducir la latencia de reproducción después de un comando por voz. Las apps de música que deseen mejorar la latencia de reproducción pueden usar el tiempo adicional para comenzar a almacenar en caché contenido y preparar la reproducción de contenido multimedia.

Cómo analizar búsquedas

Cuando un usuario busca un elemento multimedia específico, como "Reproduce jazz en [nombre de tu app]" o "Escuchar [título de la canción]", el método de devolución de llamada onPrepareFromSearch() o onPlayFromSearch() recibe un parámetro de búsqueda y un paquete de extras.

Tu app debe analizar la búsqueda por voz e iniciar la reproducción siguiendo estos pasos:

  1. Usa el paquete de extras y la cadena de búsqueda que muestra la búsqueda por voz para filtrar los resultados.
  2. Crea una cola de reproducción a partir de los resultados.
  3. 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 pueden proporcionar estos datos, puedes implementar la lógica para analizar la búsqueda sin procesar y reproducir las pistas adecuadas según la consulta.

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 Android 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 música debe reproducir el contenido multimedia "actual". Si no hay contenido multimedia actual, la app debería intentar reproducir algo, como una canción de la playlist más reciente o de una cola aleatoria. El Asistente usa estas APIs cuando un usuario pide "Reproducir música en [nombre de tu app]" sin información adicional.

Cuando un usuario dice "Reproducir música en [nombre de tu app]", el SO Android Automotive o Android Auto intentarán iniciar la app y reproducir audio llamando al método onPlayFromSearch() de la app. Sin embargo, como el usuario no indicó el nombre del elemento multimedia, el método onPlayFromSearch() recibe un parámetro de consulta vacío. En estos casos, la app debe responder de inmediato reproduciendo audio, como 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, administrar 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 tu app contenga 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 de 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 la sesión multimedia de tu app está activa, Asistente puede emitir comandos por voz para controlar la reproducción y actualizar los metadatos del contenido multimedia. Para que esto funcione, el código debe 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

Nota:

  • Para que los comandos de saltar funcionen, PlaybackState necesita estar actualizado con respecto a los parámetros state, position, playback speed, and update time. La app debe llamar a setPlaybackState() 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 implementar onSetRating(). Si la app no admite calificaciones, debe establecer el tipo de calificación en RATING_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

Deben ser compatibles: Reproducir, Pausar, Detener, Pasar al Siguiente y Ir al Anterior

Recomendamos especialmente asistencia para: Buscar en

Podcast

Debe ser compatible: Reproducir, Pausar, Detener y Saltar a

Recomendar asistencia para: Saltar al siguiente y Saltar al anterior

Audiolibro Debe ser compatible: Reproducir, Pausar, Detener y Saltar a
Radio Debe ser compatible: Reproducir, Pausar y Detener
Noticias Deben ser compatibles: Reproducir, Pausar, Detener, Pasar al Siguiente y Ir al Anterior
Video

Debe ser compatible: Reproducir, Pausar, Detener, Saltar, Retroceder y Avanzar

Recomendamos especialmente asistencia para lo siguiente: Ir al siguiente y al anterior

Debes admitir tantas de las acciones mencionadas anteriormente como permitan tus ofertas de productos, pero debes responder con fluidez a cualquier otra acción. Por ejemplo, si solo los usuarios premium tienen la capacidad de volver al elemento anterior, podrías generar un error si un usuario de nivel gratuito le pide al Asistente que vuelva al elemento anterior. Consulta la sección de manejo de errores para obtener más orientación.

Ejemplos de consultas por voz

En la siguiente tabla, se describen algunas consultas de muestra que debes usar mientras pruebas tu implementación:

Devolución de llamada de MediaSession Frase "Hey Google" para usar
onPlay()

"Reproduce".

"Reanudar"

onPlayFromSearch()
onPlayFromUri()
Música

"Reproducir música o canciones en (nombre de la app)". Esta es una consulta 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 "Reproduce (podcast) en (nombre de la app)".
onPause() "Pausar"
onStop() "Detener"
onSkipToNext() "Siguiente (canción | episodio | pista)".
onSkipToPrevious() “Previous (canción | episodio | pista)”.
onSeekTo()

"Reiniciar"

"Avanza ## segundos".

"Volver ## minutos".

N/A (mantener actualizado su MediaMetadata) "¿Qué está sonando?"

Con errores

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, como se describe en Cómo trabajar con una sesión multimedia. Asistente reconoce todos los códigos de error que muestra getErrorCode().

Casos comúnmente mal manejados

Estos son algunos ejemplos de casos de error que debes asegurarte de manejar de forma correcta:

  • El usuario debe acceder.
    • Establece el código de error PlaybackState en ERROR_CODE_AUTHENTICATION_EXPIRED.
    • Establece el mensaje de error PlaybackState.
    • Si es necesario para la reproducción, establece el estado PlaybackState en STATE_ERROR. De lo contrario, conserva el resto de PlaybackState tal como está.
  • El usuario solicita una acción no disponible.
    • Establece el código de error PlaybackState de manera correcta. Por ejemplo, establece PlaybackState en ERROR_CODE_NOT_SUPPORTED si la acción no es compatible o en ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED si la acción está protegida por el acceso.
    • Establece el mensaje de error PlaybackState.
    • Conserva el resto de PlaybackState tal como está.
  • El usuario solicita contenido que no está disponible en la app
    • Establece el código de error PlaybackState de manera correcta. Por ejemplo, usa ERROR_CODE_NOT_AVAILABLE_IN_REGION.
    • Establece el mensaje de error PlaybackState.
    • Establece el estado PlaybackSate en STATE_ERROR para interrumpir la reproducción; de lo contrario, conserva el resto de PlaybackState tal como está.
  • El usuario solicita contenido para el que no hay una concordancia exacta disponible. Por ejemplo, un usuario de nivel gratuito que solicita contenido que solo está disponible para usuarios de nivel premium.
    • Te recomendamos que no muestres un error y, en su lugar, priorices encontrar algo similar para reproducir. Asistente se encargará de decir la respuesta de voz más relevante antes de que comience la reproducción.

Reproducción con un intent

Para iniciar una app de audio o video, Asistente puede enviar un intent con un vínculo directo y comenzar la reproducción.

El intent y su vínculo directo pueden provenir de distintas fuentes:

  • Cuando Asistente inicia una app para dispositivos móviles, puede usar la Búsqueda de Google para recuperar contenido marcado que proporciona una acción de visualización con un vínculo.
  • Cuando Asistente inicia una app para TV, esta debe incluir un proveedor de búsqueda de TV para exponer los URI de contenido multimedia. Asistente envía una consulta al proveedor de contenido, que 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, Asistente agregará ACTION_VIEW al intent.

Asistente agrega el EXTRA_START_PLAYBACK adicional con el valor true al intent que envía a tu app. Esta debe 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 la app sigue reproduciendo 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 controlar las solicitudes nuevas.

Cuando inicia la reproducción, Asistente puede agregar marcas adicionales al intent que envía a tu app. En particular, puede agregar FLAG_ACTIVITY_CLEAR_TOP, FLAG_ACTIVITY_NEW_TASK o ambos. Si bien tu código no necesita manejar estas marcas, el sistema Android responde a ellas. Esto podría afectar el comportamiento de tu app cuando llega una segunda solicitud de reproducción con un URI nuevo mientras se está reproduciendo el URI anterior. Recomendamos probar cómo responde tu app en este caso. Puedes usar la herramienta de línea de comandos de adb para simular la situación (la constante 0x14000000 es el OR a nivel de bits 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 media browser service que permite conexiones del Asistente, este puede comunicarse con el media session del servicio para iniciar la app. El servicio de exploración multimedia nunca debe iniciar una actividad. Asistente iniciará tu actividad en función del elemento PendingIntent que definas con setSessionActivity().

Asegúrate de configurar MediaSession.Token cuando inicialices el servicio de navegador multimedia. Recuerda configurar las acciones de reproducción admitidas en todo momento, incluso durante la inicialización. Asistente espera que tu app de música configure las acciones de reproducción antes de que 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 muestra el orden de las llamadas que genera Asistente y las devoluciones de llamada de sesión multimedia correspondientes. (Las devoluciones de llamada de preparación se envían solo si tu app las admite). Todas las llamadas son asíncronas. Asistente no espera ninguna respuesta de tu app.

Cómo iniciar la reproducción con una sesión multimedia

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 ACTION_PREPARE_*, Asistente llamará a la acción PREPARE antes de iniciar el anuncio.

Cómo establecer conexión con un MediaBrowserService

A fin de usar un servicio para iniciar tu app, Asistente debe poder conectarse al MediaBrowserService de la app y recuperar su MediaSession.Token. Las solicitudes de conexión se controlan 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>