Android XR

Criar experiências de áudio para óculos inteligentes com o Gemini Nano 4

Alto-falantes

Sobre

Sobre a palestra

Descubra como criar experiências de áudio robustas e viva-voz com a IA no dispositivo. Vamos abordar tudo o que você precisa: configurar a atividade dos óculos, capturar feeds de microfone e transmissões da câmera e usar a voz com outras dicas de áudio como sua interface definitiva. Em uma demonstração real do XR Language Teacher, um app de código aberto criado no app Google AI Edge Gallery, aprenda a usar o Gemini Nano 4 (criado com base no Gemma 4 e otimizado para dispositivos Android) e o Kit de ML, além de aproveitar o ambiente de execução do LiteRT para criar experiências de XR rápidas que ouvem, veem e falam naturalmente.

Resumo

  • A biblioteca Jetpack Projected (Alfa) conecta smartphones Android a óculos inteligentes. Adicionar uma atividade projetada aos seus apps atuais projeta conteúdo para óculos com tela ou somente áudio.
  • O Kit de ML apresenta bibliotecas de fala Alfa no dispositivo para interações com foco em áudio em dispositivos selecionados. Ele oferece conversão de texto em voz com 8 vozes em 21 localidades e reconhecimento de fala com um modo avançado do Gemini Nano no Pixel 10.
  • O Gemini Nano 4 processa frames de câmera e perguntas transcritas juntos em uma única etapa. Ele possibilita experiências off-line ao encadear modelos de fala no dispositivo localmente em smartphones de última geração compatíveis.
  • Um design de duas passagens prioriza a latência de áudio em tempo real nos óculos inteligentes. A Passagem 1 transmite o texto falado usando tags XML inline para alternar a localidade no meio da frase, enquanto a Passagem 2 extrai dados estruturados em segundo plano.
  • A implementação de código aberto do Professor de Idiomas XR é fornecida no app Google AI Edge Gallery. Os desenvolvedores podem adaptar o código para outros casos de uso de assistentes multimodais.

Recursos

Gemini Notebook

Tópicos

  • intelligent-eyewear
  • android-xr
  • gemini-nano
  • on-device-ai
  • ml-kit
  • speech-recognition
  • conversão de texto em voz

Transcrição

Introdução

  • Olá, pessoal!

  • É ótimo ver todos vocês hoje.

  • Agradecemos sua presença.

  • Sou a Amy.

  • Hoje, minha colega Alice vai se juntar a mim para falar sobre como vocês podem criar experiências incríveis com foco em áudio para óculos inteligentes usando o Gemini Nano 4.

  • Vamos começar.

  • Quando falamos de óculos inteligentes, estamos nos referindo a óculos portáteis e leves, feitos para serem usados o dia todo.

  • Essa categoria inclui óculos com tela e somente áudio, e esses dispositivos têm hardware variado.

  • A primeira coisa que você vai notar é que eles têm um touchpad, que pode ser usado para entrada do usuário, como toques e deslizamentos.

  • Eles também têm alto-falantes, uma câmera e um microfone.

  • Eles também podem ter uma tela ou não, ou uma combinação dessas opções.

  • Hoje, vamos falar sobre o áudio em primeiro lugar e como criar experiências envolventes, inteligentes e em qualquer lugar.

  • E uma das coisas interessantes sobre o áudio é que a voz se torna sua interface do usuário.

  • E a IA no dispositivo é ótima para desenvolvedores que buscam experiências off-line e em qualquer lugar.

