Assistente Google e app multimediali

L'Assistente Google ti consente di utilizzare i comandi vocali per controllare molti dispositivi, come Google Home, il tuo smartphone e altro ancora. Ha una funzionalità integrata per comprendere i comandi multimediali ("riproduci qualcosa di Beyoncé") e supporta i controlli multimediali (come pausa, salto, avanzamento veloce, Mi piace).

L'assistente comunica con le app multimediali per Android utilizzando una sessione multimediale. Può utilizzare intent o servizi per avviare l'app e iniziare la riproduzione. Per ottenere risultati ottimali, la tua 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 azionare i controlli di trasporto una volta avviata la riproduzione.

Tieni presente che, sebbene l'assistente utilizzi solo le azioni elencate in questa sezione, la best practice è implementare tutte le API di preparazione e riproduzione per garantire la compatibilità con altre applicazioni. Per qualsiasi azione non supportata, i callback della sessione multimediale 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 della tua app deve dichiarare le azioni che supporta e implementare i callback della sessione multimediale corrispondenti. Dichiara le azioni supportate in setActions().

Il progetto di esempio Universal Android Music Player è un buon esempio di come configurare una sessione multimediale.

Azioni di riproduzione

Per avviare la riproduzione da un servizio, una sessione multimediale deve avere queste azioni PLAY e i relativi callback:

Azione Callback
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 Callback
ACTION_PREPARE onPrepare()
ACTION_PREPARE_FROM_SEARCH onPrepareFromSearch()
ACTION_PREPARE_FROM_URI(*) onPrepareFromUri()

(*) Le azioni basate su URI dell'Assistente Google funzionano solo per le aziende che forniscono URI a Google. Per scoprire di più su come descrivere i tuoi contenuti multimediali a Google, consulta Azioni multimediali.

Implementando le API di preparazione, è possibile ridurre la latenza di riproduzione dopo un comando vocale. Le app multimediali che vogliono migliorare la latenza di riproduzione possono utilizzare il tempo aggiuntivo per iniziare a memorizzare nella cache i contenuti e preparare la riproduzione multimediale.

Analizzare le query di ricerca

Quando un utente cerca un elemento multimediale specifico, ad esempio "Riproduci jazz su [nome della tua app]" o "Ascolta [titolo del brano]", il metodo di callback onPrepareFromSearch() o onPlayFromSearch() riceve un parametro di query e un bundle di extra.

La tua app deve analizzare la query di ricerca vocale e avviare la riproduzione seguendo questi passaggi:

  1. Utilizza il bundle di componenti aggiuntivi e la stringa di query di ricerca restituita dalla ricerca vocale per filtrare i risultati.
  2. Crea una coda di riproduzione in base a questi risultati.
  3. Riproduci l'elemento multimediale più pertinente dai risultati.

Il metodo onPlayFromSearch() accetta un parametro aggiuntivo con informazioni più dettagliate dalla ricerca vocale. Questi extra ti aiutano a trovare i contenuti audio nell'app per la riproduzione. Se i risultati di ricerca non sono in grado di fornire questi dati, puoi implementare una logica per analizzare la query di ricerca non elaborata e riprodurre le tracce appropriate in base alla query.

I seguenti componenti aggiuntivi sono supportati in Android Automotive OS e Android Auto:

Il seguente snippet di codice mostra come eseguire l'override del metodo onPlayFromSearch() nell'implementazione di MediaSession.Callback per analizzare la query di ricerca vocale e iniziare 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 nella tua app, consulta l'esempio Universal Android Music Player.

Gestire le query vuote

Se vengono chiamati onPrepare(), onPlay(), onPrepareFromSearch() o onPlayFromSearch() senza una query di ricerca, la tua app multimediale deve riprodurre i contenuti multimediali "correnti". Se non sono presenti contenuti multimediali in riproduzione, l'app dovrebbe provare a riprodurre qualcosa, ad esempio una canzone dell'ultima playlist o una coda casuale. L'assistente utilizza queste API quando un utente chiede di "Riproduci musica su [nome della tua app]" senza informazioni aggiuntive.

Quando un utente dice "Riproduci musica su [nome dell'app]", Android Automotive OS o Android Auto tenta di avviare l'app e riprodurre l'audio chiamando il metodo onPlayFromSearch() dell'app. Tuttavia, poiché l'utente non ha detto il nome dell'elemento multimediale, il metodo onPlayFromSearch() riceve un parametro di query vuoto. In questi casi, l'app deve rispondere riproducendo immediatamente l'audio, ad esempio un brano dell'ultima playlist o una coda casuale.

Dichiarare il supporto legacy 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 la tua app contenga un filtro per intent per la ricerca. Devi dichiarare il supporto per questo filtro Intent nei file manifest della tua 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

