L'Assistant Google vous permet d'utiliser des commandes vocales pour contrôler de nombreux appareils, comme Google Home, votre téléphone, etc. Il est doté d'une fonctionnalité intégrée pour comprendre les commandes multimédias ("Mets du contenu de Beyoncé") et prend en charge les commandes multimédias (pause, passer, avance rapide, pouce vers le haut, etc.).
L'Assistant communique avec les applications multimédias Android à l'aide d'une session multimédia. Elle peut utiliser des intents ou des services pour lancer votre application et lancer la lecture. Pour des résultats optimaux, votre application doit implémenter toutes les fonctionnalités décrites sur cette page.
Utiliser une session multimédia
Chaque application audio et vidéo doit implémenter une session multimédia afin que l'Assistant puisse utiliser les commandes de transport une fois la lecture commencée.
Bien que l'Assistant n'utilise que les actions listées dans cette section, il est recommandé d'implémenter toutes les API de préparation et de lecture pour assurer la compatibilité avec les autres applications. Pour toutes les actions non compatibles, les rappels de session multimédia peuvent simplement renvoyer une erreur à l'aide de ERROR_CODE_NOT_SUPPORTED
.
Activez les commandes multimédias et de transport en définissant ces indicateurs dans l'objet MediaSession
de votre application:
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 session multimédia de votre application doit déclarer les actions compatibles et implémenter les rappels de session multimédia correspondants. Déclarez les actions compatibles dans setActions()
.
L'exemple de projet Universal Android Music Player constitue un bon exemple de configuration d'une session multimédia.
Actions de lecture
Pour lancer la lecture à partir d'un service, une session multimédia doit comporter les actions PLAY
et leurs rappels:
Action | Rappel |
---|---|
ACTION_PLAY |
onPlay() |
ACTION_PLAY_FROM_SEARCH |
onPlayFromSearch() |
ACTION_PLAY_FROM_URI (*) |
onPlayFromUri() |
Votre session doit également implémenter ces actions PREPARE
et leurs rappels:
Action | Rappel |
---|---|
ACTION_PREPARE |
onPrepare() |
ACTION_PREPARE_FROM_SEARCH |
onPrepareFromSearch() |
ACTION_PREPARE_FROM_URI (*) |
onPrepareFromUri() |
En implémentant les API de préparation, vous pouvez réduire la latence de lecture après une commande vocale. Les applications multimédias qui souhaitent améliorer la latence de lecture peuvent utiliser le temps supplémentaire pour commencer à mettre en cache le contenu et à préparer la lecture des contenus multimédias.
Analyser les requêtes de recherche
Lorsqu'un utilisateur recherche un élément multimédia spécifique, comme Mets du jazz sur [nom de votre application] ou Écouter [titre du titre], la méthode de rappel onPrepareFromSearch()
ou onPlayFromSearch()
reçoit un paramètre de requête et un bundle d'extras.
Votre application doit analyser la requête de recherche vocale et lancer la lecture en procédant comme suit:
- Utilisez le bundle d'extras et la chaîne de requête de recherche renvoyés par la recherche vocale pour filtrer les résultats.
- Créez une file d'attente de lecture en fonction de ces résultats.
- Lire l'élément multimédia le plus pertinent parmi les résultats
La méthode onPlayFromSearch()
utilise un paramètre extras avec des informations plus détaillées issues de la recherche vocale. Ces éléments supplémentaires vous aident à trouver le contenu audio dans votre application à lire.
Si les résultats de recherche ne peuvent pas fournir ces données, vous pouvez mettre en œuvre une logique pour analyser la requête de recherche brute et lire les pistes appropriées en fonction de la requête.
Les extras suivants sont compatibles avec Android Automotive OS et Android Auto:
L'extrait de code suivant montre comment remplacer la méthode onPlayFromSearch()
dans votre implémentation de MediaSession.Callback
pour analyser la requête de recherche vocale et lancer la lecture:
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 } }
Pour obtenir un exemple plus détaillé d'implémentation de la recherche vocale afin de lire du contenu audio dans votre application, consultez l'exemple Universal Android Music Player.
Gérer les requêtes vides
Si onPrepare()
, onPlay()
, onPrepareFromSearch()
ou onPlayFromSearch()
sont appelés sans requête de recherche, votre application multimédia doit lire le contenu multimédia "actuel". Si aucun contenu multimédia n'est en cours de lecture, l'application doit essayer de lire quelque chose, comme un titre de la playlist la plus récente ou une file d'attente aléatoire. L'assistant utilise ces API lorsqu'un utilisateur demande "Mets de la musique sur [nom de votre application]" sans informations supplémentaires.
Lorsqu'un utilisateur dit Mets de la musique sur [nom de votre application], Android Automotive OS ou Android Auto tente de lancer votre application et de lire du contenu audio en appelant la méthode onPlayFromSearch()
de votre application. Toutefois, comme l'utilisateur n'a pas prononcé le nom de l'élément multimédia, la méthode onPlayFromSearch()
reçoit un paramètre de requête vide. Dans ce cas, votre application doit répondre en lisant immédiatement du contenu audio, par exemple un titre de la playlist la plus récente ou une file d'attente aléatoire.
Déclarer l'ancienne compatibilité des commandes vocales
Dans la plupart des cas, la gestion des actions de lecture décrites ci-dessus donne à votre application toutes les fonctionnalités de lecture dont elle a besoin. Cependant, certains systèmes nécessitent que votre application contienne un filtre d'intent pour la recherche. Vous devez déclarer la prise en charge de ce filtre d'intent dans les fichiers manifestes de votre application.
Incluez ce code dans le fichier manifeste d'une application pour téléphone:
<activity>
<intent-filter>
<action android:name=
"android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<category android:name=
"android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Commandes de transport
Une fois la session multimédia de votre application active, l'Assistant peut émettre des commandes vocales pour contrôler la lecture et mettre à jour les métadonnées multimédias. Pour que cela fonctionne, votre code doit activer les actions suivantes et implémenter les rappels correspondants:
Action | Rappel | Description |
---|---|---|
ACTION_SKIP_TO_NEXT |
onSkipToNext() |
Vidéo suivante |
ACTION_SKIP_TO_PREVIOUS |
onSkipToPrevious() |
Titre précédent |
ACTION_PAUSE, ACTION_PLAY_PAUSE |
onPause() |
Suspendre |
ACTION_STOP |
onStop() |
Arrêter |
ACTION_PLAY |
onPlay() |
Réactiver |
ACTION_SEEK_TO |
onSeekTo() |
Revenir en arrière de 30 secondes |
ACTION_SET_RATING |
onSetRating(android.support.v4.media.RatingCompat) |
J'aime/Je n'aime pas. |
ACTION_SET_CAPTIONING_ENABLED |
onSetCaptioningEnabled(boolean) |
activer ou désactiver les sous-titres ; |
Remarque :
- Pour que les commandes de recherche fonctionnent,
PlaybackState
doit être à jour avecstate, position, playback speed, and update time
. L'application doit appelersetPlaybackState()
lorsque l'état change. - L'application multimédia doit également maintenir les métadonnées de la session multimédia à jour. Cela permet d'obtenir des réponses à des questions telles que "Quelle est la chanson qui est diffusée ?" L'application doit appeler
setMetadata()
lorsque les champs applicables (comme le titre, l'artiste et le nom du morceau) changent. MediaSession.setRatingType()
doit être défini pour indiquer le type de classification accepté par l'application, et celle-ci doit implémenteronSetRating()
. Si l'application ne prend pas en charge la classification, définissez le type de classification surRATING_NONE
.
Les commandes vocales acceptées varient probablement selon le type de contenu.
Type de contenu | Actions requises |
---|---|
Musique |
Compatibilité requise : "Lire", "Mettre en pause", "Arrêter", "Passer à l'élément suivant" et "Passer à la page précédente" Recommander fortement l'assistance pour: Rechercher |
Podcast |
Compatibilité requise: Lire, Mettre en pause, Arrêter et Aller à Recommander l'assistance pour : "Passer à l'élément suivant" et "Passer à l'élément précédent" |
Livre audio | Compatibilité requise: Lire, Mettre en pause, Arrêter et Aller à |
Radio | Compatibilité requise : "Lire", "Mettre en pause" et "Arrêter" |
Actualités | Compatibilité requise : "Lire", "Mettre en pause", "Arrêter", "Passer à l'élément suivant" et "Passer à la page précédente" |
Vidéo |
Compatibilité requise : "Lecture", "Pause", "Arrêt", "Avance rapide", "Retour rapide" et "Avance rapide" Assistance vivement recommandée : "Passer à l'élément suivant" et "Passer à l'élément précédent" |
Vous devez prendre en charge autant d'actions listées ci-dessus que votre offre de produits le permet, mais réagir de manière appropriée pour toute autre action. Par exemple, si seuls les utilisateurs Premium ont la possibilité de revenir à l'élément précédent, vous pouvez générer une erreur si un utilisateur du niveau sans frais demande à l'Assistant de revenir à l'élément précédent. Pour en savoir plus, consultez la section Gestion des erreurs.
Exemples de requêtes vocales à essayer
Le tableau suivant présente quelques exemples de requêtes à utiliser pour tester votre implémentation:
Rappel MediaSession | Commande "Hey Google" à utiliser | |
---|---|---|
onPlay() |
"Lance la lecture." "Reprends la lecture". |
|
onPlayFromSearch()
onPlayFromUri() |
Musique |
"Mets de la musique ou des chansons sur (nom de l'application)." Cette requête est vide. "Mets (titre | artiste | album | genre | playlist) sur (nom de l'application)." |
Radio | "Mets (fréquence | station) sur (nom de l'application)." | |
Audiobook |
"Lis mon livre audio sur (nom de l'application)." "Lis (livre audio) sur (nom de l'application)." |
|
Podcasts | "Mets (podcast) sur (nom de l'application)." | |
onPause() |
"Mets sur pause." | |
onStop() |
"Arrête la lecture." | |
onSkipToNext() |
"Suivant (titre | épisode | titre)." | |
onSkipToPrevious() |
"Précédent (titre | épisode | titre)." | |
onSeekTo() |
"Recommencer". "Avance de ## secondes." "Reviens en arrière de ## minutes." |
|
N/A (Maintenez à jour votre MediaMetadata ) |
"Qu'est-ce qui passe actuellement ?" |
Erreurs
L'Assistant gère les erreurs d'une session multimédia lorsqu'elles se produisent et les signale aux utilisateurs. Assurez-vous que votre session multimédia met correctement à jour l'état de transport et le code d'erreur dans son PlaybackState
, comme décrit dans la section Utiliser une session multimédia. L'Assistant reconnaît tous les codes d'erreur renvoyés par getErrorCode()
.
Cas souvent mal gérés
Voici quelques exemples de cas d'erreur que vous devez vous assurer de les gérer correctement:
- L'utilisateur doit se connecter.
- Définissez le code d'erreur
PlaybackState
surERROR_CODE_AUTHENTICATION_EXPIRED
. - Définissez le message d'erreur
PlaybackState
. - Si nécessaire pour la lecture, définissez l'état de
PlaybackState
surSTATE_ERROR
. Sinon, conservez le reste dePlaybackState
tel quel.
- Définissez le code d'erreur
- L'utilisateur demande une action indisponible.
- Définissez correctement le code d'erreur
PlaybackState
. Par exemple, définissezPlaybackState
surERROR_CODE_NOT_SUPPORTED
si l'action n'est pas compatible ou surERROR_CODE_PREMIUM_ACCOUNT_REQUIRED
si elle est protégée par une connexion. - Définissez le message d'erreur
PlaybackState
. - Conservez le reste des
PlaybackState
en l'état.
- Définissez correctement le code d'erreur
- L'utilisateur demande à voir du contenu non disponible dans l'application.
- Définissez correctement le code d'erreur
PlaybackState
. Par exemple, utilisezERROR_CODE_NOT_AVAILABLE_IN_REGION
. - Définissez le message d'erreur
PlaybackState
. - Définissez l'état
PlaybackSate
surSTATE_ERROR
pour interrompre la lecture. Sinon, vous pouvez conserver le reste dePlaybackState
tel quel.
- Définissez correctement le code d'erreur
- L'utilisateur demande un contenu pour lequel aucune correspondance exacte n'est disponible. Par exemple, un utilisateur de la version sans frais demandant du contenu réservé aux utilisateurs du niveau Premium.
- Nous vous recommandons de ne pas renvoyer d'erreur et de privilégier plutôt un jeu semblable à celui-ci. L'Assistant se chargera de prononcer la réponse vocale la plus pertinente avant le début de la lecture.
Lecture avec un intent
L'Assistant peut lancer une application audio ou vidéo et commencer la lecture en envoyant un intent avec un lien profond.
L'intent et son lien profond peuvent provenir de différentes sources:
- Lorsque l'Assistant démarre une application mobile, il peut utiliser la recherche Google pour récupérer le contenu balisé qui fournit une action de visionnage avec un lien.
- Lorsque l'Assistant démarre une application TV, celle-ci doit inclure un fournisseur de recherche TV pour exposer les URI du contenu multimédia. L'Assistant envoie une requête au fournisseur de contenu, qui doit renvoyer un intent contenant un URI pour le lien profond et une action facultative.
Si la requête renvoie une action dans l'intent, l'Assistant renvoie cette action et l'URI à votre application. Si le fournisseur n'a spécifié aucune action, l'Assistant ajoute
ACTION_VIEW
à l'intent.
L'Assistant ajoute le EXTRA_START_PLAYBACK
supplémentaire avec la valeur true
à l'intent qu'il envoie à votre application. Votre application doit lancer la lecture lorsqu'elle reçoit un intent avec EXTRA_START_PLAYBACK
.
Gérer les intents actifs
Les utilisateurs peuvent demander à l'Assistant de lire quelque chose pendant que votre application lit le contenu d'une requête précédente. Cela signifie que votre application peut recevoir de nouveaux intents pour lancer la lecture lorsque son activité de lecture est déjà lancée et active.
Les activités qui acceptent les intents avec des liens profonds doivent remplacer onNewIntent()
pour gérer les nouvelles requêtes.
Lors du démarrage de la lecture, l'Assistant peut ajouter des indicateurs supplémentaires à l'intent qu'il envoie à votre application. En particulier, il peut ajouter FLAG_ACTIVITY_CLEAR_TOP
, FLAG_ACTIVITY_NEW_TASK
ou les deux. Bien que votre code n'ait pas besoin de gérer ces indicateurs, le système Android y répond.
Cela peut affecter le comportement de votre application lorsqu'une deuxième requête de lecture avec un nouvel URI arrive alors que l'URI précédent est toujours en cours de lecture. Nous vous recommandons de tester la réaction de votre application dans ce cas. Vous pouvez utiliser l'outil de ligne de commande adb
pour simuler la situation (la constante 0x14000000
est la valeur booléenne OR au niveau du bit des deux indicateurs):
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
Lecture depuis un service
Si votre application dispose d'un media browser service
qui autorise les connexions depuis l'Assistant, celui-ci peut démarrer l'application en communiquant avec l'media session
du service.
Le service de navigateur multimédia ne doit jamais lancer d'activité.
L'Assistant lancera votre activité en fonction du PendingIntent
que vous définissez avec setSessionActivity().
Veillez à définir le jeton MediaSession.Token lorsque vous initialisez le service de navigateur multimédia. N'oubliez pas de définir les actions de lecture compatibles à tout moment, y compris lors de l'initialisation. L'Assistant s'attend à ce que votre application multimédia définisse les actions de lecture avant qu'il n'envoie la première commande de lecture.
Pour commencer à partir d'un service, l'Assistant implémente les API clientes du navigateur multimédia. Elle effectue des appels TransportControls qui déclenchent des rappels d'action de lecture sur la session multimédia de votre application.
Le schéma suivant montre l'ordre des appels générés par l'Assistant et les rappels de session multimédia correspondants. (Les rappels de préparation ne sont envoyés que si votre application les prend en charge.) Tous les appels sont asynchrones. L'Assistant n'attend pas de réponse de votre application.
Lorsqu'un utilisateur émet une commande vocale, l'Assistant répond par une brève annonce. Dès que l'annonce est terminée, l'Assistant émet une action de lecture. Il n'attend pas d'état de lecture spécifique.
Si votre application est compatible avec les actions ACTION_PREPARE_*
, l'Assistant appelle l'action PREPARE
avant de lancer l'annonce.
Se connecter à MediaBrowserService
Pour utiliser un service afin de démarrer votre application, l'Assistant doit pouvoir se connecter au MediaBrowserService de l'application et récupérer son MediaSession.Token. Les requêtes de connexion sont traitées dans la méthode onGetRoot()
du service. Il existe deux façons de gérer les requêtes:
- Accepter toutes les demandes de connexion
- Accepter uniquement les demandes de connexion de l'application Assistant
Accepter toutes les demandes de connexion
Vous devez renvoyer un BrowserRoot pour autoriser l'Assistant à envoyer des commandes à votre session multimédia. Le moyen le plus simple consiste à autoriser toutes les applications MediaBrowser à se connecter à MediaBrowserService. Vous devez renvoyer un BrowserRoot non nul. Voici le code applicable provenant du lecteur de musique Universal:
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... }
Accepter le package et la signature de l'application Assistant
Vous pouvez autoriser explicitement l'Assistant à se connecter à votre service de navigateur multimédia en recherchant le nom et la signature du package. Votre application recevra le nom du package dans la méthode onGetRoot de MediaBrowserService. Vous devez renvoyer un BrowserRoot pour autoriser l'Assistant à envoyer des commandes à votre session multimédia. L'exemple Universal Music Player gère une liste des noms de packages et des signatures connus. Vous trouverez ci-dessous les noms de packages et les signatures utilisés par l'Assistant 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>