Assistant Google et applications multimédias

L'Assistant Google vous permet d'utiliser des commandes vocales pour contrôler de nombreux appareils, comme Google Home, votre téléphone et plus encore. Il intègre une fonctionnalité permettant de comprendre les commandes multimédias ("mets un titre de Beyoncé") et est compatible avec les commandes multimédias (comme mettre en pause, passer, avancer, J'aime).

L'Assistant communique avec les applications multimédias Android à l'aide d'une session multimédia. Il peut utiliser des intents ou des services pour lancer votre application et démarrer la lecture. Pour obtenir les meilleurs résultats, 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 lancée.

Notez que, 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 d'autres applications. Pour toute action que vous ne prenez pas en charge, 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 qu'elle prend en charge et implémenter les rappels de session multimédia correspondants. Déclarez les actions acceptées dans setActions().

L'exemple de projet Universal Android Music Player est un bon exemple de configuration d'une session multimédia.

Actions de lecture

Pour démarrer la lecture à partir d'un service, une session multimédia doit comporter les actions PLAY et leurs rappels suivants :

Action Rappel
ACTION_PLAY onPlay()
ACTION_PLAY_FROM_SEARCH onPlayFromSearch()
ACTION_PLAY_FROM_URI (*) onPlayFromUri()

Votre session doit également implémenter les actions PREPARE et leurs rappels suivants :

Action Rappel
ACTION_PREPARE onPrepare()
ACTION_PREPARE_FROM_SEARCH onPrepareFromSearch()
ACTION_PREPARE_FROM_URI (*) onPrepareFromUri()

(*) Les actions basées sur les URI de l'Assistant Google ne fonctionnent que pour les entreprises qui fournissent des URI à Google. Pour en savoir plus sur la description de vos contenus multimédias à Google, consultez Actions multimédias.

En implémentant les API de préparation, la latence de lecture après une commande vocale peut être réduite. Les applications multimédias qui souhaitent améliorer la latence de lecture peuvent utiliser ce temps supplémentaire pour commencer à mettre en cache le contenu et à préparer la lecture du contenu multimédia.

Analyser les requêtes de recherche

Lorsqu'un utilisateur recherche un élément multimédia spécifique, comme "Écoute du jazz sur [nom de votre application]" ou "Écoute [titre de la chanson]", 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 suivant ces étapes :

  1. 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.
  2. Créez une file de lecture à partir de ces résultats.
  3. lire l'élément multimédia le plus pertinent parmi les résultats ;

La méthode onPlayFromSearch() accepte un paramètre "extras" contenant des informations plus détaillées sur la recherche vocale. Ces extras vous aident à trouver le contenu audio dans votre application pour la lecture. Si les résultats de recherche ne peuvent pas fournir ces données, vous pouvez implémenter 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 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é de l'implémentation de la recherche vocale pour 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". S'il n'y a pas de contenu multimédia en cours de lecture, l'application doit essayer d'en lire un, par exemple 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 à lire de la musique sur [nom de votre application] sans informations supplémentaires.

Lorsqu'un utilisateur dit "Lire de la musique sur [nom de votre application]", Android Automotive OS ou Android Auto tente de lancer votre application et de lire l'audio en appelant la méthode onPlayFromSearch() de votre application. Toutefois, comme l'utilisateur n'a pas indiqué 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 un contenu audio, comme un titre de la playlist la plus récente ou une file d'attente aléatoire.

Déclarer la prise en charge des anciennes commandes vocales

Dans la plupart des cas, la gestion des actions de lecture décrites ci-dessus fournit à votre application toutes les fonctionnalités de lecture dont elle a besoin. Cependant, certains systèmes exigent 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.

Ajoutez 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 permettre 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() Pause
ACTION_STOP onStop() Arrêter
ACTION_PLAY onPlay() Reprendre
ACTION_SEEK_TO onSeekTo() Revenir en arrière de 30 secondes
ACTION_SET_RATING onSetRating(android.support.v4.media.RatingCompat) Pouce vers le haut/bas.
ACTION_SET_CAPTIONING_ENABLED onSetCaptioningEnabled(boolean) Activez ou désactivez les sous-titres.

Remarque :

  • Pour que les commandes de recherche fonctionnent, PlaybackState doit être à jour avec state, position, playback speed, and update time. L'application doit appeler setPlaybackState() lorsque l'état change.
  • L'application multimédia doit également tenir à jour les métadonnées de la session multimédia. Vous pouvez ainsi poser des questions comme "Quelle est cette chanson ?". L'application doit appeler setMetadata() lorsque les champs applicables (tels que le titre, l'artiste et le nom du titre) changent.
  • MediaSession.setRatingType() doit être défini pour indiquer le type de classification pris en charge par l'application, et l'application doit implémenter onSetRating(). Si l'application n'est pas compatible avec les notes, elle doit définir le type de note sur RATING_NONE.

Les actions vocales que vous acceptez varient probablement en fonction du type de contenu.

Type de contenu Actions requises
Musique

Doit être compatible avec : lecture, pause, arrêt, passer au titre suivant et passer au titre précédent

Prise en charge fortement recommandée : Seek To

Podcast

Doit être compatible avec : Lecture, Pause, Arrêt et Aller à

Recommandations d'assistance pour : Passer à la piste suivante et Passer à la piste précédente

Livre audio Doit être compatible avec : Lecture, Pause, Arrêt et Aller à
Radio Doit être compatible avec : Lecture, Pause et Arrêt
Actualités Doit être compatible avec : lecture, pause, arrêt, passer au titre suivant et passer au titre précédent
Vidéo

Doit être compatible avec : lecture, pause, arrêt, recherche, retour arrière et avance rapide

Prise en charge fortement recommandée : Passer à la piste suivante et Passer à la piste précédente

Vous devez prendre en charge autant d'actions listées ci-dessus que vos offres de produits le permettent, tout en répondant de manière appropriée à toute autre action. Par exemple, si seuls les utilisateurs Premium peuvent revenir à l'élément précédent, vous pouvez générer une erreur si un utilisateur du forfait sans frais demande à l'Assistant de revenir à l'élément précédent. Pour obtenir plus d'informations, consultez la section sur la gestion des erreurs.

Exemples de requêtes vocales à essayer

Le tableau suivant présente quelques exemples de requêtes que vous devez utiliser pour tester votre implémentation :

Rappel MediaSession Phrase "Hey Google" à utiliser
onPlay()

"Jouer."

"Reprends la lecture."

onPlayFromSearch()
onPlayFromUri()
Musique

"Mets de la musique ou des titres sur (nom de l'application)." Il s'agit d'une requête vide.

"Mets (chanson | 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 en pause."
onStop() "Arrête."
onSkipToNext() "Suivant (titre | épisode | piste)"
onSkipToPrevious() "Précédent (titre | épisode | piste)"
onSeekTo()

"Redémarre."

"Avance de ## secondes."

"Reviens en arrière de ## minutes."

N/A (maintenez votre MediaMetadata à jour) "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 à jour correctement l'état du transport et le code d'erreur dans son PlaybackState, comme décrit dans 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 gérer correctement :

  • L'utilisateur doit se connecter
    • Définissez le code d'erreur PlaybackState sur ERROR_CODE_AUTHENTICATION_EXPIRED.
    • Définissez le message d'erreur PlaybackState.
    • Si l'état PlaybackState est nécessaire pour la lecture, définissez-le sur STATE_ERROR. Sinon, conservez le reste de l'état PlaybackState tel quel.
  • L'utilisateur demande une action non disponible.
    • Définissez le code d'erreur PlaybackState de manière appropriée. Par exemple, définissez PlaybackState sur ERROR_CODE_NOT_SUPPORTED si l'action n'est pas prise en charge ou sur ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED si l'action est protégée par une connexion.
    • Définissez le message d'erreur PlaybackState.
    • Conservez le reste de PlaybackState tel quel.
  • L'utilisateur demande du contenu non disponible dans l'application
    • Définissez le code d'erreur PlaybackState de manière appropriée. Par exemple, utilisez ERROR_CODE_NOT_AVAILABLE_IN_REGION.
    • Définissez le message d'erreur PlaybackState.
    • Définissez l'état PlaybackSate sur STATE_ERROR pour interrompre la lecture, sinon conservez le reste de PlaybackState tel quel.
  • L'utilisateur demande du contenu pour lequel aucune correspondance exacte n'est disponible. Par exemple, un utilisateur du forfait sans frais demande du contenu réservé aux utilisateurs du forfait Premium.
    • Nous vous recommandons de ne pas renvoyer d'erreur, mais plutôt de trouver un contenu similaire à lire. 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 démarrer la lecture en envoyant un intent avec un lien profond.

L'intention 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 du contenu balisé qui fournit une action de visionnage avec un lien.
  • Lorsque l'Assistant démarre une application TV, votre application doit inclure un fournisseur de recherche TV pour exposer les URI des contenus multimédias. 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 pas spécifié d'action, l'Assistant ajoutera ACTION_VIEW à l'intent.

L'Assistant ajoute l'extra EXTRA_START_PLAYBACK avec la valeur true à l'intention qu'il envoie à votre application. Votre application doit lancer la lecture lorsqu'elle reçoit une intention avec EXTRA_START_PLAYBACK.

Gérer les intents en mode actif

Les utilisateurs peuvent demander à l'Assistant de lire un contenu alors que votre application est en train de lire un contenu provenant d'une requête précédente. Cela signifie que votre application peut recevoir de nouveaux intents pour démarrer la lecture alors que 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.

Lorsque la lecture commence, l'Assistant peut ajouter des indicateurs supplémentaires à l'intention qu'il envoie à votre application. Il peut notamment ajouter FLAG_ACTIVITY_CLEAR_TOP ou 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 demande de lecture avec un nouvel URI arrive alors que l'URI précédent est toujours en cours de lecture. Il est recommandé 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 le OU bit à bit booléen 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 à partir d'un service

Si votre application possède un media browser service qui autorise les connexions depuis l'Assistant, celui-ci peut démarrer l'application en communiquant avec le media session du service. Le service de navigateur multimédia ne doit jamais lancer d'activité. L'Assistant lancera votre activité en fonction de la PendingIntent que vous définissez avec setSessionActivity().

Veillez à définir 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 démarrer à 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 PLAY sur la session multimédia de votre application.

Le diagramme 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 aucune réponse de votre application.

Démarrer la lecture avec une session multimédia

Lorsqu'un utilisateur émet une commande vocale pour lancer la lecture, l'Assistant répond par une brève annonce. Dès que l'annonce est terminée, l'Assistant émet une action PLAY. Elle n'attend aucun état de lecture spécifique.

Si votre application est compatible avec les actions ACTION_PREPARE_*, l'Assistant appelle l'action PREPARE avant de commencer l'annonce.

Se connecter à un 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 demandes de connexion sont traitées dans la méthode onGetRoot() du service. Il existe deux façons de traiter les demandes :

  • Accepter toutes les demandes de connexion
  • Accepter les demandes de connexion uniquement depuis l'application Assistant

Accepter toutes les demandes de connexion

Vous devez renvoyer un BrowserRoot pour permettre à l'Assistant d'envoyer des commandes à votre session multimédia. La méthode la plus simple consiste à autoriser toutes les applications MediaBrowser à se connecter à votre MediaBrowserService. Vous devez renvoyer un BrowserRoot non nul. Voici le code applicable de 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...
}

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 vérifiant son nom de package et sa signature. Votre application recevra le nom du package dans la méthode onGetRoot de votre MediaBrowserService. Vous devez renvoyer un BrowserRoot pour permettre à l'Assistant d'envoyer des commandes à votre session multimédia. L'exemple Universal Music Player gère une liste de noms de packages et de signatures connus. Vous trouverez ci-dessous les noms de package 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>