Desenvolvimento para Android XR

  • E temos ótimas notícias.

  • Se você já desenvolve para Android, já está desenvolvendo para o Android XR.

  • Os apps Android atuais foram projetados para funcionar bem com óculos com áudio, enviando notificações por áudio e outros sons por Bluetooth.

  • E se você quiser ir além e diferenciar seu app para XR, há muitas maneiras de fazer isso.

  • Nesta palestra, vamos falar sobre modelos no dispositivo, mas essa não é uma lista completa.

  • Também há modelos de nuvem.

  • Há recursos como a API Gemini Live.

  • Há agentes do sistema com os quais você pode se integrar, como o Gemini, e todos eles podem ajudar você a criar a melhor experiência.

  • Como criar essa experiência que ouve, vê e fala naturalmente?

  • Hoje, vamos mostrar como criar essas experiências robustas e viva-voz, que usam IA no dispositivo.

  • Vou explicar o básico: como criar para óculos, como configurar sua atividade, conseguir as permissões necessárias e usar o Kit de ML para a interface de conversação.

  • Depois, vou passar a palavra para Alice, que vai mostrar alguns exemplos reais que usam o Gemini Nano 4.

  • Vamos analisar o código.

  • Com os óculos, o smartphone faz o trabalho pesado, e o conteúdo é projetado do smartphone para os óculos.

  • Portanto, você precisa de duas coisas para conectar esses dispositivos ao configurar pela primeira vez.

  • Primeiro, você vai precisar da biblioteca Jetpack Projected.

  • Você precisa adicionar essa dependência, que vai conter as APIs para alimentar essa experiência projetada.

    dependencies {
       implementation("androidx.xr.projected:projected:1.0.0-alpha13")
    }
  • Depois, você também vai precisar criar uma nova atividade especificamente para os óculos.

    class ProjectedMainActivity : ComponentActivity() {
       override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
           setContent {
               // Glasses content
           }
       }
    }
  • Em seguida, você vai usar essa biblioteca do Jetpack Projected no app atual e na nova atividade para que os dois dispositivos possam se comunicar sem problemas sempre que necessário.

  • Assim como qualquer atividade normal que você já viu, declare isso no manifesto.

    <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>
  • A principal diferença é que você precisa usar o XR_PROJECTED como o requiredDisplayCategory.

  • Isso é para que o sistema saiba que essa atividade é para óculos.

  • E você também vai querer usar esta categoria de intent aqui, XR_PROJECTED_LAUNCHER.

  • Isso permite que o Gemini descubra seu app para que os usuários possam iniciá-lo por comando de voz em qualquer lugar.

  • Certo.

  • Depois de configurar essa atividade, como mencionei, você vai usar a biblioteca Projected para potencializar a experiência no app.

  • Essa biblioteca contém todas as ferramentas necessárias para criar a experiência completa dos óculos.

  • Vamos analisar algumas das principais APIs que você vai usar aqui.

  • O primeiro que quero destacar é o método 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)
  • É assim que você vai monitorar continuamente a conexão dos óculos.

  • Você usa isso com um ciclo de vida do dispositivo para saber quando os óculos estão realmente disponíveis, não apenas quando estão pareados ou ligados, mas também quando estão sendo usados e estão prontos para uso.

  • E é isso, então, sim.

  • Assim, você vai saber exatamente quando estiver tudo pronto para iniciar a atividade.

  • Para fazer isso, quando estiver tudo pronto para iniciar a atividade, você precisará definir algumas opções específicas.

  • Para isso, você vai usar o createProjectedActivityOptions no ProjectedContext.

  • E, novamente, você vai usar isso com o contexto na intent.

  • Outra ferramenta que você vai querer usar é um ProjectedDeviceController.

  • É assim que você vai verificar o tipo de óculos que o usuário está usando.

  • Como eu disse antes, os óculos inteligentes incluem óculos com tela e somente áudio. Você vai querer ver se os recursos contêm esse CAPABILITY_VISUAL_UI, o que vai permitir que você adapte seu app de forma adequada para diferentes dispositivos e personalize uma experiência que otimize para esse dispositivo.

  • Ao criar essa experiência diferenciada, talvez você queira acessar o hardware do dispositivo, por exemplo, usando a câmera dos óculos.

  • Para fazer isso em uma atividade projetada, você precisa solicitar as permissões adequadas usando 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)
           }
       }
    }
  • Mesmo que o app já tenha as permissões necessárias para o smartphone, você ainda precisa solicitar as permissões específicas para o hardware dos óculos.

  • E, ao trabalhar com permissões, lembre-se de seguir as práticas recomendadas para o Android em geral.

  • Declare as permissões necessárias no manifesto, peça permissões no contexto em que elas são necessárias e solicite apenas as permissões que você realmente precisa para que o app funcione.

  • Isso está documentado no nosso site developer.android.com.

  • Mas também temos uma habilidade totalmente nova, a de hardware projetado.

  • Você pode usar isso com a CLI do Android no Android Studio para configurar seu fluxo de trabalho de permissões e ter acesso a essa habilidade, além de todas as outras habilidades do Android.

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

