Android XR

Creazione di esperienze audio-first per occhiali intelligenti con Gemini Nano 4

Altoparlanti

Informazioni

Informazioni sulla presentazione

Scopri come creare esperienze audio solide e con comandi vocali basate sull'AI on-device. Tratteremo tutto ciò di cui hai bisogno: configurazione dell'attività degli occhiali, acquisizione dei feed del microfono e della videocamera e utilizzo della voce insieme ad altri segnali audio come UI definitiva. Attraverso un'entusiasmante dimostrazione reale dell'XR Language Teacher open source integrato nell'app Google AI Edge Gallery, impara a orchestrare Gemini Nano 4 (basato su Gemma 4 e ottimizzato per i dispositivi Android) e ML Kit e sfrutta il runtime LiteRT per creare esperienze XR veloci che ascoltano, vedono e parlano in modo naturale.

Riepilogo

  • La libreria Jetpack Projected (alpha) collega gli smartphone Android agli occhiali intelligenti. L'aggiunta di un'attività proiettata alla tua app esistente proietta i contenuti su occhiali con display o solo audio.
  • ML Kit introduce librerie di riconoscimento vocale on-device in versione alpha per interazioni incentrate sull'audio su dispositivi selezionati. Offre la sintesi vocale con 8 voci in 21 impostazioni internazionali e il riconoscimento vocale con una modalità avanzata di Gemini Nano su Pixel 10.
  • Gemini Nano 4 elabora i frame della fotocamera e le domande trascritte insieme in un unico passaggio. Supporta le esperienze offline concatenando localmente i modelli vocali on-device sugli smartphone di punta supportati.
  • Un design a due passaggi dà la priorità alla latenza audio in tempo reale sugli occhiali smart. Il Passaggio 1 trasmette in streaming il testo parlato utilizzando tag XML incorporati per il cambio di impostazioni internazionali a metà frase, mentre il Passaggio 2 estrae i dati strutturati in background.
  • L'implementazione open source di XR Language Teacher è disponibile nell'app Google AI Edge Gallery. Gli sviluppatori possono adattare il codice per altri casi d'uso di assistenti multimodali.

Risorse

Gemini Notebook

Argomenti

  • intelligent-eyewear
  • android-xr
  • gemini-nano
  • on-device-ai
  • ml-kit
  • riconoscimento vocale
  • sintesi vocale

Trascrizione

Scarica la trascrizione

Introduzione

  • Ciao, benvenuti a tutti.

  • È un piacere vedervi oggi.

  • Grazie per aver partecipato.

  • Sono Amy.

  • Oggi sarò affiancato dalla mia collega Alice e parleremo di come creare esperienze audio-first incredibili per gli occhiali intelligenti utilizzando Gemini Nano 4.

  • Iniziamo.

  • Quindi, quando parliamo di occhiali intelligenti, ci riferiamo a occhiali portatili e leggeri pensati per essere indossati tutto il giorno.

  • Questa categoria include sia gli occhiali con display sia quelli solo audio e, con questi dispositivi, vedrai un hardware variegato.

  • La prima cosa che noterai è che sono dotati di un touchpad, che può essere utilizzato per l'input dell'utente, ad esempio tocchi e scorrimenti.

  • Inoltre, avranno altoparlanti, una videocamera e un microfono.

  • Inoltre, potrebbero avere o meno un display o, potenzialmente, una combinazione di questi elementi.

  • Oggi parleremo di audio-first e di come creare esperienze coinvolgenti, intelligenti e on-the-go.

  • E una delle cose interessanti della modalità solo audio è che la voce diventa la tua UI.

  • Inoltre, l'AI on-device è ideale per gli sviluppatori che cercano esperienze offline e in mobilità.

