L'Assistente Google ti consente di usare i comandi vocali per controllare tanti dispositivi, come Google Home, il tuo telefono e altri ancora. Ha una funzionalità integrata di comprensione dei comandi multimediali ("riproduci qualcosa di Beyoncé") e supporta i controlli multimediali (come pausa, salto, avanti veloce, Mi piace).
L'assistente comunica con le app multimediali Android utilizzando una sessione multimediale. Può utilizzare intent o servizi per avviare la tua app e avviare la riproduzione. Per ottenere risultati ottimali, l'app deve implementare tutte le funzionalità descritte in questa pagina.
Utilizzare una sessione multimediale
Ogni app audio e video deve implementare una sessione multimediale in modo che l'assistente possa utilizzare i controlli di trasporto dopo l'avvio della riproduzione.
Tieni presente che sebbene l'assistente utilizzi solo le azioni elencate in questa sezione, la best practice prevede l'implementazione di tutte le API di preparazione e riproduzione per garantire la compatibilità con altre applicazioni. Per le azioni non supportate, i callback delle sessioni multimediali possono semplicemente restituire un errore utilizzando ERROR_CODE_NOT_SUPPORTED
.
Attiva i controlli multimediali e di trasporto impostando questi flag nell'oggetto MediaSession
della tua 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 sessione multimediale dell'app deve dichiarare le azioni supportate e implementare i callback corrispondenti della sessione multimediale. Dichiara le azioni supportate in setActions()
.
Il progetto di esempio del player di musica universale per Android è un buon esempio di come configurare una sessione multimediale.
Azioni di riproduzione
Per avviare la riproduzione da un servizio, una sessione multimediale deve avere le seguenti azioni PLAY
e i relativi callback:
Azione | Richiamata |
---|---|
ACTION_PLAY |
onPlay() |
ACTION_PLAY_FROM_SEARCH |
onPlayFromSearch() |
ACTION_PLAY_FROM_URI (*) |
onPlayFromUri() |
La sessione deve implementare anche queste azioni PREPARE
e i relativi callback:
Azione | Richiamata |
---|---|
ACTION_PREPARE |
onPrepare() |
ACTION_PREPARE_FROM_SEARCH |
onPrepareFromSearch() |
ACTION_PREPARE_FROM_URI (*) |
onPrepareFromUri() |
Con l'implementazione delle API di preparazione, è possibile ridurre la latenza di riproduzione dopo un comando vocale. Le app multimediali che desiderano migliorare la latenza di riproduzione possono impiegare il tempo aggiuntivo per avviare la memorizzazione nella cache dei contenuti e preparare la riproduzione dei contenuti multimediali.
Analizzare le query di ricerca
Quando un utente cerca un elemento multimediale specifico, ad esempio "Fammi ascoltare musica jazz su [nome dell'app]" o "Ascolta [titolo del brano]", il metodo di callback onPrepareFromSearch()
o onPlayFromSearch()
riceve un parametro di ricerca e un bundle extra.
L'app deve analizzare la query di ricerca vocale e avviare la riproduzione procedendo nel seguente modo:
- Usa il gruppo extra e la stringa di query di ricerca restituite dalla ricerca vocale per filtrare i risultati.
- Crea una coda di riproduzione basata su questi risultati.
- Riproduci l'elemento multimediale più pertinente dai risultati.
Il metodo onPlayFromSearch()
utilizza un parametro extra con informazioni più dettagliate dalla ricerca vocale. Questi extra ti aiutano a trovare i contenuti audio nella tua app per la riproduzione.
Se i risultati di ricerca non sono in grado di fornire questi dati, puoi implementare la logica per analizzare la query di ricerca non elaborata e riprodurre i canali appropriati in base alla query.
I seguenti extra sono supportati nel sistema operativo Android Automotive e Android Auto:
Il seguente snippet di codice mostra come eseguire l'override del metodo onPlayFromSearch()
nell'implementazione MediaSession.Callback
per analizzare la query di ricerca vocale e avviare la riproduzione:
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 } }
Per un esempio più dettagliato su come implementare la ricerca vocale per riprodurre contenuti audio nell'app, vedi l'esempio di Universal Android Music Player.
Gestire le query vuote
Se onPrepare()
, onPlay()
, onPrepareFromSearch()
o onPlayFromSearch()
vengono chiamati senza una query di ricerca, l'app multimediale dovrebbe riprodurre il contenuto multimediale "corrente". Se non sono disponibili contenuti multimediali, l'app dovrebbe provare a riprodurre qualcosa, ad esempio
un brano della playlist più recente o una coda casuale. L'assistente utilizza
queste API quando un utente chiede di "ascoltare musica su [nome dell'app]" senza
ulteriori informazioni.
Quando un utente dice "Fammi ascoltare musica su [nome dell'app]", il sistema operativo Android Automotive o Android Auto tenta di avviare l'app e riprodurre l'audio chiamando il metodo onPlayFromSearch()
dell'app. Tuttavia, poiché l'utente non ha pronunciato il nome dell'elemento multimediale, il metodo onPlayFromSearch()
riceve un parametro di query vuoto. In questi casi, l'app dovrebbe
rispondere riproducendo l'audio immediatamente, ad esempio un brano della playlist più recente
o una coda casuale.
Dichiara il supporto precedente per le azioni vocali
Nella maggior parte dei casi, la gestione delle azioni di riproduzione descritte sopra fornisce all'app tutte le funzionalità di riproduzione necessarie. Tuttavia, alcuni sistemi richiedono che l'app contenga un filtro per intent per la ricerca. Devi dichiarare il supporto di questo filtro per intent nei file manifest dell'app.
Includi questo codice nel file manifest per un'app per smartphone:
<activity>
<intent-filter>
<action android:name=
"android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<category android:name=
"android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Controlli di trasporto
Dopo aver attivato la sessione multimediale dell'app, l'assistente può inviare comandi vocali per controllare la riproduzione e aggiornare i metadati dei contenuti multimediali. Affinché funzioni, il codice deve abilitare le seguenti azioni e implementare i callback corrispondenti:
Azione | Richiamata | Descrizione |
---|---|---|
ACTION_SKIP_TO_NEXT |
onSkipToNext() |
Video successivo |
ACTION_SKIP_TO_PREVIOUS |
onSkipToPrevious() |
Brano precedente |
ACTION_PAUSE, ACTION_PLAY_PAUSE |
onPause() |
Mettere in pausa |
ACTION_STOP |
onStop() |
Interrompi |
ACTION_PLAY |
onPlay() |
Ripristina |
ACTION_SEEK_TO |
onSeekTo() |
Indietro di 30 secondi |
ACTION_SET_RATING |
onSetRating(android.support.v4.media.RatingCompat) |
Pollice su/giù. |
ACTION_SET_CAPTIONING_ENABLED |
onSetCaptioningEnabled(boolean) |
Attiva/disattiva i sottotitoli. |
Nota:
- Affinché i comandi di ricerca funzionino,
PlaybackState
deve essere aggiornato constate, position, playback speed, and update time
. L'app deve chiamaresetPlaybackState()
quando lo stato cambia. - L'app multimediale deve inoltre mantenere aggiornati i metadati della sessione multimediale. Supporta domande quali "qual è il brano in riproduzione?" L'app deve chiamare
setMetadata()
quando i campi applicabili (come titolo della traccia, artista e nome) cambiano. - L'app
MediaSession.setRatingType()
deve essere impostata per indicare il tipo di classificazione supportato dall'app, che deve implementareonSetRating()
. Se l'app non supporta la classificazione, deve impostare il tipo di classificazione suRATING_NONE
.
Le azioni vocali supportate probabilmente varieranno in base al tipo di contenuti.
Tipo di contenuti | Azioni richieste |
---|---|
Musica |
Supporto richiesto: riproduzione, pausa, interruzione, passa al successivo e vai al precedente Consigliamo vivamente assistenza per: vai a |
Podcast |
Supporto richiesto: riproduzione, pausa, interruzione e vai a Consigliare assistenza per: vai al passaggio successivo e passa al precedente |
Audiolibro | Supporto richiesto: riproduzione, pausa, interruzione e vai a |
Radio | Supporto richiesto: riproduzione, messa in pausa e interruzione |
Notizie | Supporto richiesto: riproduzione, pausa, interruzione, passa al successivo e vai al precedente |
Video |
Supporto richiesto: riproduzione, pausa, interruzione, vai a, riavvolgimento e avanzamento veloce È vivamente consigliato assistenza per: vai al passaggio successivo e passa al precedente |
Devi supportare il numero di azioni elencate sopra consentito, ma rispondere comunque con eleganza a qualsiasi altra azione. Ad esempio, se solo gli utenti Premium possono tornare all'elemento precedente, potresti generare un errore se un utente del livello senza costi chiede all'assistente di tornare all'elemento precedente. Per ulteriori indicazioni, consulta la sezione relativa alla gestione degli errori.
Esempi di query vocali da provare
La seguente tabella illustra alcuni esempi di query da utilizzare per i test dell'implementazione:
Callback MediaSession | Frase "Hey Google" da utilizzare | |
---|---|---|
onPlay() |
"Riproduci." "Riprendi". |
|
onPlayFromSearch()
onPlayFromUri() |
Musica |
"Riproduci musica o brani su (nome dell'app)". Questa query è vuota. "Fammi ascoltare (brano | artista | album | genere | playlist) su (nome dell'app)." |
Radio | "Riproduci (frequenza | stazione) su (nome app)." | |
Audiolibro |
"Leggi il mio audiolibro su (nome dell'app)." "Leggi (audiolibro) su (nome dell'app)." |
|
Podcast | "Fammi ascoltare (podcast) su (nome dell'app)." | |
onPause() |
"Metti in pausa". | |
onStop() |
"Interrompi". | |
onSkipToNext() |
"Successivo (brano | episodio | traccia)." | |
onSkipToPrevious() |
"Precedente (brano | puntata | traccia)." | |
onSeekTo() |
"Riavvia". "Vai avanti di ## secondi." "Torna indietro di ## minuti." |
|
N/D (mantieni aggiornato il tuo
MediaMetadata ) |
"Cosa c'è in riproduzione?" |
Errori
L'assistente gestisce gli errori di una sessione multimediale quando si verificano e li segnala agli utenti. Assicurati che la sessione multimediale aggiorni correttamente lo stato di trasporto e il codice di errore in PlaybackState
, come descritto in Utilizzo di una sessione multimediale. L'assistente
riconosce tutti i codici di errore restituiti da
getErrorCode()
.
Casi comunemente gestiti in modo errato
Di seguito sono riportati alcuni esempi di casi di errore che devi assicurarti di gestire correttamente:
- L'utente deve accedere.
- Imposta il codice di errore
PlaybackState
suERROR_CODE_AUTHENTICATION_EXPIRED
. - Imposta il messaggio di errore
PlaybackState
. - Se necessario per la riproduzione, imposta lo stato
PlaybackState
suSTATE_ERROR
, altrimenti mantieni il resto diPlaybackState
così com'è.
- Imposta il codice di errore
- L'utente richiede un'azione non disponibile
- Imposta il codice di errore
PlaybackState
in modo appropriato. Ad esempio, impostaPlaybackState
suERROR_CODE_NOT_SUPPORTED
se l'azione non è supportata oERROR_CODE_PREMIUM_ACCOUNT_REQUIRED
se l'azione è protetta da accesso. - Imposta il messaggio di errore
PlaybackState
. - Conserva gli altri
PlaybackState
così come sono.
- Imposta il codice di errore
- L'utente richiede contenuti non disponibili nell'app
- Imposta il codice di errore
PlaybackState
in modo appropriato. Ad esempio, utilizzaERROR_CODE_NOT_AVAILABLE_IN_REGION
. - Imposta il messaggio di errore
PlaybackState
. - Imposta lo stato
PlaybackSate
suSTATE_ERROR
per interrompere la riproduzione, altrimenti mantieni il resto diPlaybackState
così com'è.
- Imposta il codice di errore
- L'utente richiede contenuti in cui non è disponibile una corrispondenza esatta. Ad esempio, un utente di livello senza costi che richiede contenuti disponibili solo per gli utenti di livello premium.
- Ti consigliamo di non restituire alcun errore e di dare la priorità alla ricerca di qualcosa di simile a Riproduci. L'assistente gestirà la risposta vocale più pertinente prima di avviare la riproduzione.
Riproduzione con un intent
L'assistente può avviare un'app audio o video e avviare la riproduzione inviando un intent con un link diretto.
L'intent e il relativo link diretto possono provenire da diverse fonti:
- Quando l'assistente avvia un'app mobile, può utilizzare la Ricerca Google per recuperare i contenuti sottoposti a markup che forniscono un'azione di visualizzazione con un link.
- Quando l'assistente avvia un'app per la TV, quest'ultima deve includere un provider di ricerca TV per esporre gli URI dei contenuti multimediali. L'assistente invia una query al fornitore di contenuti che deve restituire un intent contenente un URI per il link diretto e un'azione facoltativa.
Se la query restituisce un'azione nell'intent, l'assistente invia l'azione e l'URI all'app. Se il provider non ha specificato un'azione, l'assistente aggiungerà
ACTION_VIEW
all'intent.
L'assistente aggiunge EXTRA_START_PLAYBACK
extra con valore true
all'intent che invia alla tua app. La riproduzione dell'app dovrebbe iniziare quando
riceve un intent con EXTRA_START_PLAYBACK
.
Gestione degli intent durante l'attività
Gli utenti possono chiedere all'assistente di riprodurre contenuti mentre nell'app è ancora in corso la riproduzione di contenuti di una richiesta precedente. Ciò significa che l'app può ricevere nuovi intent per avviare la riproduzione mentre la relativa attività di riproduzione è già avviata e attiva.
Le attività che supportano gli intent con link diretti dovrebbero sostituire
onNewIntent()
per gestire le nuove richieste.
Quando avvia la riproduzione, l'assistente potrebbe aggiungere altri
flag
all'intent che invia alla tua app. In particolare, potrebbe aggiungere
FLAG_ACTIVITY_CLEAR_TOP
,
FLAG_ACTIVITY_NEW_TASK
o entrambi. Anche se il tuo codice non deve gestire questi flag, il sistema Android li risponde.
Ciò potrebbe influire sul comportamento della tua app quando arriva una seconda richiesta di riproduzione con un nuovo URI mentre l'URI precedente è ancora in riproduzione. Ti consigliamo di verificare la risposta dell'app in questo caso. Puoi utilizzare lo strumento a riga di comando adb
per simulare la situazione (la costante 0x14000000
è l'OR booleano a livello di bit dei due flag):
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
Riproduzione da un servizio
Se la tua app ha un
media browser service
che consente le connessioni dall'assistente,
l'assistente può avviare l'app comunicando con il
media session
del servizio.
Il servizio browser multimediale non dovrebbe mai avviare un'attività.
L'assistente avvierà la tua attività in base al PendingIntent
che definisci con setSessionActivity().
Assicurati di impostare MediaSession.Token quando inizializzi il servizio del browser multimediale. Ricorda di impostare sempre le azioni di riproduzione supportate, anche durante l'inizializzazione. L'assistente si aspetta che l'app multimediale imposti le azioni di riproduzione prima che l'assistente invii il primo comando di riproduzione.
Per iniziare da un servizio, l'assistente implementa le API client del browser multimediale. Esegue chiamate TransportControls che attivano i callback dell'azione PLAY nella sessione multimediale dell'app.
Il seguente diagramma mostra l'ordine delle chiamate generate dall'assistente e i callback corrispondenti delle sessioni multimediali. I callback di preparazione vengono inviati solo se la tua app li supporta. Tutte le chiamate sono asincrone. L'assistente non attende alcuna risposta dall'app.
Quando un utente invia un comando vocale per avviare la riproduzione, l'assistente risponde con un breve annuncio. Al termine dell'annuncio, l'assistente esegue un'azione RIPRODUCI. Non attende alcuno stato di riproduzione specifico.
Se la tua app supporta le azioni ACTION_PREPARE_*
, l'assistente chiama l'azione PREPARE
prima di iniziare l'annuncio.
Connessione a MediaBrowserService
Per utilizzare un servizio per avviare la tua app, l'assistente deve essere in grado di connettersi a MediaBrowserService dell'app e recuperare il relativo MediaSession.Token. Le richieste di connessione vengono gestite nel metodo onGetRoot()
del servizio. Esistono due modi per gestire le richieste:
- Accetta tutte le richieste di connessione
- Accetta le richieste di connessione solo dall'app Assistente
Accetta tutte le richieste di connessione
Devi restituire un browserRoot per consentire all'assistente di inviare comandi alla tua sessione multimediale. Il modo più semplice è consentire a tutte le app MediaBrowser di connettersi a MediaBrowserService. Deve essere restituito un BrowserRoot con valore non null. Ecco il codice applicabile del player di Universal Music:
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... }
Accetta il pacchetto e la firma dell'app dell'assistente
Puoi consentire esplicitamente all'assistente di connettersi al servizio browser di contenuti multimediali controllando il nome e la firma del pacchetto. L'app riceverà il nome del pacchetto nel metodo onGetRoot di MediaBrowserService. Devi restituire un browserRoot per consentire all'assistente di inviare comandi alla tua sessione multimediale. L'esempio di Universal Music Player mantiene un elenco di firme e nomi di pacchetti noti. Di seguito sono riportati i nomi e le firme dei pacchetti utilizzati dall'Assistente 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>