Como criar experiências com foco em áudio

  • Agora que estamos conectados, cientes dos recursos de hardware e temos as permissões certas, vamos falar sobre as ferramentas que você tem para criar essa experiência de áudio.

  • Por isso, ao projetar uma experiência de áudio, queremos garantir que ela seja de alta qualidade para os usuários.

  • Isso pode incluir algumas interações importantes.

  • A primeira é usar a voz como saída, permitindo que sua voz fale naturalmente com o usuário.

  • Em seguida, use ícones auditivos para enviar sinais de áudio específicos ao usuário.

  • E também processar a entrada do usuário, seja pelo touchpad usando toques e deslizes, ou se você quiser permitir que o usuário fale com o app.

  • A primeira coisa que vamos falar é sobre o uso da voz como saída.

  • Para isso, quero apresentar a nova biblioteca de conversão de texto em voz da GenAI do Kit de ML.

  • Esse recurso já está disponível em versão Alfa em alguns dispositivos Pixel e Samsung.

  • Estamos muito animados com isso, principalmente para as experiências que priorizam o áudio.

  • Se você já trabalhou com a conversão de texto em voz, provavelmente sabe que um dos problemas é que a voz do sistema nem sempre soa tão natural quanto você gostaria.

  • Além disso, a conexão com modelos de nuvem para outras vozes pode causar latência ou custos adicionais.

  • Assim, o Kit de ML oferece uma fala muito mais natural em comparação com a conversão de texto em voz do sistema.

  • O Kit de ML oferece oito vozes exclusivas com características diferentes para atender ao seu app.

  • Além disso, o Kit de ML oferece suporte robusto a idiomas com 21 localidades diferentes para você escolher.

  • Além disso, um dos grandes benefícios é que, após o download inicial, ele vai funcionar completamente no dispositivo.

  • Portanto, esse modelo será baixado no sistema e compartilhado entre os apps, sem aumentar o tamanho do APK.

  • E não só está disponível off-line, o que é especialmente importante para usuários de óculos que estão em movimento, que podem não ter Wi-Fi ou estar em áreas de cobertura ruim, mas também é provável que você tenha um desempenho melhor, já que não precisa fazer essa chamada de ida e volta para um servidor.

  • Certo.

  • Vamos analisar os detalhes da biblioteca e como podemos criar com ela.

  • Primeiro, é claro, você precisa adicionar a dependência.

    dependencies {
       // ML Kit GenAI TTS SDK
       implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
    }
  • Aqui, você só precisa do SDK de conversão de texto em voz do Kit de ML.

  • Depois, você pode começar a escrever o código.

  • Por isso, a biblioteca do Kit de ML da GenAI inclui as APIs 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
    }
  • Isso oferece uma maneira simples de implementar as vozes que você escolher.

  • A primeira etapa é inicializar o cliente usando o sintetizador de voz novamente, definindo a localidade que você escolher.

  • Em seguida, verifique o status do download e selecione a voz.

  • Neste exemplo, estamos escolhendo a primeira voz, mas também quero destacar que estamos usando firstOrNull.

  • Como mencionei, o Kit de ML requer um smartphone específico, como os dispositivos Samsung ou Pixel.

  • Portanto, se você receber um valor nulo, isso pode significar que o usuário não está usando um smartphone compatível.

  • Depois, é só garantir que você tenha um substituto, seja voltando para a conversão de texto em voz do sistema ou reproduzindo uma mensagem gravada ou algo parecido.

  • Certo, e depois você vai usar a síntese de fala SpeechSynthesizerRequest para criar a solicitação, fornecendo o texto de entrada e definindo o uso e o tipo de conteúdo.

    // 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}")
       }
    }
  • Reproduza a fala, monitore o status e saiba quando ela termina. Depois, colete os erros que podem ocorrer.

  • E, em seguida, feche os recursos quando necessário.

    // 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()
    }
  • Isso é bem simples, certo?

  • Com essas etapas, você pode transformar o som do seu app, que antes era assim…

  • Olá, desenvolvedores Android.

  • É ótimo ver todos vocês hoje.

  • Esperamos que vocês estejam gostando da nossa apresentação sobre experiências que priorizam o áudio.

  • Para algo que soa mais como isto.

  • Olá, desenvolvedores Android.

  • É ótimo ver todos vocês hoje.

  • Esperamos que vocês estejam gostando da nossa apresentação sobre experiências que priorizam o áudio.

  • Bem melhor, né?

  • Ok.

  • A biblioteca de conversão de texto em voz do Kit de ML é uma ferramenta muito poderosa, mas você não está limitado a usar essas vozes. Também é possível tocar ícones auditivos com essa biblioteca.

    // 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()
    )
  • Os ícones auditivos, como o nome indica, são basicamente o equivalente em áudio dos ícones.

  • Isso permite que você reproduza sons curtos para se comunicar com o usuário.

  • E, assim como os ícones visuais com ícones auditivos, o usuário vai precisar saber o que eles são e o que significam.

  • Por isso, no início, é bom usar as notificações por voz para deixar isso claro.

  • Já criamos alguns sons padronizados que estão disponíveis no Android Design Hub. Você pode baixar e usar esses sons para ações como abrir e fechar o app, notificações de erro e ativar ou desativar o som.

  • Mas você também pode criar seus próprios ícones auditivos personalizados.

  • Por isso, recomendamos seguir algumas práticas recomendadas ao usar ícones auditivos, sejam eles baixados do Design Hub ou criados por você.

  • Por isso, é importante pensar bem em como e quando usar os ícones auditivos.

  • Então, é importante lembrar de algumas coisas, como não usar ícones auditivos em excesso.

  • Não repita os mesmos sons várias vezes, porque isso pode causar o que é chamado de fadiga auditiva.

  • E lembre-se de que os ícones auditivos precisam ser superintuitivos. Use também tons e ritmos familiares ao implementá-los.

  • Além disso, evite ícones auditivos muito complexos que precisam de explicação. Talvez seja melhor usar uma notificação por voz.

  • E lembre-se de que o que pode parecer sutil no estúdio ou no seu sistema, nos alto-falantes do computador, pode se tornar irritante após a reprodução repetida para os usuários.

  • Por isso, faça o possível para não sobrecarregar os usuários com sons que possam confundi-los ou levá-los a ignorar alertas importantes.

  • Certo.

  • Agora, podemos fornecer dicas e feedback em áudio, seja por fala natural ou usando ícones auditivos.

  • Mas e a entrada?

  • Há um touchpad ou botões dedicados no dispositivo que podem ser usados para criar interações de toque e deslize.

  • Mas talvez você também queira permitir essa interface de conversação. Para isso, use a API GenAI Speech Recognition do Kit de ML.

  • Atualmente em versão Alfa e disponível em alguns dispositivos, mas basicamente funciona em dois modos distintos.

  • O modo básico usa o reconhecimento de fala tradicional no dispositivo e está disponível em dispositivos Android com nível da API 31 e versões mais recentes.

  • E também há o modo avançado, que usa o Gemini Nano, disponível nos dispositivos Pixel 10 e versões mais recentes. Com ele, você terá melhor qualidade e cobertura de idiomas.

  • Portanto, SpeechRecognizer segue um padrão de código muito semelhante ao de SpeechSynthesizer.

  • Você cria o cliente, verifica se o modelo está disponível, faz o download se necessário, cria a solicitação e libera os recursos.

  • Vamos nos concentrar nas principais diferenças ao criar a solicitação, capturar o áudio do microfone e processar o resultado.

  • Novamente, você precisa adicionar as dependências necessárias.

    dependencies {
       // ML Kit Speech Recognition
       implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
    }
  • Tudo o que você precisa aqui é da API Speech Recognition.

  • Depois, você pode começar a criar sua solicitação.

  • Portanto, use uma função de suspensão ao definir sua lógica de reconhecimento.

    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() }
    ...
  • Em seguida, especifique as opções de reconhecimento de fala, definindo a localidade e o preferredMode.

  • É aqui também que você vai escolher o audioSource e definir a solicitação.

  • Em seguida, você vai iniciar o reconhecimento e coletar uma resposta.

    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()
           }
    ...
  • Portanto, com SpeechRecognizer, você tem quatro respostas possíveis: parcial, final, erro e completa.

  • A parcial vai analisar o stream em tempo real, que é usado para hotwords ou palavras-chave.

  • "Final" é o stream de áudio final depois de analisar e atualizar totalmente, "Completed" é depois que a fala é concluída e "Error" coleta os erros que podem ocorrer.

  • E veja como juntamos tudo isso.

    // 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")
       }
    }
  • Digamos que você queira que um usuário possa usar um comando de voz para pedir ao app que salve algum tipo de conteúdo para mais tarde.

  • Podemos usar um único comando de voz explícito que informamos para eles usarem ou permitir que usem várias palavras-chave e frases.

  • Aqui, estamos permitindo que eles usem: salvar, marcar ou lembrar.

  • Assim que o SpeechRecognizer detectar que o usuário usou uma dessas frases, ele vai salvar o conteúdo, interromper o reconhecimento de fala, tocar um ícone auditivo personalizado e usar a conversão de texto em voz com IA generativa do Kit de ML para informar ao usuário que o conteúdo foi salvo.