Sviluppo per Android XR

  • E ci sono ottime notizie.

  • Quindi, se stai già sviluppando per Android, stai già sviluppando per Android XR.

  • Le app per Android esistenti sono progettate per estendersi bene agli occhiali audio, fornendo notifiche vocali e altri contenuti audio tramite Bluetooth.

  • Se vuoi fare un passo avanti e differenziare la tua app per XR, hai a disposizione molti modi per farlo.

  • In questo intervento parleremo dei modelli on-device, ma non si tratta affatto di un elenco esaustivo.

  • Abbiamo anche modelli cloud.

  • Ci sono cose come l'API Gemini Live.

  • Esistono agenti di sistema con cui puoi eseguire l'integrazione, come Gemini, e tutti questi possono aiutarti a creare l'esperienza più adatta a te.

  • Come si crea un'esperienza che ascolta, vede e parla in modo naturale?

  • Oggi ti mostreremo come creare queste esperienze solide e con comandi vocali basate sull'AI on-device.

  • Ti guiderò attraverso le nozioni di base: come creare per gli occhiali, come configurare la tua attività, ottenere le autorizzazioni necessarie e poi come utilizzare ML Kit per l'UI conversazionale.

  • Poi passerò la parola ad Alice, che vi illustrerà alcuni esempi reali di utilizzo di Gemini Nano 4.

  • Andiamo avanti e analizziamo il codice.

  • Quindi, con gli occhiali, lo smartphone è quello che fa il lavoro più pesante e i contenuti degli occhiali verranno proiettati dallo smartphone agli occhiali.

  • Per collegare questi dispositivi durante la prima configurazione, sono necessarie due cose.

  • Per prima cosa, avrai bisogno della libreria Jetpack Projected.

  • Dovrai aggiungere questa dipendenza, che conterrà le API per l'esperienza proiettata.

    dependencies {
       implementation("androidx.xr.projected:projected:1.0.0-alpha13")
    }
  • Dovrai anche creare una nuova attività specifica per gli occhiali.

    class ProjectedMainActivity : ComponentActivity() {
       override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
           setContent {
               // Glasses content
           }
       }
    }
  • Poi, utilizzerai la libreria Jetpack Projected sia all'interno dell'app esistente sia all'interno di questa nuova attività, in modo che i due dispositivi possano comunicare senza problemi ogni volta che è necessario.

  • Quindi, proprio come qualsiasi attività normale che hai visto, la dichiarerai nel manifest.

    <activity
       android:name=".ProjectedMainActivity"
       android:exported="false"
       android:requiredDisplayCategory="android.hardware.display.category.XR_PROJECTED"
       android:label="Example activity for audio glasses">
       <intent-filter>
           <action android:name="android.intent.action.MAIN" />
           <category android:name="android.intent.category.XR_PROJECTED_LAUNCHER"/>
       </intent-filter>
    </activity>
  • La differenza principale è che devi utilizzare XR_PROJECTED come requiredDisplayCategory.

  • In questo modo, il sistema sa che questa attività riguarda gli occhiali.

  • Inoltre, dovrai utilizzare questa categoria di intent, XR_PROJECTED_LAUNCHER.

  • In questo modo, Gemini può scoprire la tua app, così gli utenti possono avviarla con comandi vocali ovunque si trovino.

  • Okay.

  • Una volta configurata l'attività, come ho accennato, utilizzerai la libreria Projected per l'esperienza con l'app.

  • Questa libreria contiene tutti gli strumenti necessari per creare l'esperienza completa degli occhiali.

  • Vediamo alcune delle API principali che utilizzerai qui.

  • Il primo che voglio segnalare è il metodo isProjectedDeviceConnected.

    // Continuously monitor whether the glasses are connected
    val isGlassesConnected by ProjectedContext.isProjectedDeviceConnected(
       context,
       scope.coroutineContext
    ).collectAsStateWithLifecycle(initialValue = false)
    
    // Launch the activity
    val options = ProjectedContext.createProjectedActivityOptions(context)
    val intent = Intent(context, ProjectedMainActivity::class.java)
    context.startActivity(intent, options.toBundle())
    
    // Check if the device is audio only or has a display
    val projectedDeviceController = ProjectedDeviceController.create(this@ProjectedMainActivity)
    isVisualUiSupported = projectedDeviceController.capabilities.contains(CAPABILITY_VISUAL_UI)
  • In questo modo potrai monitorare continuamente la connessione degli occhiali.

  • Puoi utilizzarlo insieme a un ciclo di vita del dispositivo per sapere quando gli occhiali sono effettivamente disponibili, quindi non solo quando sono accoppiati o accesi, ma anche, ad esempio, quando vengono indossati e sono pronti per l'uso.

  • Ed è questo che, quindi, sì.

  • In questo modo saprai esattamente quando l'attività sarà pronta per essere lanciata.

  • Per farlo, quando è tutto pronto per lanciare l'attività, devi impostare alcune opzioni specifiche quando avvii l'attività.

  • Per questi, utilizzerai createProjectedActivityOptions su ProjectedContext.

  • E ancora, lo utilizzerai con il contesto nell'intent.

  • Un altro strumento che ti consigliamo di utilizzare è un ProjectedDeviceController.

  • In questo modo potrai verificare il tipo di occhiali utilizzato dall'utente.

  • Come ho detto prima, gli occhiali intelligenti includono occhiali con display e solo audio, quindi dovrai verificare se le funzionalità contengono questo CAPABILITY_VISUAL_UI, che ti consentirà di adattare l'app in modo ottimale ai diversi dispositivi e di personalizzare un'esperienza ottimizzata per quel dispositivo.

  • Quindi, mentre crei questa esperienza differenziata, potresti voler accedere all'hardware del dispositivo, ad esempio sfruttando la fotocamera degli occhiali.

  • Per farlo da un'attività proiettata, dovrai richiedere le autorizzazioni appropriate utilizzando ProjectedActivityCompat.

    // In a projected activity
    private fun requestProjectedPermissions() {
       lifecycleScope.launch {
           try {
               ProjectedActivityCompat.requestPermissions(
                   this@PermissionsFromProjectedActivity,
                   arrayOf(Manifest.permission.CAMERA),
                   // CAMERA_PERMISSION_REQUEST_CODE is a developer defined constant
                   requestCode = CAMERA_PERMISSION_REQUEST_CODE,
               )
           } catch (e: IllegalStateException) {
               // Thrown when the projected system service can't be bound.
               Log.e(TAG, "Request failed: projected service unavailable.", e)
           }
       }
    }
  • Anche se la tua app dispone già delle autorizzazioni necessarie per lo smartphone, devi comunque richiedere le autorizzazioni specifiche anche per l'hardware degli occhiali.

  • Inoltre, quando lavori con le autorizzazioni, ricorda di seguire le best practice per Android in generale.

  • Dichiara le autorizzazioni necessarie nel file manifest, richiedi le autorizzazioni nel contesto in cui sono necessarie e richiedi solo quelle effettivamente necessarie per il funzionamento della tua app.

  • Naturalmente, abbiamo documentato tutto questo sul nostro sito developer.android.com.

  • Ma abbiamo anche una nuova skill, la skill hardware proiettato.

  • Puoi utilizzarla con la CLI Android in Android Studio per configurare il workflow delle autorizzazioni e puoi accedere a questa skill e poi anche a tutte le altre skill di Android.

    android skills add projected-hardware
    android skills add --all

