Cihaz üzerinde yapay zeka destekli, eller serbest ses deneyimleri oluşturmayı öğrenin. Gözlük etkinliğinizi ayarlama, mikrofon ve kamera feed'lerini yakalama, sesinizi ve diğer sesli ipuçlarını nihai kullanıcı arayüzünüz olarak kullanma gibi ihtiyacınız olan her şeyi ele alacağız. Google AI Edge Gallery uygulamasında yerleşik olarak bulunan açık kaynak XR Language Teacher'ın heyecan verici bir gerçek dünya örneği üzerinden, Gemma 4 üzerine inşa edilmiş ve Android cihazlar için optimize edilmiş Gemini Nano 4 ile ML Kit'i yönetmeyi ve LiteRT çalışma zamanından yararlanarak doğal bir şekilde duyan, gören ve konuşan hızlı XR deneyimleri oluşturmayı öğrenin.
Transkript
Herkese merhaba, hoş geldiniz.
Bugün herkesi görmek çok güzel.
Geldiğiniz için teşekkürler.
Ben Amy.
Bugün meslektaşım Alice ile birlikte, Gemini Nano 4'ü kullanarak akıllı gözlükler için ses odaklı muhteşem deneyimler oluşturma hakkında konuşacağız.
Hemen başlayalım.
Akıllı gözlükler, tüm gün takılabilen taşınabilir ve hafif gözlüklerdir.
Bu kategori hem ekranlı hem de yalnızca sesli gözlükleri içerir. Bu cihazlarda farklı donanımlar göreceksiniz.
İlk fark edeceğiniz şey, dokunma ve kaydırma gibi kullanıcı girişleri için kullanılabilen bir dokunmatik yüzeylerinin olmasıdır.
Ayrıca hoparlör, kamera ve mikrofon da bulunur.
Ayrıca ekranlı veya ekransız olabilirler ya da bu özelliklerin bir kombinasyonunu içerebilirler.
Bugün, ses odaklı deneyimlerden ve ilgi çekici, akıllı, hareket halindeyken kullanılabilen deneyimlerin nasıl oluşturulacağından bahsedeceğiz.
Yalnızca sesli iletişimde ilginç olan şeylerden biri, sesin kullanıcı arayüzünüz haline gelmesidir.
Cihaz üzerinde yapay zeka, çevrimdışı ve hareket halindeyken deneyimler arayan geliştiriciler için idealdir.
İyi bir haberimiz var.
Android için uygulama geliştiriyorsanız Android XR için de uygulama geliştiriyorsunuz demektir.
Mevcut Android uygulamaları, ses arabirimli gözlüklerde iyi çalışacak şekilde tasarlanmıştır. Bu uygulamalar, Bluetooth üzerinden sesli bildirimler ve diğer sesleri iletir.
Uygulamanızı XR için daha da farklılaştırmak istiyorsanız bunu yapmanın birçok yolu vardır.
Bu konuşmada cihaz üzerinde çalışan modellerden bahsediyoruz ancak bu liste kesinlikle kapsamlı bir liste değil.
Bulut modelleri de var.
Gemini Live API gibi araçlar var.
Gemini gibi entegre edebileceğiniz sistem temsilcileri vardır ve bunların tümü sizin için en iyi deneyimi oluşturmanıza yardımcı olabilir.
Peki doğal bir şekilde duyan, gören ve konuşan bu deneyimi nasıl oluşturabilirsiniz?
Bugün, cihaz üzerinde yapay zeka tarafından desteklenen bu güçlü, eller serbest deneyimleri nasıl oluşturacağınızı anlatacağız.
Size temel bilgileri anlatacağım: Gözlük için nasıl uygulama geliştirilir, etkinliğinizi nasıl ayarlarsınız, ihtiyacınız olan izinleri nasıl alırsınız ve ardından konuşma kullanıcı arayüzü için ML Kit'i nasıl kullanırsınız?
Ardından sözü Alice'e bırakacağım. Alice, Gemini Nano 4'ün kullanıldığı gerçek örneklerden bahsedecek.
Şimdi koda göz atalım.
Gözlük kullanırken asıl işi telefon yapar ve gözlükteki içerik telefondan gözlüğe yansıtılır.
Bu nedenle, ilk kurulumu yaparken bu cihazları bağlamak için iki şeye ihtiyacınız vardır.
Öncelikle Jetpack Projected kitaplığına ihtiyacınız olacak.
Bu bağımlılığı eklemeniz gerekir. Bu bağımlılık, yansıtılan deneyimi destekleyecek API'leri içerir.
dependencies {
implementation("androidx.xr.projected:projected:1.0.0-alpha13")
}Ardından, gözlükler için özel olarak yeni bir etkinlik oluşturmanız gerekir.
class ProjectedMainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Glasses content
}
}
}Ardından, bu Jetpack Projected kitaplığını hem mevcut uygulamanızda hem de bu yeni etkinlikte kullanarak iki cihazın gerektiğinde sorunsuz bir şekilde iletişim kurmasını sağlayacaksınız.
Gördüğünüz diğer normal etkinlikler gibi bunu da manifest dosyanızda beyan edeceksiniz.
<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>Buradaki temel fark, requiredDisplayCategory olarak XR_PROJECTED kullanmanız gerekmesidir.
Böylece sistem, bu etkinliğin gözlükle ilgili olduğunu anlar.
Ardından, buradaki amaç kategorisini (XR_PROJECTED_LAUNCHER) de kullanmak isteyeceksiniz.
Bu sayede Gemini, uygulamanızı keşfedebilir ve kullanıcılar hareket halindeyken uygulamanızı eller serbest modunda başlatabilir.
İşte böyle.
Bu etkinliği ayarladıktan sonra, bahsettiğim gibi, uygulama deneyimini desteklemek için Projected kitaplığını kullanırsınız.
Bu kitaplık, tam gözlük deneyimini oluşturmak için ihtiyacınız olan tüm araçları içerir.
Burada kullanacağınız temel API'lerden birkaçını inceleyelim.
İlk olarak isProjectedDeviceConnected yönteminden bahsetmek istiyorum.
// 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)Gözlük bağlantısını bu şekilde sürekli olarak izleyebilirsiniz.
Bu özelliği cihaz yaşam döngüsüyle birlikte kullanarak gözlüğün ne zaman kullanılabilir olduğunu öğrenebilirsiniz. Yani yalnızca eşlendiği veya açıldığı zamanı değil, takıldığı ve kullanıma hazır olduğu zamanı da görebilirsiniz.
Bu da böyle bir şey.
Bu sayede etkinliği başlatmaya tam olarak ne zaman hazır olduğunuzu bileceksiniz.
Bunu yapmak için etkinliği başlatmaya hazır olduğunuzda, etkinliği başlatırken ayarlamanız gereken bazı özel seçenekler vardır.
Bunlar için ProjectedContext üzerindeki createProjectedActivityOptions kullanacaksınız.
Ardından, bu bilgiyi niyet bağlamıyla birlikte kullanırsınız.
Kullanmak isteyeceğiniz bir diğer araç da ProjectedDeviceController.
Kullanıcının ne tür gözlük kullandığını bu şekilde kontrol edeceksiniz.
Daha önce de söylediğim gibi akıllı gözlükler yalnızca ekran ve ses özellikli gözlükleri de kapsar. Bu özelliklerin CAPABILITY_VISUAL_UI içerip içermediğini kontrol etmeniz gerekir. Böylece uygulamanızı farklı cihazlara sorunsuz bir şekilde uyarlayabilir ve o cihaz için optimize edilmiş bir deneyim sunabilirsiniz.
Bu farklılaştırılmış deneyimi oluştururken cihazın donanımına erişmek isteyebilirsiniz. Örneğin, gözlüğün kamerasından yararlanabilirsiniz.
Bu işlemi planlanan bir etkinlikten yapmak için ProjectedActivityCompat kullanarak uygun izinleri istemeniz gerekir.
// 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)
}
}
}Uygulamanız telefon için gereken izinlere zaten sahip olsa bile gözlüğün donanımı için de özel izinler istemeniz gerekir.
İzinlerle çalışırken genel olarak Android için en iyi uygulamaları takip etmeyi unutmayın.
Gerekli izinleri manifest dosyasında beyan edin, izinleri ihtiyaç duyuldukları bağlamda isteyin ve yalnızca uygulamanızın çalışması için gerçekten ihtiyacınız olan izinleri isteyin.
Bu konuyla ilgili bilgileri developer.android.com adresindeki sitemizde bulabilirsiniz.
Ancak yepyeni bir becerimiz daha var: Öngörülen donanım becerisi.
İzin iş akışınızı ayarlamak için bunu Android Studio'daki Android KSA ile kullanabilir ve bu beceriye, ardından diğer tüm Android becerilerine erişebilirsiniz.
android skills add projected-hardware
android skills add --allBağlantıyı kurduğumuza, donanım özelliklerimizi bildiğimize ve doğru izinlere sahip olduğumuza göre, ses deneyimini oluşturmak için kullanabileceğiniz araçlardan bahsedelim.
Bu nedenle, ses öncelikli bir deneyim tasarlarken kullanıcılar için yüksek kaliteli bir deneyim oluşturduğumuzdan emin olmak istiyoruz.
Bu, birkaç önemli etkileşimi içerebilir.
İlk olarak, ses çıkış olarak kullanılır. Yani sesiniz, kullanıcıyla doğal bir şekilde konuşur.
Bir sonraki adım, kullanıcıya belirli ses sinyalleri göndermek için ses sinyallerini kullanmak olacaktır.
Ayrıca, dokunmatik alandaki dokunma ve kaydırma hareketleri veya kullanıcının uygulamanızla konuşmasına izin vermek istiyorsanız sesli komutlar gibi kullanıcı girişlerini de işler.
İlk olarak, sesin çıkış olarak kullanılmasından bahsedeceğiz.
Bu nedenle, ML Kit'in yeni GenAI Text To Speech kitaplığını tanıtmak istiyorum.
Şu anda alfa sürümünde olan bu özellik, belirli Pixel ve Samsung cihazlarda kullanılabilir.
Bu, özellikle sesli podcast deneyimleri için çok heyecan verici bir gelişme.
Daha önce Text-to-Speech ile çalıştıysanız sistem sesinin her zaman istediğiniz kadar doğal olmamasının sorunlardan biri olabileceğini muhtemelen biliyorsunuzdur.
Diğer sesler için bulut modellerine bağlanmak ise gecikmeye veya ek maliyetlere neden olabilir.
Bu nedenle ML Kit, sistem TTS'sine kıyasla çok daha doğal bir konuşma sunar.
ML Kit, uygulamanızın ihtiyaçlarına göre seçebileceğiniz farklı özelliklere sahip sekiz benzersiz ses sunar.
Ayrıca ML Kit, seçebileceğiniz 21 farklı yerel ayar ile güçlü bir dil desteği sunar.
Ayrıca, ilk indirme işleminden sonra tamamen cihaz üzerinde çalışması da büyük bir avantaj.
Bu model sisteme indirilir ve uygulamalar arasında paylaşılır. Bu nedenle APK'nızın boyutunu artırmaz.
Üstelik bu özellik çevrimdışı olarak da kullanılabiliyor. Bu, özellikle hareket halindeki gözlük kullanıcıları için çok önemli. Wi-Fi bağlantıları olmayabilir veya kapsama alanının zayıf olduğu yerlerde bulunabilirler. Ayrıca, sunucuya gidiş dönüş çağrısı yapmanız gerekmediği için daha iyi performans elde edebilirsiniz.
İşte böyle.
Şimdi kitaplığın ayrıntılarına ve bu kitaplıkla tam olarak nasıl derleme yapabileceğimize bakalım.
Öncelikle bağımlılığı eklemeniz gerekir.
dependencies {
// ML Kit GenAI TTS SDK
implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
}Bu nedenle, burada ihtiyacınız olan tek şey ML Kit'in Text To Speech SDK'sıdır.
Ardından kodunuzu yazmaya başlayabilirsiniz.
Bu nedenle, GenAI ML Kit kitaplığı, Speech Synthesizer API'lerini içerir.
// 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
}Bu sayede, seçtiğiniz sesleri kolayca uygulayabilirsiniz.
Burada yapacağınız ilk adım, seçtiğiniz yerel ayarı belirleyerek istemcinizi tekrar bu konuşma sentezleyiciyi kullanarak ilk kullanıma hazırlamaktır.
Ardından, indirme durumunu kontrol edip sesi seçip seçmediğinizi göreceksiniz.
Bu örnekte ilk sesi seçiyoruz ancak burada firstOrNull kullandığımızı da belirtmek isterim.
Bahsettiğim gibi, ML Kiti için Samsung veya Pixel cihazlar gibi belirli özelliklere sahip telefonlar gerekir.
Dolayısıyla, boş bir değer alırsanız bu, kullanıcının uyumlu bir telefon kullanmadığı anlamına gelebilir.
Ardından, sistemin metin okuma özelliğine geri dönmek veya kayıtlı bir mesajı oynatmak gibi bir yedek seçeneğinizin olduğundan emin olmanız gerekir.
Ardından, isteği oluşturmak için konuşma sentezini SpeechSynthesizerRequest kullanacaksınız. Giriş metnini sağlayıp kullanımı ve içerik türünü ayarlayacaksınız.
// 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}")
}
}Konuşmayı oynatın, durumu izleyin ve konuşmanın ne zaman tamamlandığını öğrenmek için bildirimleri takip edin. Ardından, oluşabilecek hataları toplayın.
Gerektiğinde de kaynakları kapatır.
// 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()
}Bu oldukça basit, değil mi?
Bu adımlarla uygulamanızın sesini gerçekten dönüştürebilirsiniz. Şöyle bir sesten…
Merhaba Android geliştiricileri.
Bugün hepinizi görmek çok güzel.
Ses öncelikli deneyimlerle ilgili sunumumuzun faydalı olduğunu umuyoruz.
Şu şekilde daha doğal bir sese dönüştürebilirsiniz.
Merhaba Android geliştiricileri.
Bugün hepinizi görmek çok güzel.
Ses öncelikli deneyimlerle ilgili sunumumuzun faydalı olduğunu umuyoruz.
Çok daha iyi, değil mi?
Anladım.
ML Kit'in Metin Okuma kitaplığı son derece güçlü bir araçtır. Ancak yalnızca bu sesleri kullanmakla sınırlı değilsiniz. Bu kitaplıkla ses sinyalleri de çalabilirsiniz.
// 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()
)Ses sinyalleri, adından da anlaşılacağı gibi temel olarak simgelerin sesli karşılığıdır.
Bu sayede, kullanıcınızla iletişim kurmak için kısa sesler çalabilirsiniz.
Ses sinyalleri içeren görsel simgelerde olduğu gibi, kullanıcıların bunların ne olduğunu ve ne anlama geldiğini bilmesi gerekir.
Bu nedenle, başlangıçta bunları sesli bildirimlerle eşleştirerek netleştirmek isteyebilirsiniz.
Android Design Hub'da kullanıma sunduğumuz bazı standart sesler de var. Bunları doğrudan oradan indirip uygulama açma, uygulama kapatma, hata bildirimleri ve sesi kapatma veya açma gibi işlemler için kullanabilirsiniz.
Dilerseniz kendi özel ses sinyallerinizi de oluşturabilirsiniz.
Ses sinyalleriyle ilgili olarak, ister Tasarım Merkezi'nden indirdiğiniz ses sinyallerini kullanın ister kendi ses sinyallerinizi oluşturun, uymanızı önerdiğimiz bazı en iyi uygulamalar vardır.
Bu nedenle, ses sinyallerini nasıl ve ne şekilde kullanacağınız konusunda dikkatli olmanız gerekir.
Bu nedenle, dikkat etmeniz gereken bazı noktalar var. Örneğin, ses sinyallerini aşırı kullanmamaya çalışın.
Aynı sesleri tekrar tekrar çalmayın. Bu, kulak yorgunluğuna neden olabilir.
Ses sinyallerinizin mükemmel ve sezgisel olması gerektiğini unutmayın. Ayrıca, bunları uygularken tanıdık ses tonları ve ritimler kullanın.
Ayrıca, açıklama gerektiren aşırı karmaşık ses sinyallerinden kaçınmaya çalışın. Bunlar sesli bildirim olarak daha uygun olabilir.
Stüdyoda veya sisteminizde, bilgisayarınızın hoparlörlerinde hafif gelen bir sesin, kullanıcılarınız tarafından tekrar tekrar dinlendiğinde rahatsız edici olabileceğini her zaman aklınızda bulundurun.
Bu nedenle, kullanıcılarınızı kafalarını karıştırabilecek veya önemli uyarıları göz ardı etmelerine yol açabilecek seslerle boğmamak için elinizden geleni yapın.
İşte böyle.
Artık doğal konuşma veya ses sinyalleri aracılığıyla sesli ipuçları ve geri bildirimler sağlayabiliyoruz.
Peki ya giriş?
Cihazda dokunma ve kaydırma etkileşimleri oluşturmak için kullanabileceğiniz bir dokunmatik alan veya özel düğmeler de bulunur.
Ancak bu etkileşimli kullanıcı arayüzüne de izin vermek isteyebilirsiniz. Bunun için ML Kit'in GenAI Konuşma Tanıma API'sini kullanabilirsiniz.
Şu anda alfa sürümünde olan ve belirli cihazlarda kullanılabilen bu özellik, temel olarak iki farklı modda çalışır.
Temel mod, geleneksel cihaz üzerinde konuşma tanıma özelliğini kullanır ve API düzeyi 31 ve üzeri olan Android cihazlarda kullanılabilir.
Ayrıca Gemini Nano'yu kullanan Gelişmiş mod da var. Pixel 10 ve sonraki cihazlarda sunulan bu modda daha yüksek kalite ve daha iyi dil kapsamı da göreceksiniz.
Bu nedenle SpeechRecognizer, SpeechSynthesizer ile çok benzer bir kod kalıbı kullanır.
İstemcinizi oluşturur, modelin kullanılabilir olup olmadığını kontrol eder, gerekirse indirir, isteği oluşturur ve kaynaklarınızı serbest bırakırsınız.
Bu nedenle, isteğinizi oluşturduğunuz, mikrofon sesini yakaladığınız ve ardından sonucu işlediğiniz temel farklara odaklanalım.
Yine gerekli bağımlılıklarınızı eklemeniz gerekir.
dependencies {
// ML Kit Speech Recognition
implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
}Burada ihtiyacınız olan tek şey Speech Recognition API'dir.
Ardından isteğinizi oluşturmaya başlayabilirsiniz.
Bu nedenle, tanıma mantığınızı tanımlarken bir askıya alma işlevi kullanacaksınız.
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() }
...Ardından, yerel ayarı ve preferredMode değerini ayarlayarak konuşma tanıma seçeneklerinizi belirtin.
Ayrıca, audioSource seçip isteği tanımlayacağınız yer de burasıdır.
Ardından tanıma işlemini başlatıp yanıtı alırsınız.
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()
}
...SpeechRecognizer ile dört olası yanıt alabilirsiniz: kısmi, nihai, hata ve tamamlandı.
Kısmi, gerçek zamanlı akışı inceler. Bu nedenle, uyandırma kelimeleri veya anahtar kelimeler gibi şeyler için kullanılır.
Final, tam olarak analiz edip güncelledikten sonra son ses akışını alır. Completed, konuşma tamamlandıktan sonra, Error ise oluşabilecek hataları topladıktan sonra gerçekleşir.
Şimdi de tüm bunları nasıl bir araya getirdiğimize bakalım.
// 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")
}
}Örneğin, kullanıcının sesli komutla uygulamaya bir içerik türünü daha sonra kullanmak üzere kaydetmesini söyleyebilmesini istiyoruz.
Kullanıcılara kullanmalarını söylediğimiz tek bir açık sesli komut kullanabilir veya bir dizi anahtar kelime ve kelime öbeği kullanmalarına izin verebilirsiniz.
Burada, "kaydet", "yer işareti ekle" veya "hatırla" kelimelerini kullanmalarına izin veriyoruz.
SpeechRecognizer, kullanıcının bu ifadelerden birini kullandığını algıladığı anda içeriği kaydedip konuşma tanımayı durdurur, özel bir ses sinyali çalar ve ML Kiti'nin GenAI Text to Speech özelliğini kullanarak kullanıcıya içeriğin kaydedildiğini bildirir.
Anladım.
Birçok konuyu ele aldık.
Bu kavram biraz soyut gelebilir.
Siz de benim gibiyseniz gerçek hayattan örnekler görmek istersiniz.
Alice ile birlikte size tam olarak bunu anlatmak istiyoruz.
Google AI Edge Gallery uygulamasındaki XR Dil Öğretmeni'ni nasıl kullanacağınızı anlatacak.
Alice'in ekibi tarafından geliştirilen bu aracın nasıl çalıştığını Alice'ten dinleyelim.
Söz sizde.
Teşekkürler, Amy.
Evet.
Birçoğunuzun bildiği bir örnekle başlamak istiyorum.
Japonca öğreniyorum ve dil öğrenme uygulamaları, bilgi kartları, seriler gibi her şeye saatler harcadım.
Tokyo'ya gidip bir pazar yerine girdiğimde ise öğrendiğim kelimelerin neredeyse hiçbirinin kullanılmadığını fark ettim.
Pazar tezgahında, görünüşünden tanıdığım ahşap bir top ve kupa oyuncağı var ancak Japonca konuşmayı bilmiyorum.
Bir mağazanın vitrininde satın almak istediğim ama adını bilmediğim bir süs var.
İşte o an, bir kelimeyi öğrenmek için en çok motive olduğum andır.
Kanepemde oturup dil uygulamasına bakarak değil.
O anda öğrenmek istediğim şeye bakıyorum.
İşte istediğim şey.
Akıllı gözlüğümü tüm gün takıp dolaşmak istiyorum.
Gözüme çarpan bir şey olduğunda sesli olarak sorabilirim.
Akşam otele döndüğümde, sorduğum her şey telefonumda kelime kartları gibi beni bekliyor olurdu.
Biz de bunu geliştirdik.
Kısa bir klip izleyelim.
Bu nedenle buna XR Dil Öğretmeni diyoruz.
Merhaba,
Bugün hangi konuda bilgi edinmek istiyorsunuz?
Öğrenme deneyiminizi öğretmen ayarlarından özelleştirebilirsiniz.
Örneğin, öğrenmek istediğiniz dili ve size uygun öğretmen karakterini seçebilirsiniz.
Evet.
Gözlük ve telefon modu arasında geçiş yapabilirsiniz.
İspanyolcada "bu üzümler" nasıl denir?
Buna göre, bu lezzetli üzümlere "uvas" deniyor.
İspanyolcada üzümleri tanımlamak için "Estas uvas son deliciosas" diyebilirsiniz.
Özellikle yeşil üzüm diyebilirim.
Hemen ilgileniyorum.
Yeşil üzümleri belirtmek için "Uvas verdes" diyebilirsiniz.
Me gustan las uvas verdes" (Yeşil üzümü severim) diyerek birine ondan hoşlandığınızı söyleyebilirsiniz.
Bunun Fransızcası ne ve üç tane istediğimi nasıl söyleyebilirim?
Birlikte inceleyelim.
Bu lezzetli görünen hamur işlerine "des madeleines" denir.
Fransızcada üç tane istemek için "Je voudrais trois madeleines, s'il vous plaît." diyebilirsiniz.
Bununla nasıl para üstü isteyebilirim?
Bakalım burada ne varmış.
Para üstü istemek için "Otsuri wa arimasu ka?" diyebilirsin.
Yani, "Bozuk paranız var mı?"
Para üstünü kibarca istemek için "Otsuri o onegaishimasu" da diyebilirsiniz.
Öğrendiğiniz her şey otomatik olarak kelime kartları şeklinde kaydedilir.
La botella de agua.
Esta es una botella de agua.
Evet, ayrıca çalışma sekmesinde hafızanızı test edebilir ve pratik yapabilirsiniz.
İşte böyle.
Evet.
Yani az önce gördüğünüz her şey cihazda çalıştı, ağ bağlantısı kullanılmadı.
Uluslararası veri planıyla seyahat ediyorum. Bu plan, genellikle pazar salonuna, yer altına veya kalın beton duvarların arkasına girene kadar sorunsuz çalışıyor.
En çok ihtiyaç duyduğunuz an, genellikle sinyalin en kötü olduğu andır.
Sinyal kesildiğinde çalışmayan bir deneyimin gerçek bir seyahat arkadaşı olamayacağını söyleyebilirim. Bu nedenle, XR Language Teacher'ın tamamen çevrimdışı ve cihaz üzerinde çalışmasını sağladık.
Peki, bu nasıl oluşturuldu?
Amy, mimari açıdan gözlükten telefona yansıtılan kamera çerçevesinin nasıl kullanılacağını zaten gösterdi.
ML Kiti'ni ve konuşma tanıma özelliğini kullanarak metni sese dönüştürmeyi ve ML Kiti Metin Okuma özelliğini kullanarak metni tekrar sese dönüştürmeyi gösterdi.
Ancak ortada bir beyne ihtiyacımız vardı. Kamera karesine ve soruya birlikte bakıp kullanıcının aslında ne sorduğunu anlayabilecek bir şeye.
Bu beyin, Google'ın telefonda yerel olarak çalışan açık kaynaklı modeli Gemma 4'ü temel alan Gemini Nano 4'tür.
Bu nedenle, desteklenen üst düzey Android cihazlarda ML Kit API'lerini doğrudan kullanarak optimize edilmiş performans elde edebilirsiniz.
Android sistemi, model yayınlama işlemini sizin için halleder. Bu nedenle, uygulamanıza herhangi bir şey eklemeniz gerekmez.
Özetlemek gerekirse mikrofon, konuşma tanıma özelliğine bağlanır. Kamera karesi ve transkript ise Gemini Nano 4'e bağlanır. Gemini Nano 4, konuşma sentezleme özelliğine yazma işlemi yapar ve ardından hoparlörden ses çıkar.
Üç model, zincirleme olarak telefonunuzda yerel olarak çalışır.
Gemini Nano 4'ün bu sorun için özellikle uygun olmasının birkaç nedeni vardır.
İlk olarak, tek bir geçişte görür ve okur.
Gemini Nano 4, ML Kiti aracılığıyla metinleri ve görüntüleri yerel olarak işler. Bu sayede algılayıcı, sınıflandırıcı ve çevirmen kullanmanız gerekmez.
Kamera karesini ve soruyu birlikte inceleyen tek bir modeldir.
İkincisi ise kapsamlı dil desteğidir.
Gemini Nano 4, 140'tan fazla dilde eğitildi.
Ayrıca, dil eğitmeni için çeviri tek başına yeterli değildir.
Deyimler, resmi ve günlük dil arasındaki farklar ve gerçek bir kişinin söyleyeceği örnek cümleler istiyorsunuz.
Son olarak, bir kişiliğe sahip olacak kadar yönlendirilebilir.
Telaffuz alıştırmasını sesli yapmak göz korkutucu olabilir.
Bu model, sistem istemlerini iyi takip ettiği için kullanıcılar kendi öğretmen karakterlerini seçebilir.
Sabırlı ve teşvik edici bir yaklaşım, kullanıcıların deneme isteğini artırabilir.
Tasarım hakkında vurgulamak istediğim bir şey daha var.
Sohbet, gözlük modunda gerçekleşirken uygulama arka planda sessizce kelime kartları oluşturur.
Kelime, çeviri, örnek cümle ve sorunuzu sorduğunuz anın fotoğrafı.
Kullanıcının herhangi bir şeye manuel olarak dokunması gerekmez.
Yürüyüp konuşuyorlar.
Gün içinde daha sonra, her şey telefonlarında onları bekliyor. Tokyo'dayken tam olarak istediğim şey buydu.
Dolayısıyla, ses öncelikli olması yalnızca sesli içerik olacağı anlamına gelmez.
Gözlük, merakınızı gidermek için doğru yüzeydir. Telefonunuz ise sonrasında düşünmek için doğru yüzeydir.
Eller serbest anılarınızı kalıcı hale getirin.
Şimdi, bu demoyu oluştururken öğrendiğimiz, Gemini Nano 4 ve ML Kiti ile gerçek zamanlı deneyimler oluşturmaya yönelik birkaç pratik ipucuna yakından bakalım.
1. ipucu: İki aşamalı tasarımla gerçek zamanlı gecikmeye öncelik verin.
Ses arabirimli gözlükte "Bu nedir?" diye sorduğunuzda
Her sessizlik anı fark edilir.
XR Dil Öğretmeni örneğinde, kullanıcı etkileşiminin her aşamasında modelden iki şey bekleriz: anında sözlü yanıt ve uygulamaya kaydedilecek yapılandırılmış bir kelime kartı.
Modelden tek bir adımda iki şeyi birlikte yapmasını istersek arka planda veri oluşturma için harcanan her jeton, ilk konuşulan cümleyi geciktirir.
Bu nedenle, kullanıcının o anda ihtiyaç duyduğu şeyleri, uygulamanın birkaç saniye sonra ihtiyaç duyacağı şeylerden ayırırız.
Bu nedenle her sırayı iki geçişe böleriz.
1. aşamada, kamera karesini ve soruyu yalnızca sözlü yanıt için yayınlarız. İlk cümle tamamlanır tamamlanmaz metni sese dönüştürme özelliğini tetikleriz.
TPU veya NPU, kısa yanıtı metin okuma işlemi tamamlanmadan çok önce oluşturduğu için kullanıcının dinlemeye devam ettiği ve çipin boşta kaldığı birkaç saniyelik bir zaman dilimi elde ederiz.
1. adımda nesne metin yanıtında zaten tanımlandığı için 2. adımda resim girişine gerek bile yoktur.
Öğretmen konuşmasını bitirmeden önce yapılandırılmış kartı çıkarıp kaydetmek için 1. adımın yanıtını boşta olan çipe geri göndeririz.
Şimdi 1. geçişe odaklanalım. Bu da beni ikinci ipucuna getiriyor.
Yayın yaparken basit XML etiketleri kullanın.
1. adım doğrudan metin okumaya aktarıldığı için ilk sözlü kelimenizin ağır JSON nesnesi tarafından engellenmesini istemezsiniz.
Bunun yerine, uygulamanın akışın ortasında işlem yapması gerektiğinde basit, satır içi etiketlerle hemen konuşabilen düz metin istersiniz.
Şimdi sizi bu noktaya getiren sorunu göstereyim.
Pazar tezgahında gördüğüm o ahşap top ve kupa oyuncağını hatırlıyor musun?
Japoncada buna "kendama" denir.
Model, oyuncağı kamera karesinden tanıyarak "Bu, ahşap bir oyuncak olan kendama." şeklinde doğru yanıtı verdi.
Bu tam cümleyi İngilizce metin okuma oturumuna gönderdik ve sonuç şu şekilde oldu.
Bu, ken-mi adlı ahşap bir oyuncak.
Bu pek doğru görünmüyor.
Aslında bu cümle şöyle olmalı:
Bu, ahşap bir oyuncak olan kendama.
Peki bu neden oldu?
Kendama, hiragana "ken" ve kanji karakteri "dama" ile yazılır.
İngilizce metin okuma motoru, "ken" gibi fonetik hiragana karakterlerini sorunsuz bir şekilde işlerken kanji kısmına geldiğinde tamamen başarısız oluyor ve dama kelimesini "me" gibi bir sese dönüştürüyor.
Yerel ayarı değiştirmezseniz bu durum aslında birçok farklı dilde farklı şekillerde gerçekleşir.
Örneğin, İspanyolca'da şunları duyabilirsiniz:
Suh-BOH-luh…
Ancak beklediğiniz şey…
Cebolla.
Benzer şekilde, Fransızca'da şunları duyabilirsiniz:
PAIN oh chock-uh-lat…
Aslında beklediğiniz…
Pain au chocolat.
Evet, TTS oturumları genellikle tek bir yerel ayar için tasarlanır ancak dil öğretmenlerinin her cümlede kod değiştirme yapması gerekir.
Bu nedenle, 1. adımda modelden, her hedef dil kelimesini veya ifadesini akış sırasında hedef XML etiketinin içine sarmasını isteriz.
Parçalar geldikçe istemcimiz hedefe göre bölme işlemi yapar, her bir bölümü eşleşen metin okuma yerel ayarına (Japonca, İspanyolca, Fransızca veya öğrendiğiniz dil hangisiyse) yönlendirir ve ardından bunları sorunsuz bir şekilde oynatır. Cümlenin ortasında aksan değiştirerek "kendama" gibi kelimelerin doğru şekilde telaffuz edilmesini sağlar.
Özetle, model hangi kelimenin hangi dile ait olduğunu zaten biliyor. Akış sırasında hedef bölümleri satır içinde etiketlemesi, tüm bu işlemleri daha sonra kendiniz yapmanızdan çok daha temiz bir yöntem.
Kodda 1. geçişi görüyorsunuz.
// 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>"Üst kısımda, modele yanıt etiketleri içinde bir ila üç cümleyle yanıt vermesini ve ardından her Japonca kelimeyi veya örnek cümleyi hedef etiketlerin içine sarmasını söylemek için istemi kullanırız.
Ardından, kamerayı ImagePart ve istemi TextPart ML Kit'in generateContentStream sınıfına aktarırız. Bu sınıf, hedef aralıkları her cümle geldiğinde Metin Okuma'ya böler.
Böylece 2. aşamaya ve üçüncü ipucumuza geçiyoruz.
Canlı ses akışı için basit XML etiketleri kullanmak iyi bir yöntem olsa da uygulamanıza veri kaydederken temiz ve yazılı bir nesne döndürmek için tasarlanmış yapılandırılmış çıkış kullanın.
XR Dil Öğretmeni sohbet tabanlı olduğu için kullanıcılar yeni kelime öğretilmeyen ek sorular da sorabilir.
Bir kelime öğretildiğinde, veritabanının kelime kartlarını oluşturmak için temiz sözlük alanlarına ihtiyacı vardır.
Bu nedenle, birinci aşamanın transkriptini ayrıştırmak için kırılgan normal ifadeler yazmak yerine, ikinci aşamanın arka planda tip güvenli nesneyi ayıklamasına izin veriyoruz.
İkinci adımda şemayı uygulamak için ML Kit'in yapılandırılmış çıkış API'sini kullanıyoruz. Bu API, arka planda kısıtlı kod çözme yöntemini kullanıyor.
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?
)Buradaki kelime kartı sınıfı örneğimizde olduğu gibi, bir veri sınıfına @Generable ve her alana @Guide ek açıklama eklemeniz yeterlidir.
Boş değer atanabilir kart alanını sarmalayarak 2. geçiş, yeni bir kelime öğretildiğinde doldurulmuş bir kart döndürür veya tamamen sohbet içeren sıralarda boş değer döndürür.
Kodda 2. geçişi burada görebilirsiniz.
// 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": "けん玉をください。", ...}Üst kısımda, 1. adımın akışla aktarılan yanıt metnini alırız (resim gerekmez) ve çıkarma istemimizi oluştururuz.
Ardından, kullanıcı Metin Okuma'yı dinlemeye devam ederken istemi ve sarmalayıcı sınıfımızı generateTypedContentRequest modeline iletiriz. Model, temiz ve etiketsiz bir kelime kartı oluşturur veya öğretilen bir kelime yoksa null değerini döndürür.
İşte böyle.
Birçok konuyu ele aldık.
Şu anda "Harika, sahnede güzel görünen ama asla dokunamayacağım bir şey daha" diye düşünmeniz normal.
Bu değil.
XR Language Teacher, diğer cihaz üzerinde yapay zeka deneyimleriyle birlikte Google AI Edge Gallery uygulamasında sunulur.
Hemen indirip telefonunuza yükleyebilir ve farklı bir dilde nasıl söylendiğini öğrenmek istediğiniz her şeye doğrultabilirsiniz.
Nasıl oluşturulduğunu öğrenmek isterseniz bu uygulamanın tamamı GitHub'da açık kaynaklıdır.
Kullanıcılarınızın tümünde sesli gözlük, görüntülü gözlük veya desteklenen amiral gemisi Android cihazlar yoksa endişelenmeyin.
Kod tabanımızda, aynı deneyimin yalnızca mobil cihazlara yönelik bir deneyime nasıl uyarlanabileceğine dair örnekler de gösterilmektedir.
Diğer platformlara da ulaşmak istiyorsanız Google AI Edge Gallery uygulama kodu, Google LiteRT çalışma zamanı ve MediaPipe Tasks API'lerini kullanarak bu özelliği nasıl oluşturacağınızı da gösterir.
Diğer açık kaynaklı cihaz üzerinde ses veya dil modellerini kullanmak istiyorsanız bunu koddan nasıl yapacağınızı da görebilirsiniz.
Bu nedenle, prototip oluşturmanızı kolaylaştırmak için XR Dil Öğretmeni'ni açık kaynaklı hale getirdik.
Bu canlı, cihaz üzerinde, yapay zeka destekli deneyimi başka bir şeye dönüştürmek için seçtiğiniz temsilciyi GitHub depomuza yönlendirin.
Müze rehberi, mutfak tezgahınıza bakan bir yemek pişirme asistanı, onarım kılavuzu, erişilebilirlik anlatıcısı veya saha denetim aracı olarak kullanılabilir.
Model, çalışma zamanı, açık kaynaklı uygulama ve araçlar artık kullanıma hazır. Böylece, bu sonbaharda piyasaya sürülecek akıllı gözlükler için hazırlık yapabilirsiniz.
Bu özellikten yararlanarak neler üreteceğinizi merakla bekliyoruz.
Özetlemek gerekirse bugün, cihaz üzerinde sorunsuz çalışan yapay zeka modelleriyle neler yapılabileceğine dair etkileyici örnekler gösterdik.
Başlamak için birkaç öneri listeledik.
Öncelikle ekrandaki kaynakları toplayın.
Demoyu denemek için AI Edge Gallery uygulamasını indirin ve GitHub'daki referans koduna göz atın.
Android XR SDK'sı, ML Kit API'leri ve Google AI Edge tekliflerine erişmek için QR kodlarını tarayın.
İkincisi, telefonunuzda denemelere hemen başlayabilirsiniz.
Bahsettiğimiz birçok şey, mobil öncelikli bir deneyimle oluşturulabilir.
Üçüncü olarak, mevcut mobil uygulamanıza bakın ve eller serbest olarak kullanıldığında daha iyi olacak temel özelliklerin neler olduğunu düşünün.
Bu deneyimi gözlüklere de taşımak için mevcut APK'nıza bir Projected Activity eklemeniz yeterlidir.
Bugün bize katıldığınız için çok teşekkür ederiz.
Android'de ve akıllı gözlükler için oluşturacağınız muhteşem ses öncelikli deneyimleri görmek için sabırsızlanıyoruz.
Teşekkür ederiz.
dependencies {
implementation("androidx.xr.projected:projected:1.0.0-alpha13")
}class ProjectedMainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Glasses content
}
}
}<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>// 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 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)
}
}
}android skills add projected-hardware
android skills add --alldependencies {
// ML Kit GenAI TTS SDK
implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
}// 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
}// 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}")
}
}// 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()
}// 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()
)dependencies {
// ML Kit Speech Recognition
implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
}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() }
...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()
}
...// 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")
}
}// 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>"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?
)// 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": "けん玉をください。", ...}