Estudo de caso do Professor de Idiomas em XR

  • Ok.

  • Então, passamos por muita coisa.

  • Pode parecer um pouco abstrato.

  • Se você é como eu, quer ver alguns exemplos reais.

  • Alice e eu queremos mostrar exatamente isso.

  • Ela vai mostrar como funciona o Professor de Idiomas em XR no app Galeria do Google AI Edge.

  • A equipe da Alice criou o recurso, e ela vai mostrar exatamente como ele funciona.

  • É com você.

  • Obrigada, Amy.

  • Sim.

  • Gosto de começar com algo que muitos de vocês reconheceriam.

  • Estou aprendendo japonês e passei horas em apps de idiomas, flashcards, sequências de dias, tudo isso.

  • E aí, eu chego em Tóquio, entro em um mercado e percebo que quase nada do que está na minha frente é o vocabulário que estudei.

  • Há um brinquedo de madeira de bilboquê em uma barraca de mercado que reconheço de vista, mas não sei como falar em japonês.

  • Tem um enfeite na vitrine de uma loja que eu quero comprar, mas não sei o nome.

  • Esse é o momento em que estou mais motivado para aprender uma palavra.

  • Não no meu sofá, olhando para um app de idiomas.

  • Nesse momento, estou olhando para o que quero aprender.

  • Então, é isso que eu quero.

  • Quero usar meus óculos inteligentes o dia todo, só andando por aí.

  • E, quando algo chamar minha atenção, é só perguntar em voz alta.

  • E, à noite, quando eu voltasse para o hotel, tudo o que perguntei estaria me esperando como cartões de vocabulário no meu celular.

  • E foi isso que criamos.

  • Vamos assistir a um clipe curto.

  • Por isso, chamamos de Professor de Idiomas em XR.

  • Olá!

  • O que você quer aprender hoje?

  • E você pode personalizar sua experiência de aprendizado nas configurações do professor.

  • Por exemplo, escolha o idioma que você quer aprender e o perfil de professor ideal para você.

  • Nossa!

  • E você pode alternar entre o modo de óculos e o de smartphone.

  • Como se diz "estas uvas" em espanhol?

  • Olhando para isso, aquelas uvas deliciosas são chamadas de uvas.

  • Em espanhol, você pode dizer "Estas uvas son deliciosas" para descrevê-las.

  • Quero dizer uvas verdes, especificamente.

  • vamos conferir isso.

  • Para especificar as uvas verdes, diga "Uvas verdes".

  • Me gustan las uvas verdes" é uma ótima maneira de dizer que você gosta de uvas verdes.

  • Como se diz isso em francês e como posso pedir três?

  • Vamos conferir.

  • Esses doces deliciosos são chamados de madeleines.

  • Em francês, para pedir três, você pode dizer: "Je voudrais trois madeleines, s'il vous plaît".

  • Como pedir troco com isso?

  • Vamos ver o que temos aqui.

  • Para pedir o troco, você pode dizer: "Otsuri wa arimasu ka?"

  • Que significa "Você tem troco?"

  • Você também pode usar "Otsuri o onegaishimasu" para pedir o troco educadamente.

  • Depois, tudo o que você aprendeu é salvo automaticamente como cards de vocabulário.

  • La botella de agua.

  • Esta es una botella de agua.

  • Sim, e você pode testar sua memória e praticar na guia de estudo.

  • Certo.

  • Sim.

  • Então, tudo o que você acabou de ver foi executado no dispositivo, sem nenhuma rede no loop.

  • Eu viajo com um plano de dados internacional, que geralmente funciona bem até o momento em que entro em um mercado, no metrô ou em um lugar com paredes de concreto espessas.

  • O momento em que você mais precisa disso é, geralmente, quando o sinal está pior.

  • E eu diria que uma experiência que morre quando as barras caem não é um verdadeiro companheiro de viagem. Por isso, fizemos o Professor de Idiomas XR funcionar totalmente off-line e no dispositivo.