Progettare esperienze audio-first

  • Ora che abbiamo stabilito la connessione, conosciamo le funzionalità hardware e abbiamo le autorizzazioni corrette, parliamo degli strumenti a tua disposizione per creare l'esperienza audio.

  • Pertanto, quando progettiamo un'esperienza audio-first, vogliamo assicurarci di creare un'esperienza di alta qualità per gli utenti.

  • Questo potrebbe includere alcune interazioni chiave.

  • Il primo è l'utilizzo della voce come output, ovvero consentire alla tua voce di parlare in modo naturale con l'utente.

  • Il passaggio successivo consiste nell'utilizzare gli earcon per inviare segnali audio specifici all'utente.

  • Inoltre, gestisce l'input dell'utente, ad esempio tramite il touchpad con tocchi e scorrimenti, o se vuoi consentire all'utente di parlare con la tua app.

  • La prima cosa di cui parleremo è l'utilizzo della voce come output.

  • Per questo, voglio presentare la nuova libreria di sintesi vocale GenAI di ML Kit.

  • Questa funzionalità è ora disponibile in versione alpha su alcuni dispositivi Pixel e Samsung.

  • Siamo davvero entusiasti di questa funzionalità, soprattutto per le esperienze audio-first.

  • Se hai già lavorato con la sintesi vocale, probabilmente sai che uno dei pain point può essere che la voce del sistema non suona sempre naturale come vorresti.

  • Inoltre, la connessione ai modelli cloud per altre voci potrebbe comportare una potenziale latenza o costi aggiuntivi.

  • ML Kit offre quindi un parlato dal suono molto più naturale rispetto alla sintesi vocale del sistema.

  • Inoltre, ML Kit offre otto voci uniche tra cui scegliere, con caratteristiche diverse per soddisfare le esigenze della tua app.

  • Inoltre, ML Kit offre un solido supporto linguistico con 21 diverse impostazioni internazionali tra cui scegliere.

  • Inoltre, uno dei grandi vantaggi è che, dopo il download iniziale, l'app funzionerà completamente on-device.

  • Questo modello verrà scaricato sul sistema e verrà condiviso tra le app, quindi non aumenterà le dimensioni dell'APK.

  • Inoltre, non solo è disponibile offline, il che è particolarmente importante per gli utenti di occhiali che sono in movimento, che potrebbero non avere il Wi-Fi o trovarsi in aree con scarsa copertura, ma è anche probabile che tu veda prestazioni migliori, poiché non devi effettuare la chiamata di andata e ritorno a un server.

  • Okay.

  • Vediamo quindi i dettagli della libreria e come possiamo utilizzarla per creare.

  • Per prima cosa, ovviamente, devi aggiungere la dipendenza.

    dependencies {
       // ML Kit GenAI TTS SDK
       implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
    }
  • In questo caso, tutto ciò che ti serve è l'SDK Text To Speech di ML Kit.

  • A questo punto puoi iniziare a scrivere il codice.

  • Pertanto, la libreria ML Kit per l'AI generativa include le API Speech Synthesizer.

    // 1. Initializing the SpeechSynthesizer client.
    val options = SpeechSynthesizerOptions.Builder(Locale.US).build()
    val client = SpeechSynthesis.getClient(options)
    
    // 2. Check and download the voice model.
    val voiceInfo = client.getSupportedVoices().firstOrNull()
    
    if (voiceInfo != null && client.checkStatus(voiceInfo) == FeatureStatus.DOWNLOADABLE) {
       client.download(voiceInfo).collect { status -> println("Download status: $status") }
    } else if (voiceInfo == null) {
       // Tip: Fallback to system TTS if ML Kit voice is unavailable
    }
  • In questo modo, puoi implementare facilmente le voci che preferisci.

  • Il primo passaggio consiste nell'inizializzare il client utilizzando nuovamente il sintetizzatore vocale e impostando le impostazioni internazionali che hai scelto.

  • Poi, controlla lo stato del download e seleziona la voce.

  • In questo esempio, scegliamo la prima voce, ma voglio anche sottolineare che stiamo utilizzando firstOrNull.

  • Come ho già detto, ML Kit richiede uno smartphone specifico, quindi un dispositivo Samsung o Pixel.

  • Quindi, se dovessi ricevere un valore null, potrebbe significare che l'utente non sta utilizzando uno smartphone compatibile.

  • Poi, devi assicurarti di avere un'opzione di riserva, che si tratti di tornare alla sintesi vocale del sistema o di riprodurre un messaggio registrato o qualcosa di simile.

  • Ok, poi utilizzerai la sintesi vocale SpeechSynthesizerRequest per creare la richiesta, fornendo il testo di input e impostando l'utilizzo e il tipo di contenuti.

    // 3. Construct the SpeechSynthesizerRequest
    val request = SpeechSynthesizerRequest(
       input = SpeechSynthesizerRequest.Input.Text("Hello world", voiceInfo),
       audioAttributes = AudioAttributes.Builder()
           .setUsage(AudioAttributes.USAGE_MEDIA)
           .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
           .build()
    )
    
    // 4. Speak and collect status
    client.synthesize(request).collect { response ->
       when (response) {
           is SpeechSynthesizerResponse.StatusUpdate -> println("Playback status: ${response.status}")
       }
    }
  • Riproduci il discorso, monitora lo stato e ricevi una notifica quando il discorso è completo, quindi raccogli eventuali errori che potrebbero verificarsi.

  • e chiudere le risorse quando necessario.

    // 4. Speak and collect status
    client.synthesize(request).collect { response ->
       when (response) {
           is SpeechSynthesizerResponse.StatusUpdate -> println("Playback status: ${response.status}")
       }
    }
    
    // 5. Release resources when finished (e.g., on Activity destroy)
    override fun onDestroy() {
       super.onDestroy()
       client.close()
    }
  • È piuttosto semplice, vero?

  • Con questi passaggi, puoi davvero trasformare il suono della tua app da qualcosa che suona così…

  • Gentile sviluppatore Android,

  • È un piacere vedervi qui oggi.

  • Ci auguriamo che la nostra presentazione sulle esperienze audio-first vi piaccia.

  • A qualcosa che suona più come questo.

  • Ciao sviluppatori Android.

  • È un piacere vedervi qui oggi.

  • Ci auguriamo che la nostra presentazione sulle esperienze audio-first vi piaccia.

  • Molto meglio, vero?

  • Ok.

  • La libreria Text to Speech di ML Kit è uno strumento molto potente, ma non sei limitato all'utilizzo di queste voci, puoi anche riprodurre earcon con questa libreria.

    // 1. Register Earcon resource with Builder
    SpeechSynthesizerOptions.Builder(Locale.US)
       .addEarcon(
           name = "shutter_sound",
           Uri = "path/to/earcon.file".toUri()
       )
       .build()
    
    // 2. Request Earcon playback
    val request = SpeechSynthesizerRequest(
       input = SpeechSynthesizerRequest.Input.Earcon(
           name = "shutter_sound"
       ),
       audioAttributes = AudioAttributes.Builder()
           .setUsage(AudioAttributes
               .USAGE_ASSISTANCE_SONIFICATION)
           .setContentType(AudioAttributes
               .CONTENT_TYPE_SONIFICATION)
           .build()
    )
  • Come suggerisce il nome, gli earcon sono fondamentalmente l'equivalente audio delle icone.

  • In questo modo, potrai riprodurre brevi suoni per comunicare con l'utente.

  • E proprio come le icone visive con gli earcon, l'utente dovrà sapere cosa sono e cosa significano.

  • Quindi, inizialmente, potresti volerli abbinare alle notifiche vocali per chiarire il concetto.

  • Abbiamo già creato alcuni suoni standardizzati disponibili su Android Design Hub, che puoi scaricare direttamente da lì e utilizzare per azioni come l'apertura e la chiusura dell'app, le notifiche di errore e l'attivazione o la disattivazione dell'audio.

  • Tuttavia, hai anche la possibilità di creare i tuoi earcon personalizzati.

  • Per quanto riguarda gli earcon, esistono alcune best practice che consigliamo di seguire, sia che utilizzi quelli scaricati dall'Hub di progettazione sia che ne crei di personalizzati.

  • Quindi, devi prestare attenzione al modo in cui utilizzi gli earcon e a come li utilizzi.

  • Ci sono alcune cose da tenere a mente, ad esempio, per cominciare, cerca di non utilizzare eccessivamente gli earcon.

  • Non riprodurre gli stessi suoni più e più volte, perché questo può causare la cosiddetta "stanchezza uditiva".

  • Ricorda che gli earcon devono essere semplici e intuitivi e che devi utilizzare toni e ritmi familiari quando li implementi.

  • Inoltre, cerca di evitare earcon eccessivamente complessi che richiedono spiegazioni, in quanto potrebbero essere più adatti come notifiche vocali.

  • E tieni sempre presente che ciò che potrebbe sembrare impercettibile in studio o sul tuo sistema, sugli altoparlanti del tuo computer, può diventare fastidioso dopo una riproduzione ripetuta per i tuoi utenti.

  • Quindi, fai del tuo meglio per non sovraccaricare gli utenti con suoni che potrebbero confonderli o portarli a ignorare avvisi importanti.

  • Okay.

  • Ora abbiamo la possibilità di fornire segnali audio e feedback, sia tramite il parlato naturale sia utilizzando gli earcon.

  • E per quanto riguarda l'input?

  • Ovviamente c'è un touchpad, o pulsanti dedicati anche sul dispositivo, che puoi utilizzare per creare interazioni di tocco e scorrimento.

  • Ma potresti anche voler consentire questa UI conversazionale, per la quale puoi utilizzare l'API GenAI Speech Recognition di ML Kit.

  • Attualmente in versione alpha e disponibile su dispositivi selezionati, ma fondamentalmente funziona in due modalità distinte.

  • La modalità di base utilizza il riconoscimento vocale on-device tradizionale ed è disponibile sui dispositivi Android con livello API 31 e versioni successive.

  • C'è anche la modalità avanzata che utilizza Gemini Nano, offerta sui dispositivi Pixel 10 e versioni successive, che offre una qualità migliore e una migliore copertura linguistica.

  • Quindi SpeechRecognizer segue un pattern di codice molto simile a SpeechSynthesizer.

  • Crei il client, controlli se il modello è disponibile, lo scarichi se necessario, crei la richiesta e rilasci le risorse.

  • Concentriamoci quindi sulle differenze principali, ovvero la creazione della richiesta, l'acquisizione dell'audio del microfono e l'elaborazione del risultato.

  • Anche in questo caso, dovrai aggiungere le dipendenze necessarie.

    dependencies {
       // ML Kit Speech Recognition
       implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
    }
  • Tutto ciò di cui hai bisogno è l'API Speech Recognition.

  • Dopodiché puoi iniziare a creare la tua richiesta.

  • Quindi, utilizzerai una funzione di sospensione quando definirai la logica di riconoscimento.

    suspend fun startMyRecognition() {
       // choose locale and mode
       val options: SpeechRecognizerOptions =
           speechRecognizerOptions {
               locale = Locale.US
               preferredMode = SpeechRecognizerOptions.Mode.MODE_ADVANCED
           }
    
       val speechRecognizer: SpeechRecognizer = SpeechRecognition.getClient(options)
    
       val request: SpeechRecognizerRequest =
           speechRecognizerRequest { audioSource = AudioSource.fromMic() }
    ...
  • Poi specifica le opzioni di riconoscimento vocale, impostando le impostazioni internazionali e preferredMode.

  • Qui sceglierai anche il audioSource e definirai la richiesta stessa.

  • Poi avvierai il riconoscimento e raccoglierai una risposta.

    speechRecognizer.startRecognition(request).collect { response ->
       when (response) {
           is SpeechRecognizerResponse.PartialTextResponse -> {
               // Real-time: Act immediately if trigger phrase is heard
               handleResponse(response.text, speechRecognizer)
           }
           is SpeechRecognizerResponse.FinalTextResponse -> {
               // Final result and recognition
               handleResponse(response.text, speechRecognizer)
           }
           is SpeechRecognizerResponse.CompletedResponse -> {
               resetListeningUi()
           }
           is SpeechRecognizerResponse.ErrorResponse -> {
               Log.e("SpeechRec", "Error: ${response.e.errorCode}", response.e)
               resetListeningUi()
           }
    ...
  • Quindi, con SpeechRecognizer, hai quattro potenziali risposte: parziale, finale, errore e completa.

  • La trascrizione parziale esamina lo stream in tempo reale, quindi viene utilizzata per elementi come hotword o parole chiave.

  • Final prende lo stream audio finale dopo averlo analizzato e aggiornato completamente, Completed viene attivato dopo il completamento del parlato ed Error raccoglie eventuali errori che potrebbero verificarsi.

  • Ecco come mettiamo tutto insieme.

    // Helper function to check for multiple phrases and trigger the right action
    private suspend fun handleResponse(text: String, recognizer: SpeechRecognizer) {
       val savePhrases = listOf("save", "bookmark", "remember")
       if (savePhrases.any { text.contains(it) }) {
    
           // 1. Update app state immediately
           saveItemForLater()
    
           // 2. Stop the recognizer to enforce the action and prevent TTS echo
           recognizer.stopRecognition()
    
           // 3. Play the mute earcon for immediate, non-verbal confirmation
           playEarcon()
    
           // 4. Provide voice feedback
           playAudioFeedback("Saved")
       }
    }
  • Supponiamo di voler consentire a un utente di utilizzare un comando vocale per indicare all'app di salvare un tipo di contenuto per un secondo momento.

  • Potremmo usare un singolo comando vocale esplicito che abbiamo detto loro di usare oppure potremmo consentire loro di usare una serie di parole chiave e frasi.

  • Quindi, qui abbiamo impostato: consentiamo loro di utilizzare: salva, aggiungi ai preferiti o ricorda.

  • Non appena SpeechRecognizer rileva che l'utente ha utilizzato una di queste frasi, salverà i contenuti e interromperà il riconoscimento vocale, riprodurrà un earcon personalizzato e utilizzerà la sintesi vocale di GenAI di ML Kit per comunicare all'utente che i contenuti sono stati salvati.

Case study di XR Language Teacher

  • Ok.

  • Abbiamo visto molte cose.

  • Potrebbe sembrare un po' astratto.

  • Se sei come me, vuoi vedere alcuni esempi reali.

  • Alice e io vogliamo spiegarvi esattamente questo.

  • Ti guiderà attraverso XR Language Teacher nell'app galleria Google AI Edge.

  • Il team di Alice l'ha creata e lei ti mostrerà esattamente come funziona.

  • A te la parola.

  • Grazie, Amy.

  • Sì.

  • Mi piace iniziare con qualcosa che molti di voi riconoscerebbero.

  • Sto imparando il giapponese e ho passato ore su app per l'apprendimento delle lingue, flashcard, serie e tutto il resto.

  • Poi, atterro a Tokyo, entro in un mercato e mi rendo conto che quasi nulla di ciò che ho davanti corrisponde al vocabolario che ho studiato.

  • Su una bancarella del mercato c'è un giocattolo di legno con una tazza e una palla che riconosco a vista, ma non so come si dice in giapponese.

  • C'è un ornamento in una vetrina che voglio acquistare e di cui non conosco il nome.

  • È proprio in quel momento che sono più motivato a imparare una parola.

  • Non sul mio divano a guardare un'app di lingue.

  • In quel momento, sto guardando la cosa che voglio imparare.

  • Ecco cosa voglio.

  • Mi piacerebbe indossare gli occhiali smart tutto il giorno, semplicemente camminando.

  • E ogni volta che qualcosa attira la mia attenzione, basta chiedere ad alta voce.

  • Poi, la sera, quando torno in hotel, tutto ciò che ho chiesto mi aspetta come schede di vocabolario sul mio smartphone.

  • Ed è quello che abbiamo creato.

  • Guardiamo un breve video.

  • Per questo lo chiamiamo XR Language Teacher.

  • Ciao.

  • Cosa vuoi imparare oggi?

  • Inoltre, puoi personalizzare la tua esperienza di apprendimento nelle impostazioni per gli insegnanti.

  • Ad esempio, scegli la lingua che vuoi imparare e il tipo di insegnante più adatto a te.

  • Ah, sì.

  • Inoltre, puoi passare dalla modalità occhiali alla modalità smartphone e viceversa.

  • Come si dice "queste uve" in spagnolo?

  • A quanto pare, quei deliziosi acini d'uva si chiamano uvas.

  • In spagnolo, puoi dire "Estas uvas son deliciosas" per descriverle.

  • Voglio dire uva verde, nello specifico.

  • Fammi dare un'occhiata.

  • Per specificare l'uva verde, puoi dire "Uvas verdes.

  • Me gustan las uvas verdes" è un ottimo modo per dire a qualcuno che ti piace.

  • Come si dice in francese e come faccio a chiederne tre?

  • Diamo un'occhiata.

  • Questi deliziosi dolci si chiamano madeleine.

  • In francese, per chiederne tre, puoi dire: "Je voudrais trois madeleines, s'il vous plaît".

  • Come faccio a chiedere il resto con questo?

  • Vediamo cosa abbiamo qui.

  • Per chiedere il resto, puoi dire "Otsuri wa arimasu ka?".

  • Che significa "Hai del resto?"

  • Puoi anche dire "Otsuri o onegaishimasu" per chiedere educatamente il resto.

  • Inoltre, tutto ciò che hai imparato viene salvato automaticamente sotto forma di schede di vocabolario.

  • La botella de agua.

  • Esta es una botella de agua.

  • Sì, e puoi testare la tua memoria ed esercitarti nella scheda Studio.

  • Okay.

  • Sì.

  • Quindi, tutto ciò che avete appena visto è stato eseguito sul dispositivo, senza alcuna rete coinvolta.

  • Viaggio con un piano dati internazionale, che di solito funziona bene fino al momento in cui mi trovo all'interno di un mercato coperto o magari sottoterra o dietro un muro di cemento spesso.

  • Il momento in cui ne hai più bisogno è spesso quello in cui il segnale è peggiore.

  • E direi che un'esperienza che muore quando le barre del segnale scendono non è un vero compagno di viaggio, motivo per cui abbiamo reso XR Language Teacher completamente offline e on-device.

Concatenamento di modelli on-device

  • Come è stato realizzato?

  • Dal punto di vista dell'architettura, Amy ti ha già mostrato come utilizzare un frame della fotocamera proiettato dagli occhiali allo smartphone.

  • Ti ha mostrato come utilizzare ML Kit, il riconoscimento vocale, per convertire il testo in voce e anche ML Kit Text-to-Speech per convertire il testo in voce.

  • Ciò che ci mancava era un cervello nel mezzo, qualcosa che potesse esaminare insieme il fotogramma della fotocamera e la domanda e capire cosa l'utente stesse effettivamente chiedendo.

  • Questo cervello è Gemini Nano 4, basato su Gemma 4, il modello open source di Google eseguito localmente sullo smartphone.

  • Pertanto, sui dispositivi Android top di gamma supportati, puoi ottenere prestazioni ottimizzate utilizzando direttamente le API ML Kit.

  • Il sistema Android si occuperà della distribuzione del modello, quindi non devi raggruppare nulla nella tua app.

  • Quindi, per riassumere, il microfono entra nel riconoscimento vocale, mentre il fotogramma della videocamera e la trascrizione entrano insieme in Gemini Nano 4, che scrive nella sintesi vocale e poi esce dallo speaker.

  • Tre modelli, concatenati, tutti in esecuzione localmente sul tuo smartphone.

  • Ecco alcuni motivi per cui Gemini Nano 4 è particolarmente adatto a questo problema.

  • Innanzitutto, vede e legge in un unico passaggio.

  • Tramite ML Kit, Gemini Nano 4 gestisce testo e immagini in modo nativo, quindi non utilizzi un rilevatore, poi un classificatore e infine un traduttore.

  • Si tratta di un unico modello che esamina contemporaneamente il frame della videocamera e la domanda.

  • In secondo luogo, il suo ampio supporto linguistico.

  • Gemini Nano 4 è stato addestrato su oltre 140 lingue.

  • Inoltre, per il tutor di lingue, la traduzione da sola non è sufficiente.

  • Vuoi espressioni idiomatiche, registri formali e informali, nonché frasi di esempio che una persona reale direbbe.

  • Infine, è abbastanza orientabile da avere una personalità.

  • Esercitarsi nella pronuncia ad alta voce può essere intimidatorio.

  • Inoltre, questo modello segue bene un prompt di sistema, quindi l'utente può scegliere il proprio personaggio di insegnante.

  • E forse un tono paziente e incoraggiante potrebbe rendere le persone più propense a provare.

  • Ecco un'altra cosa che vorrei sottolineare sul design.

  • Mentre la conversazione si svolge in modalità occhiali, l'app scrive silenziosamente le schede di vocabolario in background.

  • La parola, la traduzione, la frase di esempio e la foto dal momento in cui fai la domanda.

  • L'utente non deve toccare manualmente nulla.

  • Stanno solo camminando e parlando.

  • Poi, più tardi, trovano tutto sul loro smartphone, che è esattamente ciò che volevo quando ero a Tokyo.

  • Quindi, audio-first non significa necessariamente solo audio.

  • Gli occhiali sono la superficie giusta per la curiosità e lo smartphone è la superficie giusta per la riflessione successiva.

  • Lascia che il momento a mani libere lasci un segno.

  • Ora, diamo un'occhiata ad alcuni suggerimenti pratici per creare esperienze in tempo reale con Gemini Nano 4 e ML Kit che abbiamo appreso durante la creazione di questa demo.

Prioritizzazione della latenza in tempo reale con progettazione a due passaggi

  • Suggerimento 1: dai la priorità alla latenza in tempo reale con un design a due passaggi.

  • Sugli occhiali audio, quando chiedi "Cos'è?"

  • Ogni momento di silenzio è evidente.

  • Nell'esempio di XR Language Teacher, per ogni turno dell'interazione dell'utente con il modello abbiamo bisogno di due cose: una risposta vocale immediata e una scheda di vocabolario strutturata da salvare nell'app.

  • Se chiediamo al modello di fare due cose insieme in un unico passaggio, ogni token utilizzato per la generazione dei dati in background ritarderà la prima frase pronunciata.

  • Pertanto, separiamo ciò di cui l'utente ha bisogno in questo momento da ciò di cui l'app ha bisogno pochi secondi dopo.

  • Per questo motivo, abbiamo suddiviso ogni turno in due fasi.

  • Nella fase 1, trasmettiamo in streaming il frame della fotocamera e la domanda solo per la risposta vocale, attivando la sintesi vocale non appena viene completata la prima frase.

  • E poiché la TPU o la NPU finisce di generare la risposta breve molto prima che la sintesi vocale finisca di parlare ad alta voce, abbiamo una finestra di diversi secondi in cui l'utente sta ancora ascoltando e il chip è già inattivo.

  • E poiché il Passaggio 1 ha già identificato l'oggetto nella sua risposta di testo, il Passaggio 2 non ha nemmeno bisogno dell'input dell'immagine.

  • Passiamo semplicemente la risposta del Passaggio 1 al chip inattivo per estrarre e salvare la scheda strutturata prima ancora che l'insegnante finisca di parlare.

  • Ora, concentriamoci sulla fase 1, che mi porta al secondo suggerimento.

  • Utilizza tag XML leggeri durante lo streaming.

  • Poiché la fase 1 trasmette direttamente a Text-to-Speech, non è consigliabile che un oggetto JSON pesante ritardi la prima parola pronunciata.

  • Invece, è preferibile un testo normale che possa essere pronunciato immediatamente con tag leggeri e in linea ogni volta che l'app deve passare all'azione durante lo stream.

  • Quindi, lascia che ti mostri il problema che ci ha portato qui.

  • Ricordi il giocattolo di legno con la tazza e la pallina sulla bancarella del mercato di cui ho parlato?

  • In giapponese si chiama "kendama".

  • All'inizio, il modello lo ha riconosciuto dal frame della videocamera e ha restituito la risposta corretta: "È un kendama, un giocattolo di legno".

  • Abbiamo quindi inserito la frase completa nella sessione di sintesi vocale in inglese ed ecco il risultato.

  • È un ken-mi, un giocattolo di legno.

  • Non sembra del tutto corretto.

  • Invece, la frase dovrebbe essere…

  • È un kendama, un giocattolo di legno.

  • Perché è successo?

  • Kendama è scritto con l'hiragana "ken" e il kanji "dama".

  • Il motore di sintesi vocale inglese gestisce bene l'hiragana fonetico come "ken", ma non riesce a gestire la parte kanji e trasforma dama in qualcosa che suona come me.

  • E questo accade in realtà in molte lingue diverse in modi diversi se non cambi le impostazioni internazionali.

  • Ad esempio, in spagnolo potresti sentire…

  • Suh-BOH-luh…

  • Quando aspetti un bambino…

  • Cebolla.

  • Allo stesso modo, in francese, potresti sentire…

  • PAIN oh chock-uh-lat…

  • Quando invece aspetti…

  • Pain au chocolat.

  • Sì, una sessione TTS è solitamente progettata per una singola lingua alla volta, ma un insegnante di lingue deve cambiare codice in ogni singola frase.

  • Quindi, nel passaggio 1, chiediamo al modello di inserire ogni parola o frase della lingua di destinazione all'interno del tag XML di destinazione durante lo streaming.

  • Man mano che arrivano i segmenti, il nostro client li suddivide in base al target, indirizza ogni intervallo alla lingua di Text-to-Speech corrispondente, che potrebbe essere giapponese, spagnolo, francese o qualsiasi lingua tu stia imparando, e poi li riproduce senza interruzioni, cambiando accento a metà frase, in modo che parole come kendama vengano pronunciate correttamente.

  • Quindi, il concetto chiave è che il modello sa già quale vocabolario appartiene a quale lingua, quali parole appartengono a quale lingua e l'aggiunta di tag agli intervalli di destinazione in linea durante lo streaming è molto più pulita rispetto alla post-elaborazione di tutto questo in un secondo momento.

  • Ecco il codice per il passaggio 1.

    // 1. Pass 1 System + Turn Prompt (XrLanguageTeacherPersona.kt)
    // System: "Always start immediately with <reply>...</reply> (strictly 1 to 3 sentences total).
    //   Inside <reply>, wrap every Japanese word AND every complete Japanese example sentence
    //   inside <target>...</target> tags (never output <card>)."
    val pass1TurnPrompt = """
      The student is looking at the camera view and asked: "$question". Answer in English
      teaching Japanese inside <reply>...</reply>: if they refer to 'this'/'that' or point to
      an object, ground it to the focal item in the camera frame; if they ask for a phrase or
      greeting, teach that phrase.
    """.trimIndent()
    
    // 2. Stream multimodal dialogue (ImagePart + TextPart) and split <target> tags to TTS
    val generativeModel = Generation.getClient()
    val pass1Request = generateContentRequest(ImagePart(frame), TextPart(pass1TurnPrompt)) {
      temperature = 1.0f; topK = 1; maxOutputTokens = 1024
    }
    generativeModel.generateContentStream(pass1Request).collect { chunk ->
      val delta = chunk.candidates.firstOrNull()?.text.orEmpty()
      streamReplyAndSplitTargetTagsToTts(delta) // Speaks 1st complete sentence immediately
    }
    // Streams: "<reply>That's <target>けん玉</target> — a wooden toy. <target>けん玉をください。</target></reply>"
  • In alto, utilizziamo il prompt per indicare al modello di rispondere con una o tre frasi all'interno dei tag di risposta e poi di inserire ogni parola o frase di esempio in giapponese all'interno dei tag target.

  • Poi passiamo la fotocamera ImagePart e il prompt TextPart al generateContentStream di ML Kit, suddividendo gli intervalli target in sintesi vocale man mano che arriva ogni frase.

  • E questo mi porta alla seconda fase e al nostro terzo suggerimento.

  • Sebbene l'utilizzo di tag XML leggeri sia ottimo per uno stream audio in diretta, utilizza l'output strutturato, progettato per restituire un oggetto pulito e tipizzato durante il salvataggio dei dati nella tua app.

  • Poiché il nostro XR Language Teacher è conversazionale, gli utenti possono anche porre domande aggiuntive in cui non viene insegnata nessuna nuova parola.

  • Quando viene insegnata una parola, il database ha bisogno di campi del dizionario puliti per creare le schede del vocabolario.

  • Quindi, invece di scrivere espressioni regolari fragili per analizzare la trascrizione del passaggio 1, lasciamo che il passaggio 2 estragga l'oggetto con sicurezza dei tipi in background.

  • Per applicare lo schema nel passaggio 2, utilizziamo l'API di output strutturato di ML Kit, che utilizza la decodifica vincolata.

    import com.google.mlkit.genai.prompt.Generation
    import com.google.mlkit.genai.prompt.generateTypedContentRequest
    import com.google.mlkit.genai.schema.annotations.Generable
    import com.google.mlkit.genai.schema.annotations.Guide
    
    // Pass 2 schema (XrLanguageTeacherGenerableSchemas.kt):
    @Generable(description = "Vocabulary flashcard for the taught word or short phrase")
    data class GenerableVocabCard(
      @Guide(description = TeacherResponseSchema.WORD_FIELD_DESCRIPTION) val word: String,
      @Guide(description = "Native-language meaning or translation") val meaning: String,
      @Guide(description = "Target-language example sentence") val example: String,
      @Guide(description = "Native-language translation of the example sentence") val example_translation: String,
    )
    
    @Generable(description = "Optional vocabulary card extracted from a teacher reply")
    data class GenerableOptionalVocabCardWrapper(
      @Guide(description = "Vocabulary flashcard if a vocabulary term was taught, or null otherwise")
      val card: GenerableVocabCard?
    )
  • Basta annotare una classe di dati con @Generable e ogni campo con @Guide, proprio come nell'esempio della classe della scheda del vocabolario qui.

  • Inoltre, eseguendo il wrapping del campo della scheda nullable, il passaggio 2 restituisce una scheda compilata quando viene insegnata una nuova parola o null nei turni puramente conversazionali.

  • Ecco il codice per il passaggio 2.

    // 1. Pass 1 Streamed Output -> Pass 2 Extraction Prompt (no image needed!)
    val replyWithTargetTags =
      "<reply>That's <target>けん玉</target> — a wooden toy. <target>けん玉をください。</target></reply>"
    
    val pass2Prompt = """
      If the teacher reply below teaches or introduces a specific Japanese vocabulary item, extract
      only its core focal dictionary term into "card" with keys "word", "meaning", "example", and
      "example_translation". If purely conversational with no focal term taught, set "card" to null:
      $replyWithTargetTags
    """.trimIndent()
    
    // 2. Extract @Generable card on idle NPU while TTS audio plays
    val pass2Request = generateContentRequest(TextPart(pass2Prompt)) {
      temperature = 1.0f; topK = 1; maxOutputTokens = 256
    }
    val typedRequest =
      generateTypedContentRequest(pass2Request, GenerableOptionalVocabCardWrapper::class)
    val card: GenerableVocabCard? =
      generativeModel.generateContent(typedRequest).candidates.firstOrNull()?.response?.card
    // Extracts: {"word": "けん玉", "meaning": "a wooden toy", "example": "けん玉をください。", ...}
  • In alto, prendiamo il testo della risposta in streaming del passaggio 1, senza bisogno di immagini, e creiamo il nostro prompt di estrazione.

  • Poi, mentre l'utente sta ancora ascoltando la sintesi vocale, passiamo il prompt e la nostra classe wrapper a generateTypedContentRequest ed estraiamo una scheda di vocabolario pulita e senza tag o null se non è stata insegnata alcuna parola.

  • Okay.

  • Abbiamo trattato molti argomenti.

  • In questo momento è ragionevole pensare: "Fantastico, un'altra cosa che sembra bellissima sul palco, ma che non toccherò mai".

  • Non questo!

  • XR Language Teacher viene fornito nell'app Google AI Edge Gallery insieme ad altre esperienze di AI on-device.

  • Puoi scaricarla oggi stesso, installarla sul tuo smartphone e inquadrare qualsiasi cosa per scoprire come si dice in un'altra lingua.

  • Se vuoi sapere come è stata creata, l'intera implementazione è open source su GitHub.

  • Non preoccuparti se non tutti i tuoi utenti hanno già occhiali audio o con display o dispositivi Android flagship supportati.

  • La nostra codebase mostra anche esempi di come questa stessa esperienza può essere adattata per un'esperienza solo mobile.

  • Se vuoi raggiungere anche altre piattaforme, il codice dell'app galleria Google AI Edge mostra anche come creare questa funzionalità utilizzando il runtime Google LiteRT e le API MediaPipe Tasks.

  • In alternativa, se vuoi utilizzare altri modelli linguistici o audio on-device open source, puoi anche vedere come farlo dal codice.

Adattamento ad altri casi d'uso con gli agenti AI

  • Per questo motivo, abbiamo reso XR Language Teacher open source per semplificare la prototipazione.

  • Indirizza l'agente che preferisci al nostro repository GitHub per trasformare questa esperienza on-device in tempo reale basata sull'AI in qualcos'altro.

  • Potrebbe essere una guida museale, un assistente di cucina che guarda il tuo bancone, una guida per le riparazioni, un narratore di accessibilità, uno strumento di ispezione sul campo.

  • Il modello, il runtime, l'implementazione open source e gli strumenti sono tutti disponibili ora, così puoi prepararti per il lancio degli occhiali intelligenti in arrivo questo autunno.

  • Non vediamo l'ora di vedere cosa creerai.

  • Per concludere, oggi abbiamo mostrato esempi significativi di ciò che è possibile fare con i modelli di AI eseguiti senza problemi on-device.

  • Ecco alcuni modi per iniziare.

  • Per prima cosa, prendi le risorse sullo schermo.

  • Scarica l'app AI Edge Gallery per provare la demo e dai un'occhiata al codice di riferimento su GitHub.

  • Scansiona i codici QR per accedere all'SDK Android XR, alle API ML Kit e alle offerte di Google AI Edge.

  • In secondo luogo, inizia a sperimentare direttamente sullo smartphone oggi stesso.

  • Abbiamo trattato molti aspetti che possono già essere realizzati con un'esperienza mobile-first.

  • Infine, esamina l'app mobile che stai già distribuendo e chiediti quali funzionalità principali sarebbero migliori con comandi vocali.

  • Per portare questa esperienza anche sugli occhiali, basta aggiungere un'attività proiettata all'APK esistente.

  • Grazie mille per aver partecipato oggi.

  • Non vediamo l'ora di scoprire le incredibili esperienze audio-first che creerai su Android e per gli occhiali intelligenti.

  • Grazie.

Codice

Snippet di codice

Snippet Kotlin al minuto 02:39 in "Sviluppo per Android XR"
dependencies {
   implementation("androidx.xr.projected:projected:1.0.0-alpha13")
}
Snippet Kotlin al minuto 02:45 in "Sviluppo per Android XR"
class ProjectedMainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           // Glasses content
       }
   }
}
Snippet XML al minuto 03:02 in "Sviluppo per Android XR"
<activity
   android:name=".ProjectedMainActivity"
   android:exported="false"
   android:requiredDisplayCategory="android.hardware.display.category.XR_PROJECTED"
   android:label="Example activity for audio glasses">
   <intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.XR_PROJECTED_LAUNCHER"/>
   </intent-filter>
