Assistente Google e app multimediali

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()

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

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:

  1. Usa il gruppo extra e la stringa di query di ricerca restituite dalla ricerca vocale per filtrare i risultati.
  2. Crea una coda di riproduzione basata su questi risultati.
  3. 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 con state, position, playback speed, and update time. L'app deve chiamare setPlaybackState() 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 implementare onSetRating(). Se l'app non supporta la classificazione, deve impostare il tipo di classificazione su RATING_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 su ERROR_CODE_AUTHENTICATION_EXPIRED.
    • Imposta il messaggio di errore PlaybackState.
    • Se necessario per la riproduzione, imposta lo stato PlaybackState su STATE_ERROR, altrimenti mantieni il resto di PlaybackState così com'è.
  • 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 da accesso.
    • Imposta il messaggio di errore PlaybackState.
    • Conserva gli altri PlaybackState così come sono.
  • L'utente richiede contenuti non disponibili nell'app
    • Imposta il codice di errore PlaybackState in modo appropriato. Ad esempio, utilizza ERROR_CODE_NOT_AVAILABLE_IN_REGION.
    • Imposta il messaggio di errore PlaybackState.
    • Imposta lo stato PlaybackSate su STATE_ERROR per interrompere la riproduzione, altrimenti mantieni il resto di PlaybackState così com'è.
  • 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.

Avvio della riproduzione con una sessione multimediale

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>