Encadeamento de modelos no dispositivo

  • Como isso foi criado?

  • Em termos de arquitetura, a Amy já mostrou como usar um frame de câmera projetado dos óculos para o smartphone.

  • Ela mostrou como usar o Kit de ML e o reconhecimento de fala para transformar texto em fala e também a conversão de texto em voz do Kit de ML para transformar texto em fala.

  • O que ainda precisamos é de um cérebro no meio, algo que possa analisar o frame da câmera e a pergunta juntos e descobrir o que o usuário está realmente perguntando.

  • Esse cérebro é o Gemini Nano 4, criado com base no Gemma 4, o modelo de código aberto do Google que roda localmente no smartphone.

  • Portanto, em dispositivos Android compatíveis, é possível otimizar o desempenho usando as APIs do Kit de ML diretamente.

  • O sistema Android vai cuidar da entrega do modelo para você, então não é necessário agrupar nada no app.

  • Para juntar tudo isso, o microfone entra no reconhecimento de fala, e o frame da câmera e a transcrição entram no Gemini Nano 4, que escreve na síntese de fala e sai do alto-falante.

  • Três modelos, encadeados, todos em execução localmente no seu smartphone.

  • E aqui estão alguns motivos pelos quais o Gemini Nano 4 é especialmente adequado para esse problema.

  • Primeiro, ele vê e lê em uma única passagem.

  • Com o Kit de ML, o Gemini Nano 4 processa textos e imagens de forma nativa, sem precisar de um detector, um classificador e um tradutor.

  • É um modelo que analisa o frame da câmera e a pergunta juntos.

  • A segunda é o suporte a vários idiomas.

  • O Gemini Nano 4 foi treinado em mais de 140 idiomas.

  • Além disso, para o tutor de idiomas, a tradução por si só não é suficiente.

  • Você quer expressões idiomáticas, registros formais e informais e exemplos de frases que uma pessoa real diria.

  • Por fim, ele é controlável o suficiente para ter uma personalidade.

  • Praticar a pronúncia em voz alta pode ser intimidador.

  • E esse modelo segue bem um comando do sistema, então o usuário pode escolher a própria persona de professor.

  • E talvez um paciente e encorajador possa fazer com que as pessoas fiquem mais dispostas a tentar.

  • E aqui está mais uma coisa que gosto de destacar sobre o design.

  • Enquanto a conversa acontece no modo de óculos, o app escreve silenciosamente cartões de vocabulário em segundo plano.

  • A palavra, a tradução, a frase de exemplo e a foto do momento em que você fez a pergunta.

  • O usuário não precisa tocar em nada manualmente.

  • Eles estão só andando e conversando.

  • E, mais tarde, tudo está esperando por eles no smartphone, que é exatamente o que eu queria quando estava em Tóquio.

  • Portanto, o foco no áudio não significa que o conteúdo precisa ser apenas áudio.

  • Os óculos são a superfície certa para a curiosidade, e o celular é a superfície certa para a reflexão posterior.

  • Deixe o momento viva-voz para trás.

  • Agora, vamos conferir algumas dicas práticas para criar experiências em tempo real com o Gemini Nano 4 e o Kit de ML que aprendemos ao criar essa demonstração.