</activity>
Snippet di Kotlin alle 03:44 in "Sviluppo per Android XR"
// Continuously monitor whether the glasses are connected
val isGlassesConnected by ProjectedContext.isProjectedDeviceConnected(
   context,
   scope.coroutineContext
).collectAsStateWithLifecycle(initialValue = false)

// Launch the activity
val options = ProjectedContext.createProjectedActivityOptions(context)
val intent = Intent(context, ProjectedMainActivity::class.java)
context.startActivity(intent, options.toBundle())

// Check if the device is audio only or has a display
val projectedDeviceController = ProjectedDeviceController.create(this@ProjectedMainActivity)
isVisualUiSupported = projectedDeviceController.capabilities.contains(CAPABILITY_VISUAL_UI)
Snippet di Kotlin al minuto 05:05 in "Sviluppo per Android XR"
// In a projected activity
private fun requestProjectedPermissions() {
   lifecycleScope.launch {
       try {
           ProjectedActivityCompat.requestPermissions(
               this@PermissionsFromProjectedActivity,
               arrayOf(Manifest.permission.CAMERA),
               // CAMERA_PERMISSION_REQUEST_CODE is a developer defined constant
               requestCode = CAMERA_PERMISSION_REQUEST_CODE,
           )
       } catch (e: IllegalStateException) {
           // Thrown when the projected system service can't be bound.
           Log.e(TAG, "Request failed: projected service unavailable.", e)
       }
   }
}
Snippet di shell al minuto 05:46 in "Sviluppo per Android XR"
android skills add projected-hardware
android skills add --all
Snippet Kotlin al minuto 08:14 in "Designing audio-first experiences"
dependencies {
   // ML Kit GenAI TTS SDK
   implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
}
Snippet Kotlin alle 08:26 in "Designing audio-first experiences"
// 1. Initializing the SpeechSynthesizer client.
val options = SpeechSynthesizerOptions.Builder(Locale.US).build()
val client = SpeechSynthesis.getClient(options)