Una volta attiva la sessione multimediale dell'app, l'assistente può emettere comandi vocali per controllare la riproduzione e aggiornare i metadati dei contenuti multimediali. Affinché funzioni, il tuo codice deve abilitare le seguenti azioni e implementare i callback corrispondenti:

Azione Callback Descrizione
ACTION_SKIP_TO_NEXT onSkipToNext() Video successivo
ACTION_SKIP_TO_PREVIOUS onSkipToPrevious() Brano precedente
ACTION_PAUSE, ACTION_PLAY_PAUSE onPause() Metti in pausa
ACTION_STOP onStop() Interrompi
ACTION_PLAY onPlay() Riprendi
ACTION_SEEK_TO onSeekTo() Torna 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 codificati.

Tieni presente quanto segue:

  • Affinché i comandi di ricerca funzionino, PlaybackState deve essere aggiornato con state, position, playback speed, and update time. L'app deve chiamare setPlaybackState() quando lo stato cambia.
  • L'app multimediale deve anche mantenere aggiornati i metadati della sessione multimediale. Supporta domande come "Che brano sto ascoltando?". L'app deve chiamare setMetadata() quando cambiano i campi applicabili (ad esempio titolo, artista e nome della traccia).
  • MediaSession.setRatingType() deve essere impostato per indicare il tipo di classificazione supportato dall'app e l'app deve implementare onSetRating(). Se l'app non supporta la classificazione, deve impostare il tipo di classificazione su RATING_NONE.

Le azioni vocali che supporti variano probabilmente in base al tipo di contenuti.

Tipo di contenuti Azioni richieste
Musica

Deve supportare: riproduzione, pausa, interruzione, salto alla traccia successiva e salto alla traccia precedente

Consigliamo vivamente il supporto per: Seek To

Podcast

Deve supportare: Riproduci, Metti in pausa, Interrompi e Vai a

Consiglia supporto per: Vai al successivo e Vai al precedente

Audiolibro Deve supportare: Riproduci, Metti in pausa, Interrompi e Vai a
Radio Deve supportare: Riproduci, Metti in pausa e Interrompi
Notizie Deve supportare: riproduzione, pausa, interruzione, salto alla traccia successiva e salto alla traccia precedente
Video

Deve supportare: riproduzione, pausa, interruzione, ricerca, riavvolgimento e avanzamento veloce

Consigliamo vivamente il supporto per: Salta alla successiva e Salta alla precedente

Devi supportare il maggior numero possibile di azioni elencate sopra consentite dalle tue offerte di prodotti, ma rispondere comunque in modo appropriato a qualsiasi altra azione. Ad esempio, se solo gli utenti premium hanno la possibilità di 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 mostra alcuni esempi di query da utilizzare durante il test dell'implementazione:

Callback MediaSession Frase "Hey Google" da utilizzare
onPlay()

"Riproduci".

"Riprendi".

onPlayFromSearch()
onPlayFromUri()
Musica

"Fammi ascoltare musica o canzoni su (nome dell'app)". Questa è una query vuota.

"Fammi ascoltare (brano | artista | album | genere | playlist) su (nome dell'app)."

Radio "Fammi ascoltare (frequenza | stazione) su (nome dell'app)."
Audiolibro

"Leggi il mio audiolibro su (nome dell'app)".

"Leggi (audiolibro) su (nome dell'app)."

Podcast "Riproduci (podcast) su (nome dell'app)."
onPause() "Metti in pausa".
onStop() "Stop".
onSkipToNext() "Next (song | episode | track)".
onSkipToPrevious() "Previous (song | episode | track)".
onSeekTo()

"Riavvia."

"Vai avanti di ## secondi."

"Torna indietro di ## minuti".

N/A (mantieni il tuo MediaMetadata aggiornato) "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 lo stato di trasporto e il codice di errore nel relativo PlaybackState correttamente, come descritto in Utilizzo di una sessione multimediale. L'assistente riconosce tutti i codici di errore restituiti da getErrorCode().

Casi gestiti in modo errato