Priorizar a latência em tempo real com o design de duas passagens

  • Dica 1: priorize a latência em tempo real com um design de duas passagens.

  • Nos óculos com áudio, quando você pergunta "O que é isso?"

  • Cada momento de silêncio é perceptível.

  • No exemplo do Professor de Idiomas em XR, precisamos de duas coisas do modelo para cada turno da interação do usuário: uma resposta falada imediata e um cartão de vocabulário estruturado para salvar no app.

  • Se pedirmos ao modelo duas coisas juntas em uma única passagem, cada token gasto na geração de dados em segundo plano vai atrasar a primeira frase falada.

  • Por isso, separamos o que o usuário precisa agora do que o app vai precisar alguns segundos depois.

  • Por isso, dividimos cada turno em duas passagens.

  • Na Passagem 1, transmitimos o frame da câmera e a pergunta apenas para a resposta falada, acionando a conversão de texto em voz assim que a primeira frase é concluída.

  • E como essa TPU ou NPU termina de gerar essa resposta curta muito antes de a conversão de texto em voz terminar de falar em voz alta, temos uma janela de vários segundos em que o usuário ainda está ouvindo, e o chip já está ocioso.

  • Como a Passagem 1 já identificou o objeto na resposta de texto, a Passagem 2 nem precisa da entrada de imagem.

  • Basta passar a resposta da Etapa 1 de volta para o chip inativo para extrair e salvar o card estruturado antes mesmo que o professor termine de falar.

  • Agora, vamos ampliar a Passagem 1, o que me leva à segunda dica.

  • Use tags XML leves ao fazer streaming.

  • Como a Passagem 1 é transmitida diretamente para a conversão de texto em voz, você não quer que um objeto JSON pesado atrase a primeira palavra falada.

  • Em vez disso, você quer um texto simples que possa ser lido imediatamente com tags inline leves sempre que o app precisar entrar em ação no meio da transmissão.

  • Vou mostrar o problema que nos trouxe até aqui.

  • Lembra daquele brinquedo de madeira na barraca do mercado que mencionei?

  • Em japonês, ele é chamado de "kendama".

  • Logo no início, o modelo reconheceu o objeto no frame da câmera e retornou a resposta certa: "É um kendama, um brinquedo de madeira".

  • Então, enviamos essa frase completa para a sessão de conversão de texto em voz em inglês, e o resultado foi este.

  • É um ken-mi, um brinquedo de madeira.

  • Não é bem isso.

  • Em vez disso, a frase deveria ser…

  • É um kendama, um brinquedo de madeira.

  • Por que isso aconteceu?

  • Kendama é escrito com o hiragana "ken" e o caractere kanji "dama".

  • O mecanismo de conversão de texto em voz em inglês lida bem com o hiragana fonético, como "ken", mas falha completamente quando chega à parte do kanji e transforma "dama" em algo que soa como "me".

  • E isso acontece em vários idiomas de maneiras diferentes se você não mudar a localidade.

  • Por exemplo, em espanhol, você pode ouvir…

  • Suh-BOH-luh…

  • Quando você espera…

  • Cebolla.

  • Da mesma forma, em francês, você pode ouvir…

  • PAIN oh chock-uh-lat…

  • Quando você espera…

  • Pain au chocolat.

  • Sim, uma sessão de TTS geralmente é projetada para um único local por vez, mas um professor de idiomas precisa alternar o código em cada frase.

  • Portanto, na etapa 1, pedimos que o modelo envolva cada palavra ou frase do idioma-alvo dentro da tag XML de destino durante o streaming.

  • À medida que os blocos chegam, nosso cliente divide o destino, encaminha cada período para a localidade correspondente da conversão de texto em voz, que pode ser japonês, espanhol, francês ou qualquer idioma que você esteja aprendendo, e os reproduz sem problemas, mudando o sotaque no meio da frase, para que palavras como kendama saiam corretamente.

  • Portanto, o modelo já sabe qual vocabulário pertence a qual idioma, quais palavras pertencem a qual idioma, e marcar os períodos de destino inline durante o streaming é muito mais limpo do que pós-processar tudo isso depois.

  • E aqui está a passagem 1 no código.

    // 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>"
  • Na parte de cima, usamos o comando para instruir o modelo a responder em uma a três frases dentro das tags de resposta e, em seguida, colocar cada palavra ou frase de exemplo em japonês dentro das tags de destino.

  • Em seguida, transmitimos a câmera ImagePart e o comando TextPart para o generateContentStream do Kit de ML, dividindo os períodos de destino em conversão de texto em voz à medida que cada frase chega.

  • Isso nos leva à segunda etapa e à terceira dica.

  • Embora o uso de tags XML leves seja ótimo para um stream de áudio ao vivo, use a saída estruturada, que é criada para retornar um objeto limpo e digitado ao salvar dados no app.

  • Como o Professor de Idiomas em XR é conversacional, os usuários também podem fazer perguntas complementares em que nenhuma palavra nova é ensinada.

  • E, quando uma palavra é ensinada, o banco de dados precisa de campos de dicionário limpos para criar os cartões de vocabulário.

  • Portanto, em vez de escrever expressões regulares frágeis para analisar a transcrição da primeira passagem, deixamos que a segunda passagem extraia o objeto com segurança de tipos em segundo plano.

  • Para aplicar o esquema na etapa 2, usamos a API de saída estruturada do Kit de ML, que usa decodificação restrita.

    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 anotar uma classe de dados com @Generable e cada campo com @Guide, como no exemplo de classe de card de vocabulário aqui.

  • Ao encapsular o campo de card anulável, a segunda etapa retorna um card preenchido quando uma nova palavra é ensinada ou nulo em turnos puramente de conversa.

  • E aqui está a passagem 2 no código.

    // 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": "けん玉をください。", ...}
  • Na parte de cima, pegamos o texto da resposta transmitida da etapa 1, sem precisar de imagem, e criamos nosso comando de extração.

  • Em seguida, enquanto o usuário ainda está ouvindo a conversão de texto em voz, transmitimos o comando e nossa classe wrapper para generateTypedContentRequest e extraímos um card de vocabulário limpo e sem tags ou nulo se nenhuma palavra tiver sido ensinada.

  • Certo.

  • Abordamos muitos assuntos.

  • É razoável pensar: "Ótimo, mais uma coisa que parece incrível no palco, mas que nunca vou usar".

  • Não é essa!

  • O XR Language Teacher é fornecido no app Google AI Edge Gallery, junto com outras experiências de IA no dispositivo.

  • Você pode baixar o app hoje mesmo, colocar no seu smartphone e apontar para qualquer coisa que queira aprender a dizer em outro idioma.

  • E se você quiser saber como ela foi criada, toda essa implementação é de código aberto no GitHub.

  • E não se preocupe se nem todos os usuários tiverem óculos de áudio ou de tela ou dispositivos Android compatíveis.

  • Nossa base de código também mostra exemplos de como essa mesma experiência pode ser adaptada para uma experiência somente para dispositivos móveis.

  • E se você quiser alcançar outras plataformas, o código do app da galeria do Google AI Edge também mostra como criar esse recurso usando o ambiente de execução do Google LiteRT e as APIs MediaPipe Tasks.

  • Se você quiser usar outros modelos de áudio ou linguagem de código aberto no dispositivo, também pode ver como fazer isso no código.