// 2. Check and download the voice model.
val voiceInfo = client.getSupportedVoices().firstOrNull()

if (voiceInfo != null && client.checkStatus(voiceInfo) == FeatureStatus.DOWNLOADABLE) {
   client.download(voiceInfo).collect { status -> println("Download status: $status") }
} else if (voiceInfo == null) {
   // Tip: Fallback to system TTS if ML Kit voice is unavailable
}
Snippet di Kotlin al minuto 09:26 in "Designing audio-first experiences"
// 3. Construct the SpeechSynthesizerRequest
val request = SpeechSynthesizerRequest(
   input = SpeechSynthesizerRequest.Input.Text("Hello world", voiceInfo),
   audioAttributes = AudioAttributes.Builder()
       .setUsage(AudioAttributes.USAGE_MEDIA)
       .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
       .build()
)

// 4. Speak and collect status
client.synthesize(request).collect { response ->
   when (response) {
       is SpeechSynthesizerResponse.StatusUpdate -> println("Playback status: ${response.status}")
   }
}
Snippet Kotlin al minuto 09:47 in "Designing audio-first experiences"
// 4. Speak and collect status
client.synthesize(request).collect { response ->
   when (response) {
       is SpeechSynthesizerResponse.StatusUpdate -> println("Playback status: ${response.status}")
   }
}