Ecco alcuni esempi di casi di errore che devi assicurarti di gestire correttamente:

  • L'utente deve accedere
    • Imposta il codice di errore PlaybackState su ERROR_CODE_AUTHENTICATION_EXPIRED.
    • Imposta il messaggio di errore PlaybackState.
    • Se necessario per la riproduzione, imposta lo stato di PlaybackState su STATE_ERROR, altrimenti mantieni il resto di PlaybackState invariato.
  • L'utente richiede un'azione non disponibile
    • Imposta il codice di errore PlaybackState in modo appropriato. Ad esempio, imposta PlaybackState su ERROR_CODE_NOT_SUPPORTED se l'azione non è supportata o ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED se l'azione è protetta dall'accesso.
    • Imposta il messaggio di errore PlaybackState.
    • Mantieni invariato il resto di PlaybackState.
  • Richieste utente di contenuti non disponibili nell'app
    • Imposta il codice di errore PlaybackState in modo appropriato. Ad esempio, usa ERROR_CODE_NOT_AVAILABLE_IN_REGION.
    • Imposta il messaggio di errore PlaybackState.
    • Imposta lo stato di PlaybackSate su STATE_ERROR per interrompere la riproduzione, altrimenti mantieni il resto di PlaybackState invariato.
  • L'utente richiede contenuti per i quali non è disponibile una corrispondenza esatta. Ad esempio, un utente del livello senza costi che chiede contenuti disponibili solo per gli utenti del livello premium.
    • Ti consigliamo di non restituire un errore e di dare la priorità alla ricerca di contenuti simili da riprodurre. L'assistente gestirà la risposta vocale più pertinente prima dell'inizio della riproduzione.

Riproduzione con un intento

L'assistente può avviare un'app audio o video e iniziare la riproduzione inviando un intent con un link diretto.

L'intent e il relativo link diretto possono provenire da fonti diverse:

  • Quando l'assistente avvia un'app mobile, può utilizzare la Ricerca Google per recuperare contenuti con markup che forniscono un'azione di visualizzazione con un link.
  • Quando l'assistente avvia un'app TV, l'app deve includere un provider di ricerca TV per esporre gli URI dei contenuti multimediali. L'assistente invia una query al fornitore di contenuti, che dovrebbe 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 alla tua app. Se il fornitore non ha specificato un'azione, l'assistente aggiungerà ACTION_VIEW all'intent.

L'assistente aggiunge l'extra EXTRA_START_PLAYBACK con il valore true all'intent che invia alla tua app. La tua app deve avviare la riproduzione quando riceve un intent con EXTRA_START_PLAYBACK.

Gestione degli intent durante l'attività

Gli utenti possono chiedere all'assistente di riprodurre contenuti mentre la tua app sta ancora riproducendo contenuti di una richiesta precedente. Ciò significa che la tua app può ricevere nuovi intent per avviare la riproduzione mentre la sua attività di riproduzione è già avviata e attiva.

Le attività che supportano gli intent con deep link devono eseguire l'override di onNewIntent() per gestire le nuove richieste.

Quando avvii la riproduzione, l'assistente potrebbe aggiungere flag aggiuntivi all'intent che invia alla tua app. In particolare, potrebbe aggiungere FLAG_ACTIVITY_CLEAR_TOP o FLAG_ACTIVITY_NEW_TASK o entrambi. Anche se il tuo codice non deve gestire questi flag, il sistema Android 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. È consigliabile testare la risposta della tua app in questo caso. Puoi utilizzare lo strumento a riga di comando adb per simulare la situazione (la costante 0x14000000 è l'OR bit a bit booleano 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 l'media session del servizio. Il servizio di esplorazione dei contenuti multimediali non deve mai avviare un'attività. L'assistente avvierà l'attività in base al PendingIntent che definisci con setSessionActivity().

Assicurati di impostare MediaSession.Token quando inizializzi il servizio di esplorazione dei contenuti multimediali. Ricordati di impostare le azioni di riproduzione supportate in qualsiasi momento, anche durante l'inizializzazione. L'assistente si aspetta che la tua 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 della sessione multimediale corrispondenti. (I callback di preparazione vengono inviati solo se la tua app li supporta.) Tutte le chiamate sono asincrone. L'assistente non attende alcuna risposta dalla tua app.

Avviare la riproduzione con una sessione multimediale

Quando un utente emette un comando vocale per la riproduzione, l'assistente risponde con un breve annuncio. Non appena l'annuncio è completato, l'assistente emette un'azione PLAY. Non attende uno 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 un MediaBrowserService

Per utilizzare un servizio per avviare l'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:

  • Accettare tutte le richieste di connessione
  • Accettare solo le richieste di connessione dall'app Assistente

Accettare tutte le richieste di connessione

Devi restituire un BrowserRoot per consentire all'assistente di inviare comandi alla sessione multimediale. Il modo più semplice è consentire a tutte le app MediaBrowser di connettersi a MediaBrowserService. Devi restituire un BrowserRoot non nullo. Ecco il codice applicabile 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...
}

Accetta il pacchetto applicativo e la firma dell'app Assistente

Puoi consentire esplicitamente all'assistente di connettersi al tuo servizio di navigazione multimediale controllando il nome del pacchetto e la firma. La tua app riceverà il nome del pacchetto nel metodo onGetRoot di MediaBrowserService. Devi restituire un BrowserRoot per consentire all'assistente di inviare comandi alla sessione multimediale. L'esempio Universal Music Player gestisce un elenco di nomi e firme di pacchetti noti. Di seguito sono riportati i nomi dei pacchetti e le firme 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>