Adaptação a outros casos de uso com agentes de IA

  • Por isso, disponibilizamos o código aberto do Professor de Idiomas em XR para facilitar a prototipagem.

  • Aponte o agente de sua escolha para nosso repositório do GitHub e transforme essa experiência em tempo real, no dispositivo e com tecnologia de IA em algo diferente.

  • Pode ser um guia de museu, um assistente de culinária que olha para o balcão, um tutorial de conserto, um narrador de acessibilidade ou uma ferramenta de inspeção de campo.

  • O modelo, o ambiente de execução, a implementação de código aberto e as ferramentas já estão disponíveis para você se preparar para o lançamento dos óculos inteligentes no segundo semestre.

  • Queremos ver o que você vai criar com ele.

  • Para encerrar, hoje mostramos exemplos incríveis do que é possível fazer com modelos de IA que funcionam perfeitamente no dispositivo.

  • Confira algumas maneiras de começar.

  • Primeiro, pegue os recursos na tela.

  • Baixe o app AI Edge Gallery para testar a demonstração e confira o código de referência no GitHub.

  • E leia os QR codes para acessar o SDK do Android XR, as APIs do Kit de ML e as ofertas do Google AI Edge.

  • Segundo, comece a fazer testes no smartphone hoje mesmo.

  • Muitos dos recursos que abordamos já podem ser criados com uma experiência que prioriza dispositivos móveis.

  • Terceiro, analise o app para dispositivos móveis que você já está lançando e se pergunte: quais recursos principais seriam melhores por comando de voz?

  • Basta adicionar uma atividade projetada ao APK para levar essa experiência também aos óculos.

  • Agradecemos a participação.

  • Mal podemos esperar para ver as incríveis experiências de áudio que você vai criar no Android e para óculos inteligentes.

  • Nossa equipe agradece.