// 5. Release resources when finished (e.g., on Activity destroy)
override fun onDestroy() {
   super.onDestroy()
   client.close()
}
Snippet Kotlin al minuto 10:29 in "Designing audio-first experiences"
// 1. Register Earcon resource with Builder
SpeechSynthesizerOptions.Builder(Locale.US)
   .addEarcon(
       name = "shutter_sound",
       Uri = "path/to/earcon.file".toUri()
   )
   .build()

// 2. Request Earcon playback
val request = SpeechSynthesizerRequest(
   input = SpeechSynthesizerRequest.Input.Earcon(
       name = "shutter_sound"
   ),
   audioAttributes = AudioAttributes.Builder()
       .setUsage(AudioAttributes
           .USAGE_ASSISTANCE_SONIFICATION)
       .setContentType(AudioAttributes
           .CONTENT_TYPE_SONIFICATION)
       .build()
)
Snippet di Kotlin al minuto 13:42 in "Designing audio-first experiences"
dependencies {
   // ML Kit Speech Recognition
   implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
}
Snippet di Kotlin al minuto 13:52 in "Designing audio-first experiences"
suspend fun startMyRecognition() {
   // choose locale and mode
   val options: SpeechRecognizerOptions =
       speechRecognizerOptions {
           locale = Locale.US
           preferredMode = SpeechRecognizerOptions.Mode.MODE_ADVANCED
       }

   val speechRecognizer: SpeechRecognizer = SpeechRecognition.getClient(options)

   val request: SpeechRecognizerRequest =
       speechRecognizerRequest { audioSource = AudioSource.fromMic() }
...
Snippet di Kotlin al minuto 14:12 in "Designing audio-first experiences"
speechRecognizer.startRecognition(request).collect { response ->
   when (response) {
       is SpeechRecognizerResponse.PartialTextResponse -> {
           // Real-time: Act immediately if trigger phrase is heard
           handleResponse(response.text, speechRecognizer)
       }
       is SpeechRecognizerResponse.FinalTextResponse -> {
           // Final result and recognition
           handleResponse(response.text, speechRecognizer)
       }
       is SpeechRecognizerResponse.CompletedResponse -> {
           resetListeningUi()
       }
       is SpeechRecognizerResponse.ErrorResponse -> {
           Log.e("SpeechRec", "Error: ${response.e.errorCode}", response.e)
           resetListeningUi()
       }
...
Snippet Kotlin al minuto 14:41 in "Designing audio-first experiences"
// Helper function to check for multiple phrases and trigger the right action
private suspend fun handleResponse(text: String, recognizer: SpeechRecognizer) {
   val savePhrases = listOf("save", "bookmark", "remember")
   if (savePhrases.any { text.contains(it) }) {

       // 1. Update app state immediately
       saveItemForLater()

       // 2. Stop the recognizer to enforce the action and prevent TTS echo
       recognizer.stopRecognition()

       // 3. Play the mute earcon for immediate, non-verbal confirmation
       playEarcon()

       // 4. Provide voice feedback
       playAudioFeedback("Saved")
   }
}
Snippet di Kotlin al minuto 26:27 in "Prioritizing real-time latency with two-pass design"
// 1. Pass 1 System + Turn Prompt (XrLanguageTeacherPersona.kt)
// System: "Always start immediately with <reply>...</reply> (strictly 1 to 3 sentences total).
//   Inside <reply>, wrap every Japanese word AND every complete Japanese example sentence
//   inside <target>...</target> tags (never output <card>)."
val pass1TurnPrompt = """
  The student is looking at the camera view and asked: "$question". Answer in English
  teaching Japanese inside <reply>...</reply>: if they refer to 'this'/'that' or point to
  an object, ground it to the focal item in the camera frame; if they ask for a phrase or
  greeting, teach that phrase.
""".trimIndent()

// 2. Stream multimodal dialogue (ImagePart + TextPart) and split <target> tags to TTS
val generativeModel = Generation.getClient()
val pass1Request = generateContentRequest(ImagePart(frame), TextPart(pass1TurnPrompt)) {
  temperature = 1.0f; topK = 1; maxOutputTokens = 1024
}
generativeModel.generateContentStream(pass1Request).collect { chunk ->
  val delta = chunk.candidates.firstOrNull()?.text.orEmpty()
  streamReplyAndSplitTargetTagsToTts(delta) // Speaks 1st complete sentence immediately
}
// Streams: "<reply>That's <target>けん玉</target> — a wooden toy. <target>けん玉をください。</target></reply>"
Snippet Kotlin al minuto 27:33 in "Prioritizing real-time latency with two-pass design"
import com.google.mlkit.genai.prompt.Generation
import com.google.mlkit.genai.prompt.generateTypedContentRequest
import com.google.mlkit.genai.schema.annotations.Generable
import com.google.mlkit.genai.schema.annotations.Guide

// Pass 2 schema (XrLanguageTeacherGenerableSchemas.kt):
@Generable(description = "Vocabulary flashcard for the taught word or short phrase")
data class GenerableVocabCard(
  @Guide(description = TeacherResponseSchema.WORD_FIELD_DESCRIPTION) val word: String,
  @Guide(description = "Native-language meaning or translation") val meaning: String,
  @Guide(description = "Target-language example sentence") val example: String,
  @Guide(description = "Native-language translation of the example sentence") val example_translation: String,
)

@Generable(description = "Optional vocabulary card extracted from a teacher reply")
data class GenerableOptionalVocabCardWrapper(
  @Guide(description = "Vocabulary flashcard if a vocabulary term was taught, or null otherwise")
  val card: GenerableVocabCard?
)
Snippet Kotlin al minuto 28:00 in "Prioritizing real-time latency with two-pass design"
// 1. Pass 1 Streamed Output -> Pass 2 Extraction Prompt (no image needed!)
val replyWithTargetTags =
  "<reply>That's <target>けん玉</target> — a wooden toy. <target>けん玉をください。</target></reply>"

val pass2Prompt = """
  If the teacher reply below teaches or introduces a specific Japanese vocabulary item, extract
  only its core focal dictionary term into "card" with keys "word", "meaning", "example", and
  "example_translation". If purely conversational with no focal term taught, set "card" to null:
  $replyWithTargetTags
""".trimIndent()

// 2. Extract @Generable card on idle NPU while TTS audio plays
val pass2Request = generateContentRequest(TextPart(pass2Prompt)) {
  temperature = 1.0f; topK = 1; maxOutputTokens = 256
}
val typedRequest =
  generateTypedContentRequest(pass2Request, GenerableOptionalVocabCardWrapper::class)
val card: GenerableVocabCard? =
  generativeModel.generateContent(typedRequest).candidates.firstOrNull()?.response?.card
// Extracts: {"word": "けん玉", "meaning": "a wooden toy", "example": "けん玉をください。", ...}