Código

Snippets de código

Snippet do Kotlin em 02:39 em "Como criar para o Android XR"
dependencies {
   implementation("androidx.xr.projected:projected:1.0.0-alpha13")
}
Snippet do Kotlin aos 02:45 em "Como criar para o Android XR"
class ProjectedMainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           // Glasses content
       }
   }
}
Snippet XML em 03:02 em "Como criar para o 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 em Kotlin aos 03:44 em "Como criar para o 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 do Kotlin em 05:05 em "Como criar para o 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 de shell em 05:46 em "Como criar para o Android XR"
android skills add projected-hardware
android skills add --all
Snippet do Kotlin em 08:14 em "Como criar experiências que priorizam o áudio"
dependencies {
   // ML Kit GenAI TTS SDK
   implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
}
Snippet do Kotlin em 08:26 em "Como criar experiências que priorizam o áudio"
// 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 do Kotlin em 09:26 em "Como criar experiências que priorizam o áudio"
// 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 do Kotlin em 09:47 em "Como criar experiências com foco em áudio"
// 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 do Kotlin aos 10:29 em "Como criar experiências que priorizam o áudio"
// 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 do Kotlin em 13:42 em "Como criar experiências que priorizam o áudio"
dependencies {
   // ML Kit Speech Recognition
   implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
}
Snippet do Kotlin às 13:52 em "Como criar experiências com foco em áudio"
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 do Kotlin em 14:12 em "Como criar experiências com foco em áudio"
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 do Kotlin aos 14:41 em "Como criar experiências com foco em áudio"
// 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 do Kotlin em 26:27 em "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 do Kotlin em 27:33 em "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 do Kotlin aos 28:00 em "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": "けん玉をください。", ...}