অন-ডিভাইস AI দ্বারা পরিচালিত শক্তিশালী, হ্যান্ডস-ফ্রি অডিও অভিজ্ঞতা কীভাবে তৈরি করা যায় তা জানুন। আপনার যা যা প্রয়োজন আমরা সব কভার করব: আপনার চশমার অ্যাক্টিভিটি সেট-আপ করা, মাইক ও ক্যামেরা ফিড ক্যাপচার করা এবং আপনার আল্টিমেট UI হিসেবে অন্যান্য অডিও কিউয়ের সাথে ভয়েস ব্যবহার করা। Google AI Edge Gallery অ্যাপে তৈরি ওপেন-সোর্স XR Language Teacher-এর একটি উত্তেজনাপূর্ণ রিয়েল-ওয়ার্ল্ড শোকেসের মাধ্যমে, Gemini Nano 4 (Gemma 4-এ তৈরি এবং Android ডিভাইসের জন্য অপ্টিমাইজ করা) এবং ML Kit অর্কেস্ট্রেট করতে শিখুন এবং LiteRT রানটাইম ব্যবহার করে দ্রুত XR অভিজ্ঞতা তৈরি করুন যা স্বাভাবিকভাবে শোনে, দেখে এবং কথা বলে।
ট্রান্সস্ক্রিপ্ট করা
হ্যালো, সবাইকে স্বাগত।
আজকে সবাইকে দেখে খুব ভালো লাগছে।
আসার জন্য ধন্যবাদ।
আমি সুপ্রীতি।
আজ, আমার সহকর্মী, অ্যালিস আমার সাথে যোগ দেবেন এবং আমরা কথা বলব যে কীভাবে আপনি Gemini Nano 4 ব্যবহার করে ইন্টেলিজেন্ট আইওয়্যারের জন্য অবিশ্বাস্য অডিও-ফার্স্ট অভিজ্ঞতা তৈরি করতে পারেন।
তাহলে, আসুন শুরু করা যাক।
তাই আমরা যখন ইন্টেলিজেন্ট আইওয়্যার সম্পর্কে কথা বলি, তখন আমরা পোর্টেবল, হালকা চশমার কথা বলি যা সারাদিন পরে থাকার জন্য তৈরি করা হয়েছে।
এই বিভাগে ডিসপ্লে ও শুধু অডিও চশমা, দুটিই অন্তর্ভুক্ত করা হবে এবং এইসব ডিভাইসের সাথে, আপনি এখানে কিছু ভিন্ন হার্ডওয়্যার দেখতে পাবেন।
তাই, আপনি প্রথমে যে জিনিসটি লক্ষ্য করবেন তা হল, এতে একটি টাচ প্যাড থাকবে, যা ব্যবহারকারীর ইনপুটের জন্য ব্যবহার করা যেতে পারে, যেমন ট্যাপ এবং সোয়াইপ।
এছাড়াও, এতে স্পিকার, ক্যামেরা ও মাইক্রোফোন থাকবে।
এবং তারপরে তাদের একটি ডিসপ্লে থাকতে পারে বা নাও থাকতে পারে, বা সম্ভাব্যভাবে এর কিছু সংমিশ্রণ থাকতে পারে।
আজ আমরা অডিও-ফার্স্ট এবং কীভাবে একটি আকর্ষক, বুদ্ধিমান, অন-দ্য-গো অভিজ্ঞতা তৈরি করা যায় সে সম্পর্কে কথা বলছি।
আর শুধু অডিওর ক্ষেত্রে একটি আকর্ষণীয় বিষয় হল, এখানে ভয়েসই আপনার UI হয়ে ওঠে।
এছাড়াও, যেসব ডেভেলপার অফলাইন ও চলতে চলতে অভিজ্ঞতা খুঁজছেন তাদের জন্য অন-ডিভাইস AI দারুণ।
আর একটি দারুণ খবর আছে।
তাই আপনি যদি আগে থেকেই Android-এর জন্য তৈরি করে থাকেন, তাহলে আপনি আগে থেকেই Android XR-এর জন্য তৈরি করছেন।
বর্তমান Android অ্যাপগুলি অডিও গ্লাসে ভালভাবে কাজ করার জন্য ডিজাইন করা হয়েছে, যা ব্লুটুথের মাধ্যমে পঠিত বিজ্ঞপ্তি এবং অন্যান্য অডিও প্রদান করে।
আপনি যদি আরও এগিয়ে যেতে চান এবং XR-এর জন্য আপনার অ্যাপকে আলাদা করতে চান, তাহলে তা করার অনেক উপায় আছে।
তাই এই আলোচনায় আমরা অন-ডিভাইস মডেল সম্পর্কে কথা বলছি, তবে এটিই সম্পূর্ণ তালিকা নয়।
আমরা-- ক্লাউড মডেলও আছে।
Gemini Live API-এর মতো জিনিস আছে।
আপনি Gemini-র মতো সিস্টেম এজেন্টের সাথে ইন্টিগ্রেট করতে পারেন এবং এই সবকটিই আপনার জন্য সেরা অভিজ্ঞতা তৈরি করতে সাহায্য করতে পারে।
তাহলে, আপনি কীভাবে এমন অভিজ্ঞতা তৈরি করবেন যা স্বাভাবিকভাবে শোনে, দেখে এবং কথা বলে?
আজ, আমরা আপনাকে অন-ডিভাইস AI দ্বারা পরিচালিত এই শক্তিশালী, হ্যান্ডস-ফ্রি অভিজ্ঞতা তৈরি করার মাধ্যমে নিয়ে যাব।
আমি আপনাকে প্রাথমিক বিষয়গুলি সম্পর্কে বলব: কীভাবে চশমার জন্য তৈরি করতে হয়, কীভাবে আপনার অ্যাক্টিভিটি সেট-আপ করতে হয়, আপনার প্রয়োজনীয় অনুমতিগুলি পেতে হয় এবং তারপরে কীভাবে কথোপকথনমূলক UI-এর জন্য ML Kit ব্যবহার করতে হয়।
এরপর আমি অ্যালিসকে দায়িত্ব দেব এবং সে Gemini Nano 4 ব্যবহার করে কিছু বাস্তব উদাহরণ দিয়ে আপনাকে বোঝাবে।
তাহলে চলুন, কোডটি আরও ভালভাবে বুঝে নেওয়া যাক।
তাই চশমার সাথে, ফোনটিই হল সেই ডিভাইস যা ভারী কাজ করে এবং চশমার কন্টেন্ট ফোন থেকে চশমাতে প্রজেক্ট করা হবে।
তাই প্রথমবার সেট-আপ করার সময় এই ডিভাইসগুলি ব্রিজ করার জন্য আপনার দুটি জিনিস প্রয়োজন।
আপনার প্রথমে যা প্রয়োজন হবে তা হল Jetpack Projected লাইব্রেরি।
আপনাকে এই ডিপেন্ডেন্সি যোগ করতে হবে এবং এটিই সেই প্রোজেক্টেড অভিজ্ঞতাকে শক্তিশালী করার জন্য API ধারণ করতে চলেছে।
dependencies {
implementation("androidx.xr.projected:projected:1.0.0-alpha13")
}এবং তারপরে আপনাকে চশমার জন্য বিশেষভাবে একটি নতুন অ্যাক্টিভিটি তৈরি করতে হবে।
class ProjectedMainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Glasses content
}
}
}তারপর আপনি আপনার বর্তমান অ্যাপ এবং এই নতুন অ্যাক্টিভিটি, দুটির মধ্যেই Jetpack Projected লাইব্রেরি ব্যবহার করবেন যাতে দুটি ডিভাইস প্রয়োজন মতো একে অপরের সাথে নির্বিঘ্নে যোগাযোগ করতে পারে।
তাই আপনি দেখেছেন এমন যেকোনও সাধারণ অ্যাক্টিভিটির মতোই, আপনি এটি আপনার ম্যানিফেস্টে ঘোষণা করতে চলেছেন।
<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>মূল পার্থক্য হল, আপনাকে requiredDisplayCategory হিসেবে XR_PROJECTED ব্যবহার করতে হবে।
যাতে সিস্টেম বুঝতে পারে যে এই অ্যাক্টিভিটি চশমার জন্য।
এবং তারপর আপনি এখানে এই ইনটেন্ট ক্যাটেগরি, XR_PROJECTED_LAUNCHER ব্যবহার করতে চাইবেন।
এর ফলে Gemini আপনার অ্যাপ খুঁজে পায় এবং ব্যবহারকারীরা চলতে চলতে হ্যান্ডস-ফ্রি অবস্থায় এটি লঞ্চ করতে পারেন।
ঠিক আছে।
তাই, আমি যেমন বলেছি, একবার আপনি সেই অ্যাক্টিভিটি সেট-আপ করে নিলে, সেই অ্যাপ অভিজ্ঞতাকে আরও উন্নত করতে আপনি সেই প্রোজেক্টেড লাইব্রেরি ব্যবহার করবেন।
এই লাইব্রেরিতে এমন সব টুল আছে যা চশমার সম্পূর্ণ অভিজ্ঞতা তৈরি করার জন্য আপনার প্রয়োজন।
তাই, আপনি এখানে ব্যবহার করবেন এমন কিছু মূল API-এর মাধ্যমে চলুন।
তাই আমি প্রথমে 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)এইভাবে আপনি চশমার কানেকশন ক্রমাগত মনিটর করতে পারবেন।
চশমা কখন আসলে উপলভ্য থাকে তা জানতে আপনি ডিভাইসের লাইফ সাইকেলের সাথে এটি ব্যবহার করেন, তাই শুধু যখন সেগুলি পেয়ার করা বা চালু করা হয় তখন নয়, বরং যখন সেগুলি পরা হয় এবং ব্যবহারের জন্য রেডি থাকে তখন।
আর এটাই হল, তাই, হ্যাঁ।
তাই, আপনি কখন সেই অ্যাক্টিভিটি লঞ্চ করার জন্য প্রস্তুত তা এটি আপনাকে সঠিকভাবে জানাবে।
তাই এটি করার জন্য, আপনি যখন অ্যাক্টিভিটি লঞ্চ করার জন্য প্রস্তুত হবেন, তখন অ্যাক্টিভিটি শুরু করার সময় আপনাকে কিছু নির্দিষ্ট বিকল্প সেট করতে হবে।
আর সেগুলির জন্য, আপনি ProjectedContext-এ createProjectedActivityOptions ব্যবহার করতে চলেছেন।
এবং আবার, আপনি সেখানে ইনটেন্টের প্রসঙ্গে এটি ব্যবহার করবেন।
এবং তারপরে আরও একটি টুল যা আপনি ব্যবহার করতে চাইবেন তা হল ProjectedDeviceController।
ব্যবহারকারী কোন ধরনের চশমা ব্যবহার করছেন তা আপনি এইভাবে চেক করবেন।
তাই আমি আগে যেমন বলেছি যে ইন্টেলিজেন্ট আইওয়্যারে শুধু ডিসপ্লে ও অডিও চশমা অন্তর্ভুক্ত থাকে, এটি-- আপনি দেখতে চাইবেন যে এই CAPABILITY_VISUAL_UI-এ ক্ষমতা আছে কিনা এবং এটি আপনাকে বিভিন্ন ডিভাইসের জন্য আপনার অ্যাপকে সুন্দরভাবে মানিয়ে নিতে এবং সেই ডিভাইসের জন্য অপ্টিমাইজ করা অভিজ্ঞতা তৈরি করতে দেবে।
তাই আপনি যখন এই আলাদা অভিজ্ঞতা তৈরি করছেন, তখন আপনি ডিভাইসের হার্ডওয়্যার অ্যাক্সেস করতে চাইতে পারেন, যেমন, চশমার ক্যামেরা ব্যবহার করা।
তাই, প্রোজেক্ট করা অ্যাক্টিভিটি থেকে এটি করতে, আপনাকে 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)
}
}
}এমনকি আপনার অ্যাপে ফোনের জন্য প্রয়োজনীয় অনুমতি আগে থেকেই থাকলেও, আপনাকে চশমার হার্ডওয়্যারের জন্য নির্দিষ্ট অনুমতির অনুরোধ করতে হবে।
এছাড়াও, অনুমতি নিয়ে কাজ করার সময়, সাধারণভাবে Android-এর জন্য পেশাদার পদ্ধতি অনুসরণ করার কথা মনে রাখবেন।
ম্যানিফেস্টে প্রয়োজনীয় অনুমতি ঘোষণা করুন, যে প্রসঙ্গে অনুমতি প্রয়োজন সেই প্রসঙ্গেই অনুমতি চান এবং আপনার অ্যাপের কাজ করার জন্য আসলে যে অনুমতি প্রয়োজন শুধু সেই অনুমতিই চান।
তাই, অবশ্যই, আমাদের কাছে developer.android.com-এ আমাদের সাইটে এই ডকুমেন্ট করা আছে।
তবে আমাদের কাছে একটি ব্র্যান্ড নিউ স্কিলও আছে, সেটি হল প্রোজেক্টেড হার্ডওয়্যার স্কিল।
আপনার অনুমতির ওয়ার্কফ্লো সেট-আপ করতে, Android Studio-তে Android CLI-এর সাথে এটি ব্যবহার করতে পারবেন এবং এই স্কিল ও অন্যান্য সব Android স্কিলও অ্যাক্সেস করতে পারবেন।
android skills add projected-hardware
android skills add --allঠিক আছে, এখন আমরা কানেক্ট করেছি, আমাদের হার্ডওয়্যার ক্ষমতা সম্পর্কে সচেতন এবং সঠিক অনুমতি আছে, আসুন সেই টুলগুলি সম্পর্কে কথা বলি যা আপনাকে সেই অডিও অভিজ্ঞতা তৈরি করতে হবে।
তাই, অডিও ফার্স্ট অভিজ্ঞতা ডিজাইন করার সময়, আমরা নিশ্চিত করতে চাই যে ব্যবহারকারীদের জন্য খুব ভালো কোয়ালিটির অভিজ্ঞতা তৈরি করছি।
এবং এর মধ্যে কিছু মূল ইন্টার্যাকশন অন্তর্ভুক্ত থাকতে পারে।
তাই, প্রথমটি হল আউটপুট হিসেবে ভয়েস ব্যবহার করা, অর্থাৎ আপনার ভয়েসকে ব্যবহারকারীর সাথে স্বাভাবিকভাবে কথা বলার অনুমতি দেওয়া।
এরপর, ব্যবহারকারীকে নির্দিষ্ট অডিও সিগন্যাল পাঠানোর জন্য ইয়ারকন ব্যবহার করা হবে।
এবং তারপরে ব্যবহারকারীর থেকে ইনপুট ম্যানেজ করা, তা ট্যাপ এবং সোয়াইপ ব্যবহার করে টাচপ্যাডের মাধ্যমে হোক বা আপনি যদি ব্যবহারকারীকে আপনার অ্যাপে কথা বলার অনুমতি দিতে চান।
তাই, আমরা প্রথমে যে বিষয়টি নিয়ে কথা বলব তা হল আউটপুট হিসেবে ভয়েস ব্যবহার করা।
তাই এর জন্য, আমি ML Kit-এর নতুন GenAI টেক্সট-টু-স্পিচ লাইব্রেরি চালু করতে চাই।
এটি এখন আলফা ভার্সনে উপলভ্য এবং এটি বাছাই করা Pixel ও Samsung ডিভাইসে রয়েছে।
এই বিষয়ে আমরা অত্যন্ত উৎসাহিত, বিশেষ করে এই অডিও-ফার্স্ট অভিজ্ঞতার জন্য।
আপনি যদি আগে টেক্সট-টু-স্পিচ ব্যবহার করে থাকেন, তাহলে আপনি হয়ত জানেন যে সিস্টেম ভয়েস সবসময় আপনার পছন্দমতো স্বাভাবিক শোনায় না।
অন্যান্য ভয়েসের জন্য ক্লাউড মডেলের সাথে কানেক্ট করলে, সম্ভাব্য লেটেন্সি বা অতিরিক্ত খরচ হতে পারে।
তাই ML Kit আপনাকে সিস্টেম TTS-এর তুলনায় অনেক বেশি স্বাভাবিক-সাউন্ডিং স্পিচ দেয়।
এবং ML Kit আটটি অনন্য ভয়েস অফার করে যেগুলি থেকে আপনি বেছে নিতে পারবেন, আপনার অ্যাপের জন্য বিভিন্ন বৈশিষ্ট্য রয়েছে।
এছাড়াও, ML Kit-এ ২১টি আলাদা আলাদা লোকেল সহ শক্তিশালী ভাষা সহায়তা অফার করা হয়, যেগুলি থেকে আপনি বেছে নিতে পারবেন।
আর সবচেয়ে বড় কথা হল, একবার ডাউনলোড করার পরে এটি সম্পূর্ণভাবে ডিভাইসেই কাজ করবে।
তাই এই মডেলটি সিস্টেমে ডাউনলোড করা হবে এবং এটি অ্যাপ জুড়ে শেয়ার করা হবে, তাই এটি আপনার APK-এর সাইজ বাড়াবে না।
এবং এটি শুধু অফলাইনে উপলভ্য নয়, যা বিশেষ করে চশমা ব্যবহারকারীদের জন্য গুরুত্বপূর্ণ, যারা চলতে চলতে ব্যবহার করেন, তাদের কাছে ওয়াই-ফাই নাও থাকতে পারে বা তারা দুর্বল কভারেজ এলাকায় থাকতে পারেন, তবে আপনি আবার আরও ভালো পারফর্ম্যান্স দেখতে পাবেন, কারণ আপনাকে সার্ভারে সেই রাউন্ড ট্রিপ কল করতে হবে না।
ঠিক আছে।
তাই, লাইব্রেরির বিবরণ এবং ঠিক কীভাবে আমরা এটি দিয়ে তৈরি করতে পারি তা দেখা যাক।
তাই প্রথমে, অবশ্যই, আপনাকে শুধু ডিপেন্ডেন্সি যোগ করতে হবে।
dependencies {
// ML Kit GenAI TTS SDK
implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
}তাই এখানে আপনার যা প্রয়োজন তা হল ML Kit-এর টেক্সট-টু-স্পিচ SDK।
তারপর আপনি কোড লেখা শুরু করতে পারবেন।
তাই, GenAI ML Kit লাইব্রেরিতে স্পিচ সিন্থেসাইজার API অন্তর্ভুক্ত থাকে।
// 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
}এটি আপনাকে নিজের পছন্দের ভয়েস প্রয়োগ করার একটি সহজ উপায় অফার করে।
আর এখানে প্রথম যে ধাপটি আপনি করতে চলেছেন তা হল, আপনার বেছে নেওয়া লোকেল সেট করে, সেই স্পিচ সিন্থ- সিন্থেসাইজার আবার ব্যবহার করে আপনার ক্লায়েন্টকে ইনিশিয়ালাইজ করা।
তারপর, আপনি ডাউনলোডের স্ট্যাটাস চেক করবেন এবং ভয়েস বেছে নেবেন।
এই উদাহরণে, আমরা প্রথম ভয়েসটি বেছে নিচ্ছি, তবে আমি এখানে এটিও উল্লেখ করতে চাই যে, আমরা firstOrNull ব্যবহার করছি।
আমি যেমন বলেছি, ML Kit-এর জন্য নির্দিষ্ট ক্ষমতা সম্পন্ন ফোন প্রয়োজন, তাই Samsung বা Pixel ডিভাইস ব্যবহার করতে হবে।
তাই, আপনি যদি কোনও নাল ভ্যালু পান, তাহলে এর অর্থ হতে পারে যে ব্যবহারকারী হয়ত মানানসই ফোন ব্যবহার করছেন না।
এবং তারপরে আপনি শুধু নিশ্চিত করতে চান যে আপনার কাছে ফলব্যাক আছে, তা সিস্টেম টেক্সট টু স্পিচে ফিরে যাওয়া হোক বা রেকর্ড করা মেসেজ চালানো বা এই ধরনের কিছু।
ঠিক আছে, তারপর আপনি স্পিচ সিন্থে- SpeechSynthesizerRequest ব্যবহার করে অনুরোধ তৈরি করবেন, ইনপুট টেক্সট দেবেন এবং তারপর ব্যবহার ও কন্টেন্টের ধরন সেট করবেন।
// 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()
}আর তাই এটি বেশ সহজ, তাই না?
এইসব ধাপের মাধ্যমে, আপনি সত্যিই আপনার অ্যাপের সাউন্ড পরিবর্তন করতে পারবেন, যা আগে এরকম শোনা যেত…
হ্যালো, Android ডেভেলপার।
আজ আপনাদের সবাইকে দেখে খুব ভালো লাগছে।
আশা করি, অডিও-ফার্স্ট অভিজ্ঞতা সংক্রান্ত আমাদের প্রেজেন্টেশন আপনারা সবাই উপভোগ করছেন।
এমন কিছুতে যা আরও বেশি করে এইরকম শোনাবে।
হ্যালো Android ডেভেলপার।
আজ আপনাদের সবাইকে দেখে খুব ভালো লাগছে।
আশা করি, অডিও-ফার্স্ট অভিজ্ঞতা সংক্রান্ত আমাদের প্রেজেন্টেশন আপনারা সবাই উপভোগ করছেন।
অনেক ভালো, তাই না?
ঠিক আছে।
ML Kit-এর টেক্সট-টু-স্পিচ লাইব্রেরি একটি অত্যন্ত শক্তিশালী টুল, তবে আপনি শুধু এই ভয়েসগুলি ব্যবহার করার মধ্যেই সীমাবদ্ধ নন, এই লাইব্রেরির মাধ্যমে আপনি ইয়ারকনও চালাতে পারবেন।
// 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()
)তাই, ইয়ারকন, নাম থেকেই বোঝা যায়, মূলত আইকনের অডিও সমতুল্য।
এটি আপনাকে ব্যবহারকারীর সাথে যোগাযোগ করার জন্য ছোট সাউন্ড চালানোর অনুমতি দেবে।
আর ইয়ারকন সহ ভিজ্যুয়াল আইকনের মতোই, ব্যবহারকারীকে জানতে হবে যে এগুলি কী এবং এগুলির অর্থ কী।
তাই, প্রাথমিকভাবে আপনি ভয়েস বিজ্ঞপ্তির সাথে এগুলি পেয়ার করতে চাইতে পারেন যাতে বিষয়টি স্পষ্ট হয়।
আমরা আগে থেকেই কিছু স্ট্যান্ডার্ড সাউন্ড তৈরি করেছি যা Android Design Hub-এ উপলভ্য, আপনি সেগুলি ব্যবহার করতে পারেন, আপনি সেগুলি সরাসরি সেখান থেকে ডাউনলোড করতে পারেন এবং অ্যাপ খোলা, অ্যাপ বন্ধ করা, ত্রুটির বিজ্ঞপ্তি এবং মিউট বা আনমিউট করার মতো কাজের জন্য সেগুলি ব্যবহার করতে পারেন।
এছাড়াও, আপনার কাছে নিজের কাস্টম ইয়ারকন তৈরি করার বিকল্পও আছে।
তাই, ইয়ারকনের ক্ষেত্রে, আপনি Design Hub থেকে ডাউনলোড করা ইয়ারকন ব্যবহার করুন বা নিজের তৈরি করা ইয়ারকন ব্যবহার করুন, আমরা কিছু পেশাদার পদ্ধতি অনুসরণ করার সাজেশন দিই।
তাই ইয়ারকন কীভাবে ব্যবহার করবেন সেই বিষয়ে আপনাকে সচেতন হতে হবে।
তাই কিছু জিনিস মনে রাখতে হবে, যেমন, শুরু করার জন্য, ইয়ারকন অতিরিক্ত ব্যবহার না করার চেষ্টা করুন।
একই সাউন্ড বারবার রিপ্লে করবেন না, এর ফলে কানের ক্লান্তি হতে পারে।
মনে রাখবেন, আপনার ইয়ারকন যেন দারুণ ও স্বজ্ঞাত হয় এবং সেগুলি প্রয়োগ করার সময় পরিচিত পিচ ও রিদম ব্যবহার করুন।
এবং তারপরে, খুব জটিল ইয়ারকন এড়িয়ে চলুন যেগুলির ব্যাখ্যা প্রয়োজন, এগুলি ভয়েস বিজ্ঞপ্তি হিসেবে আরও ভাল মানানসই হতে পারে।
আর সবসময় মনে রাখবেন যে স্টুডিওতে বা আপনার সিস্টেমে, আপনার কম্পিউটারের স্পিকারে যা সূক্ষ্ম শোনাতে পারে, তা আপনার ব্যবহারকারীদের কাছে বারবার প্লেব্যাক করার পরে বিরক্তিকর হয়ে উঠতে পারে।
তাই আপনার ব্যবহারকারীদের এমন শব্দ দিয়ে ওভারলোড না করার জন্য যথাসাধ্য চেষ্টা করুন যা তাদের বিভ্রান্ত করতে পারে বা তাদের গুরুত্বপূর্ণ সতর্কতা উপেক্ষা করতে পারে।
ঠিক আছে।
তাই এখন আমরা স্বাভাবিক স্পিচ বা ইয়ারকন ব্যবহার করে অডিও ইঙ্গিত এবং প্রতিক্রিয়া প্রদান করতে পারি।
কিন্তু ইনপুটের কী হবে?
তাই অবশ্যই একটি টাচপ্যাড আছে, যা বা ডিভাইসে নির্দিষ্ট বোতামও আছে, যা আপনি ট্যাপ এবং সোয়াইপ ইন্টারঅ্যাকশন তৈরি করতে ব্যবহার করতে পারেন।
তবে আপনি এই কথোপকথনমূলক UI-এর জন্য অনুমতি দিতে চাইতে পারেন, এর জন্য আপনি ML Kit-এর GenAI স্পিচ রিকগনিশন API ব্যবহার করতে পারেন।
বর্তমানে আলফা ভার্সনে এবং বাছাই করা ডিভাইসে উপলভ্য, তবে মূলত এটি দুটি আলাদা মোডে চলে।
তাই বেসিক মোড আছে যা প্রথাগত অন-ডিভাইস স্পিচ রিকগনিশন ব্যবহার করে এবং এটি API লেভেল ৩১ ও তার পরবর্তী যেকোনও ভার্সনের Android ডিভাইসে উপলভ্য।
এছাড়াও, উন্নত মোড আছে যা Gemini Nano ব্যবহার করে, এটি Pixel 10 ও তার পরবর্তী ভার্সনের ডিভাইসে অফার করা হয় এবং এর মাধ্যমে আপনি আরও ভাল কোয়ালিটি ও আরও বেশি ভাষার কভারেজ পাবেন।
তাই SpeechRecognizer, SpeechSynthesizer-এর মতো একই ধরনের কোড প্যাটার্ন অনুসরণ করে।
আপনি নিজের ক্লায়েন্ট তৈরি করেন, মডেল উপলভ্য আছে কিনা তা চেক করেন, প্রয়োজন হলে ডাউনলোড করেন, অনুরোধ তৈরি করেন এবং আপনার রিসোর্স রিলিজ করেন।
তাই, আসুন আমরা এখানে মূল পার্থক্যগুলির উপর ফোকাস করি যেখানে আপনি নিজের অনুরোধ তৈরি করছেন, সেই মাইক অডিও ক্যাপচার করছেন এবং তারপরে ফলাফল প্রসেস করছেন।
আবার, আপনার প্রয়োজনীয় ডিপেন্ডেন্সি যোগ করতে হবে।
dependencies {
// ML Kit Speech Recognition
implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
}এখানে আপনার যা প্রয়োজন তা হল স্পিচ রিকগনিশন API।
তারপর আপনি নিজের অনুরোধ তৈরি করা শুরু করতে পারবেন।
তাই আপনার শনাক্তকরণ লজিক সংজ্ঞায়িত করার সময় আপনি একটি সাসপেন্ড ফাংশন ব্যবহার করবেন।
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() }
...তারপর, আপনার স্পিচ রিকগনিশন বিকল্প নির্দিষ্ট করুন, লোকেল ও preferredMode সেট করুন।
এবং তারপরে, এখান থেকেই আপনি audioSource বেছে নেবেন এবং অনুরোধটি কী তা নির্ধারণ করবেন।
তারপর আপনি রিকগনি- রিকগনিশন শুরু করবেন এবং উত্তর সংগ্রহ করবেন।
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-এর মাধ্যমে আপনি চারটি সম্ভাব্য উত্তর পাবেন: আংশিক, চূড়ান্ত, সমস্যা এবং সম্পূর্ণ।
আংশিক রিয়েল-টাইম স্ট্রিম দেখবে, তাই এটি হটওয়ার্ড বা কীওয়ার্ডের মতো চিন্তা করার জন্য ব্যবহৃত হয়।
Final হল সম্পূর্ণ বিশ্লেষণ, আপডেট করার পরে ফাইনাল অডিও স্ট্রিম নেওয়া এবং তারপর স্পিচ সম্পূর্ণ হওয়ার পরে completed এবং তারপর কোনও এরর হলে error সংগ্রহ করা।
আর আমরা কীভাবে এই সব কিছু একসাথে করি তা এখানে দেওয়া হল।
// 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")
}
}ধরা যাক, আমরা চাই যে ব্যবহারকারী যেন ভয়েস কমান্ডের মাধ্যমে অ্যাপকে কোনও কন্টেন্ট পরে দেখার জন্য সেভ করতে বলতে পারেন।
আমরা তাদের যে নির্দিষ্ট ভয়েস কমান্ড ব্যবহার করতে বলেছি, আপনি সেটি ব্যবহার করতে পারেন অথবা তাদের একাধিক কীওয়ার্ড এবং বাক্যাংশ ব্যবহার করার অনুমতি দিতে পারেন।
তাই এখানে আমরা সেট করেছি- আমরা তাদের ব্যবহার করার অনুমতি দিচ্ছি: সেভ, বুকমার্ক বা মনে রাখা।
SpeechRecognizer যখনই শনাক্ত করবে যে ব্যবহারকারী এই ধরনের কোনও একটি শব্দগুচ্ছ ব্যবহার করেছেন, তখনই এটি কন্টেন্ট সেভ করে স্পিচ শনাক্তকরণ বন্ধ করে দেবে, কাস্টম ইয়ারকন চালাবে এবং ML Kit-এর GenAI টেক্সট-টু-স্পিচ ব্যবহার করে ব্যবহারকারীকে জানাবে যে কন্টেন্ট সেভ করা হয়েছে।
ঠিক আছে।
তাই, আমাদের অনেক কিছু সহ্য করতে হয়েছে।
এটি কিছুটা বিমূর্ত মনে হতে পারে।
আপনি যদি আমার মতো হন, তাহলে আপনি কিছু বাস্তব জীবনের উদাহরণ দেখতে চান।
আর অ্যালিস ও আমি আপনাকে ঠিক সেটাই দেখাতে চাই।
তিনি আপনাকে Google AI Edge Gallery অ্যাপে XR ল্যাঙ্গুয়েজ টিচারের মাধ্যমে গাইড করবেন।
অ্যালিসের টিম এটি তৈরি করেছে এবং এটি কীভাবে কাজ করে তা তিনি আপনাকে দেখাবেন।
এবার আপনার পালা।
ধন্যবাদ, সুপ্রীতি।
হ্যাঁ।
তাই, আমি এমন কিছু দিয়ে শুরু করতে চাই যা আপনারা অনেকেই চিনতে পারবেন।
আমি জাপানি ভাষা শিখছি এবং ভাষা শেখার অ্যাপ, ফ্ল্যাশকার্ড, স্ট্রিক, সব কিছুতেই ঘণ্টার পর ঘণ্টা সময় কাটিয়েছি।
তারপর, আমি টোকিওতে পৌঁছাই, একটি মার্কেট হলে হেঁটে যাই এবং বুঝতে পারি যে আমার সামনে যা আছে তার প্রায় কিছুই আমি পড়া শব্দভাণ্ডারের মধ্যে নেই।
একটি বাজারের স্টলে কাঠের কাপ-এন্ড-বল খেলনা আছে যা আমি দেখে চিনতে পারি, কিন্তু আমি জাপানি ভাষায় কথা বলতে জানি না।
দোকানের জানালায় একটি অলঙ্কার আছে যা আমি কিনতে চাই এবং নাম বলতে পারছি না।
সেই মুহূর্তে আমি কোনও শব্দ শেখার জন্য সবচেয়ে বেশি অনুপ্রাণিত হই।
আমার সোফায় বসে কোনও ভাষা শেখার অ্যাপে নয়।
সেই মুহূর্তে, আমি যে জিনিসটি শিখতে চাই সেটিই দেখছি।
তাহলে, আমি যা চাই তা এখানে দেওয়া হল।
আমি সারাদিন স্মার্ট গ্লাস পরে ঘুরে বেড়াতে চাই।
আর যখনই কোনও কিছু আমার চোখে পড়ে, শুধু সেটির ব্যাপারে জোরে জিজ্ঞাসা করুন।
তারপর, রাতে হোটেলে ফিরে এসে আমি যা যা জানতে চেয়েছিলাম, সেইসব তথ্য আমার ফোনে ভোকাবুলারি কার্ডের মতো আমার জন্য অপেক্ষা করে থাকে।
আর আমরা এটিই তৈরি করেছি।
আসুন, একটি ছোট ক্লিপ দেখি।
তাই, আমরা এটিকে XR ল্যাঙ্গুয়েজ টিচার বলি।
হ্যালো।
আজ আপনি কোন বিষয়ে জানতে চান?
এছাড়াও, শিক্ষক সেটিংসে গিয়ে আপনার শেখার অভিজ্ঞতা কাস্টমাইজ করতে পারবেন।
যেমন, আপনি যে ভাষা শিখতে চান সেটি বেছে নিন এবং আপনার জন্য উপযুক্ত নির্দিষ্ট শিক্ষক ব্যক্তিত্ব বেছে নিন।
ওহ, হ্যাঁ।
আপনি চশমা ও ফোন মোডের মধ্যে পরিবর্তন করতে পারবেন।
স্প্যানিশ ভাষায় কীভাবে "এই আঙুরগুলি" বলতে হয়?
এটি দেখে মনে হচ্ছে, এই সুস্বাদু আঙুরকে uvas বলা হয়।
স্প্যানিশ ভাষায়, আপনি বলতে পারেন, "Estas uvas son deliciosas," এগুলির বর্ণনা দিতে।
আমি নির্দিষ্টভাবে সবুজ আঙুর বলতে চাই।
আমাকে এটি দেখতে দিন।
সবুজ আঙুর নির্দিষ্ট করতে, আপনি বলতে পারেন, "Uvas verdes।
Me gustan las uvas verdes," হল কাউকে আপনার পছন্দের কথা জানানোর একটি দুর্দান্ত উপায়।
ফরাসি ভাষায় এটি কীভাবে বলতে হয় এবং আমি কীভাবে তিনটি চাইতে পারি?
এটি চেক করে দেখা যাক।
এই সুস্বাদু পেস্ট্রিগুলিকে ডেস ম্যাডেলিন বলা হয়।
ফরাসি ভাষায়, তিনটি চাইতে, আপনি বলতে পারেন, "Je voudrais trois madeleines, s'il vous plaît."
এর সাথে পরিবর্তন করার জন্য কীভাবে অনুরোধ করবেন?
এখানে কী আছে দেখা যাক।
বাকি টাকা ফেরত চাইতে, আপনি বলতে পারেন, "Otsuri wa arimasu ka?"
যার অর্থ, "আপনার কাছে কি খুচরো আছে?"
এছাড়াও, আপনি "Otsuri o onegaishimasu" বলে ভদ্রভাবে বাকি টাকা ফেরত চাইতে পারেন।
তারপর, আপনি যা কিছু শিখেছেন তা অটোমেটিক আপনার জন্য ভোকাবুলারি কার্ড হিসেবে সেভ হয়ে যায়।
La botella de agua.
Esta es una botella de agua.
হ্যাঁ, আর তুমি স্টাডি ট্যাবে নিজের স্মৃতিশক্তি পরীক্ষা করতে এবং অনুশীলন করতে পারো।
ঠিক আছে।
হ্যাঁ।
তাই, আপনি যা কিছু দেখলেন তা ডিভাইসেই রান করেছে, নেটওয়ার্কের কোনও ভূমিকা নেই।
আমি আন্তর্জাতিক ডেটা প্ল্যান নিয়ে ভ্রমণ করি, যা সাধারণত ঠিকঠাকই কাজ করে, যতক্ষণ না আমি কোনও মার্কেট হল বা মাটির নিচে বা পুরু কংক্রিটের পিছনে থাকি।
আপনার যখন এটি সবচেয়ে বেশি প্রয়োজন হয়, ঠিক তখনই সিগন্যাল সবচেয়ে খারাপ থাকে।
আমি বলব, নেটওয়ার্ক না থাকলে যে অভিজ্ঞতা পাওয়া যায় না, তা কখনওই ভ্রমণের প্রকৃত সঙ্গী হতে পারে না। তাই আমরা XR Language Teacher-কে সম্পূর্ণ অফলাইন এবং ডিভাইসে কাজ করার উপযোগী করে তুলেছি।
তাহলে, এটি কীভাবে তৈরি করা হয়েছে?
আর্কিটেকচারাল দিক থেকে, চশমা থেকে ফোনে কীভাবে প্রোজেক্ট করা ক্যামেরা ফ্রেম ব্যবহার করতে হয় তা এমি আপনাকে আগেই দেখিয়েছে।
তিনি আপনাকে দেখিয়েছেন যে কীভাবে ML Kit, স্পিচ রিকগনিশন ব্যবহার করে টেক্সটকে স্পিচে পরিণত করা যায় এবং ML Kit টেক্সট-টু-স্পিচ ব্যবহার করে স্পিচকে আবার টেক্সটে পরিণত করা যায়।
আমাদের এখনও যা প্রয়োজন তা হল মাঝখানে একটি মস্তিষ্ক, এমন কিছু যা ক্যামেরার ফ্রেম এবং প্রশ্ন একসাথে দেখতে পারে এবং ব্যবহারকারী আসলে কী জিজ্ঞাসা করছেন তা বুঝতে পারে।
সেই মস্তিষ্ক হল Gemini Nano 4, যা Gemma 4-এর উপর তৈরি করা হয়েছে, এটি Google-এর ওপেন-সোর্স মডেল যা ফোনে লোকালি রান করে।
তাই, কাজ করে এমন ফ্ল্যাগশিপ Android ডিভাইসে, সরাসরি ML Kit API ব্যবহার করে আপনি অপ্টিমাইজ করা পারফর্ম্যান্স পেতে পারেন।
Android সিস্টেম আপনার জন্য মডেল ডেলিভারির বিষয়টি দেখবে, তাই আপনার অ্যাপে বান্ডেল করার জন্য আপনার কাছে কিছু নেই।
এই সব কিছু একসাথে করার জন্য, মাইক্রোফোন স্পিচ রিকগনিশনে যায় এবং ক্যামেরা ফ্রেম ও ট্রান্সক্রিপ্ট একসাথে Gemini Nano 4-এ যায় এবং Gemini Nano 4 স্পিচ সিন্থেসিসে লিখে এবং তারপর স্পিকার থেকে বের হয়।
তিনটি মডেল, চেইন করা, সবকটি আপনার ফোনে লোকালি চলছে।
Gemini Nano 4 কেন এই সমস্যার জন্য বিশেষভাবে উপযুক্ত তার কয়েকটি কারণ এখানে দেওয়া হল।
প্রথমত, এটি একবারে দেখে ও পড়ে।
ML Kit-এর মাধ্যমে, Gemini Nano 4 নেটিভভাবে টেক্সট ও ছবি ম্যানেজ করে, তাই আপনি ডিটেক্টর, তারপর ক্লাসিফায়ার, তারপর ট্রান্সলেটর ব্যবহার করছেন না।
এটি এমন একটি মডেল যা ক্যামেরার ফ্রেম ও প্রশ্ন একসাথে দেখে।
দ্বিতীয়ত, এটি অনেক ভাষায় কাজ করে।
Gemini Nano 4-কে ১৪০টিরও বেশি ভাষায় ট্রেনিং দেওয়া হয়েছে।
এছাড়াও, ভাষা শেখানোর জন্য, শুধু অনুবাদই যথেষ্ট নয়।
আপনি বাগধারা, ফর্মাল বনাম ক্যাজুয়াল রেজিস্টার এবং আসল পার্সন বলতে পারেন এমন উদাহরণ বাক্যও চান।
এবং সবশেষে, এটি যথেষ্ট পরিমাণে স্টিয়ারেবল যাতে এর একটি ব্যক্তিত্ব থাকে।
জোরে জোরে উচ্চারণ প্র্যাক্টিস করাটা ভীতিজনক হতে পারে।
এই মডেল সিস্টেম প্রম্পট ভালভাবে অনুসরণ করে, তাই ব্যবহারকারী নিজের শিক্ষক পারসোনা বেছে নিতে পারেন।
আর ধৈর্যশীল ও উৎসাহব্যঞ্জক হলে, লোকজন আরও বেশি করে চেষ্টা করতে চাইবেন।
আর ডিজাইন সম্পর্কে আরও একটি বিষয় আমি হাইলাইট করতে চাই।
গ্লাস মোডে কথোপকথন চলাকালীন, অ্যাপটি ব্যাকগ্রাউন্ডে নিঃশব্দে শব্দভাণ্ডার কার্ড লিখছে।
আপনি প্রশ্ন করার সময় থেকে শব্দ, অনুবাদ, উদাহরণ বাক্য এবং ফটো।
ব্যবহারকারীকে ম্যানুয়ালি কিছু ট্যাপ করতে হবে না।
তারা শুধু হাঁটছে আর কথা বলছে।
তারপর, সেই দিনই পরে, সবকিছু তার ফোনে তার জন্য অপেক্ষা করছে, যা আমি টোকিওতে থাকার সময় ঠিক যা চেয়েছিলাম।
তাই, অডিও ফার্স্ট মানে শুধু অডিও নয়।
Glasses হল কৌতূহল মেটানোর জন্য সঠিক সারফেস এবং আপনার ফোন হল পরে প্রতিফলন দেখার জন্য সঠিক সারফেস।
হ্যান্ডস-ফ্রি মুহূর্তের স্মৃতি রেখে দিন।
এখন, Gemini Nano 4 এবং ML Kit-এর সাহায্যে রিয়েল-টাইম অভিজ্ঞতা তৈরি করার জন্য কিছু ব্যবহারিক পরামর্শের উপর ফোকাস করা যাক, যা আমরা এই ডেমো তৈরি করার সময় শিখেছি।
পরামর্শ ১, টু-পাস ডিজাইনের মাধ্যমে রিয়েল-টাইম লেটেন্সিকে অগ্রাধিকার দিন।
অডিও গ্লাসে, আপনি যখন জিজ্ঞাসা করেন, "ওটা কী?"
নীরবতার প্রতিটি মুহূর্ত লক্ষ্য করা যায়।
XR ল্যাঙ্গুয়েজ টিচারের উদাহরণে, মডেল থেকে ব্যবহারকারীর ইন্টার্যাকশনের প্রতিটি টার্নের জন্য আমাদের দুটি জিনিস প্রয়োজন: একটি তাৎক্ষণিক মৌখিক উত্তর এবং অ্যাপে সেভ করার জন্য একটি স্ট্রাকচার্ড ভোকাবুলারি কার্ড।
আমরা যদি মডেলকে একটি পাসে একসাথে দুটি জিনিস জিজ্ঞাসা করি, তাহলে সেই ব্যাকগ্রাউন্ড ডেটা জেনারেশনে খরচ করা প্রতিটি টোকেন প্রথম বলা বাক্যটিকে বিলম্বিত করবে।
তাই, ব্যবহারকারীর এই মুহূর্তে কী প্রয়োজন এবং অ্যাপের কয়েক সেকেন্ড পরে কী প্রয়োজন, আমরা তা আলাদা করে দিই।
তাই আমরা প্রতিটি টার্নকে দুটি পাসে ভাগ করেছি।
পাস ১-এ, আমরা ক্যামেরা ফ্রেম এবং মৌখিক উত্তরের জন্য প্রশ্ন স্ট্রিম করি, প্রথম বাক্যটি সম্পূর্ণ হওয়ার সাথে সাথেই টেক্সট-টু-স্পিচ ট্রিগার করি।
এবং যেহেতু সেই TPU বা NPU টেক্সট-টু-স্পিচ জোরে জোরে কথা বলা শেষ করার অনেক আগেই সেই ছোট উত্তর তৈরি করা শেষ করে, তাই আমরা আসলে একটি মাল্টি-সেকেন্ড উইন্ডো পাই যেখানে ব্যবহারকারী এখনও শুনছেন এবং চিপটি ইতিমধ্যেই অলস বসে আছে।
এবং যেহেতু পাস ১ ইতিমধ্যেই তার টেক্সট উত্তরে অবজেক্টটি শনাক্ত করেছে, তাই পাস ২-এর এমনকি ইমেজ ইনপুটেরও প্রয়োজন নেই।
শিক্ষক কথা শেষ করার আগেই, আমরা শুধু Pass 1-এর উত্তর সেই আইডল চিপে পাস করি যাতে স্ট্রাকচার্ড কার্ড এক্সট্র্যাক্ট ও সেভ করা যায়।
এখন, পাস ১-এ জুম করা যাক, যা আমা-- আমাকে দ্বিতীয় পরামর্শে নিয়ে আসে।
স্ট্রিমিংয়ের সময় লাইটওয়েট XML ট্যাগ ব্যবহার করুন।
যেহেতু পাস ১ সরাসরি টেক্সট-টু-স্পিচে স্ট্রিম করে, তাই আপনি চাইবেন না যে ভারী JSON অবজেক্ট আপনার প্রথম বলা শব্দকে আটকে রাখুক।
পরিবর্তে, আপনি এমন প্লেন টেক্সট চান যা অ্যাপের মিড-স্ট্রিমে অ্যাকশন নেওয়ার প্রয়োজন হলে, হালকা, অন-ইনলাইন ট্যাগের মাধ্যমে অবিলম্বে কথা বলতে পারে।
তাহলে, যে সমস্যার কারণে আমরা এখানে এসেছি, সেটি আপনাকে দেখাই।
আমি যে কাঠের কাপ-ও-বল খেলনার কথা বলেছিলাম, সেটি কি মনে আছে?
জাপানি ভাষায় এটিকে "কেনডামা" বলা হয়।
শুরুতেই, মডেলটি ক্যামেরা ফ্রেম থেকে এটি শনাক্ত করে এবং সঠিক উত্তর দেয়, "এটি হল কেনডামা, একটি কাঠের খেলনা।"
তাই আমরা সেই সম্পূর্ণ বাক্যটি ইংরেজি টেক্সট-টু-স্পিচ সেশনে দিয়েছি এবং এখানে যা বেরিয়ে এসেছে তা হল।
এটি হল কেন-মি, কাঠের খেলনা।
এটি ঠিক লাগছে না।
পরিবর্তে, সেই বাক্যটি আসলে এমন শোনা উচিত…
এটি হল কেন্ডামা, একটি কাঠের খেলনা।
তাহলে কেন এমন হল?
কেনডামা হিরাগানা, "কেন" এবং কাঞ্জি অক্ষর, "ডামা" দিয়ে লেখা হয়।
এবং ইংরেজি টেক্সট-টু-স্পিচ ইঞ্জিন "ken"-এর মতো ফোনেটিক হিরাগানা ঠিকঠাক হ্যান্ডেল করে, কিন্তু কাঞ্জি অংশে পৌঁছালে এবং dama-কে আমার মতো শোনায় এমন কিছুতে পরিণত করার সময় সম্পূর্ণভাবে ভেঙে পড়ে।
আপনি যদি লোকেল পরিবর্তন না করেন, তাহলে বিভিন্ন ভাষায় বিভিন্ন উপায়ে এটি ঘটে।
যেমন, স্প্যানিশ ভাষায় আপনি শুনতে পেতে পারেন…
Suh-BOH-luh…
আপনি যখন সত্যিই আশা করছেন…
Cebolla.
একইভাবে, ফরাসি ভাষায় আপনি শুনতে পেতে পারেন…
PAIN oh chock-uh-lat…
আপনি যখন সত্যিই আশা করছেন…
পেন ও চকলেট।
হ্যাঁ, একটি TTS সেশন সাধারণত একবারে একটি লোকেলের জন্য ডিজাইন করা হয়, তবে একজন ভাষা শিক্ষককে প্রতিটি বাক্যে কোড-সুইচ করতে হবে।
তাই, পাস ১-এ, আমরা মডেলকে বলি যে স্ট্রিম করার সময় টার্গেট ভাষার প্রতিটি শব্দ বা বাক্যাংশকে টার্গেট XML ট্যাগের মধ্যে র্যাপ করতে।
আর চাঙ্ক আসার সাথে সাথে, আমাদের ক্লায়েন্ট টার্গেটে স্প্লিট করে, প্রতিটি স্প্যানকে মানানসই টেক্সট-টু-স্পিচ লোকেলে রুট করে, যা জাপানি, স্প্যানিশ, ফ্রেঞ্চ বা আপনি যে ভাষাই শিখছেন তা হতে পারে এবং তারপরে সেগুলি নির্বিঘ্নে প্লেব্যাক করে, বাক্যের মাঝখানে অ্যাকসেন্ট পরিবর্তন করে, যাতে কেন্ডামার মতো শব্দ সঠিকভাবে উচ্চারিত হয়।
সুতরাং, এখানে মূল বিষয় হল, মডেলটি আগে থেকেই জানে যে কোন শব্দভাণ্ডার কোন ভাষার অন্তর্গত, কোন শব্দ কোন ভাষার অন্তর্গত এবং স্ট্রিমিং করার সময় ইনলাইনে টার্গেট স্প্যান ট্যাগ করা, পরে এই পুরো জিনিসটি নিজে থেকে পোস্ট-প্রসেস করার চেয়ে অনেক বেশি পরিষ্কার।
আর এখানে কোডে পাস ১ আছে।
// 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>"সবচেয়ে উপরে, আমরা প্রম্পট ব্যবহার করে মডেলকে নির্দেশ দিই যাতে সেটি উত্তর ট্যাগের মধ্যে এক থেকে তিনটি বাক্যে উত্তর দেয় এবং তারপর প্রতিটি জাপানি শব্দ বা উদাহরণ বাক্যকে টার্গেট ট্যাগের মধ্যে রাখে।
তারপর আমরা ক্যামেরা ImagePart এবং প্রম্পট TextPart ML Kit-এর generateContentStream-এ পাস করি, প্রতিটি বাক্য আসার সাথে সাথে টার্গেট স্প্যানকে টেক্সট-টু-স্পিচে ভাগ করি।
আর এর মাধ্যমে আমরা দ্বিতীয় পাস এবং তৃতীয় পরামর্শে পৌঁছে গেছি।
লাইভ অডিও স্ট্রিমের জন্য লাইটওয়েট XML ট্যাগ ব্যবহার করা ভালো হলেও, স্ট্রাকচার্ড আউটপুট ব্যবহার করুন, যা আপনার অ্যাপে ডেটা সেভ করার সময় একটি ক্লিন, টাইপ করা অবজেক্ট রিটার্ন করার জন্য তৈরি করা হয়েছে।
আমাদের XR ল্যাঙ্গুয়েজ টিচার কথোপকথনের মাধ্যমে শেখায়, তাই ব্যবহারকারীরা ফলো-আপ প্রশ্নও করতে পারেন যেখানে কোনও নতুন শব্দ শেখানো হয় না।
এবং কোনও শব্দ শেখানো হলে, শব্দভাণ্ডার কার্ড তৈরি করার জন্য ডেটাবেসে পরিষ্কার অভিধান ফিল্ডের প্রয়োজন হয়।
তাই, পাস ১-এর ট্রান্সক্রিপ্ট পার্স করার জন্য ভঙ্গুর রেগুলার এক্সপ্রেশন লেখার পরিবর্তে, আমরা পাস ২-কে ব্যাকগ্রাউন্ডে টাইপ-সেফ অবজেক্ট এক্সট্র্যাক্ট করতে দিই।
এবং পাস ২-এ স্কিমা এনফোর্স করতে, আমরা ML Kit-এর স্ট্রাকচার্ড আউটপুট API ব্যবহার করি, যা ব্যাকগ্রাউন্ডে কনস্ট্রেইন্ড ডিকোডিং ব্যবহার করে।
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?
)আপনি শুধু @Generable দিয়ে ডেটা ক্লাস এবং @Guide দিয়ে প্রতিটি ফিল্ড অ্যানোটেট করেন, ঠিক যেমন এখানে আমাদের ভোকাবুলারি কার্ড ক্লাসের উদাহরণে দেখানো হয়েছে।
এবং নালেবেল কার্ড ফিল্ড র্যাপ করার মাধ্যমে, নতুন শব্দ শেখানো হলে পাস ২ একটি পপুলেট করা কার্ড রিটার্ন করে অথবা সম্পূর্ণ কথোপকথনের ক্ষেত্রে নাল রিটার্ন করে।
এবং এখানে কোডে পাস ২ আছে।
// 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": "けん玉をください。", ...}সবচেয়ে উপরে, আমরা পাস ১-এর স্ট্রিম করা উত্তর টেক্সট নিই, কোনও ছবির প্রয়োজন নেই এবং আমাদের এক্সট্র্যাকশন প্রম্পট তৈরি করি।
তারপর, ব্যবহারকারী টেক্সট-টু-স্পিচ শোনার সময়, আমরা প্রম্পট এবং আমাদের র্যাপার ক্লাস generateTypedContentRequest-এ পাস করি এবং একটি পরিষ্কার, ট্যাগ-মুক্ত শব্দভাণ্ডার কার্ড এক্সট্র্যাক্ট করি, অথবা কোনও শব্দ শেখানো না হলে নাল এক্সট্র্যাক্ট করি।
ঠিক আছে।
আমরা অনেক কিছু কভার করেছি।
এই মুহূর্তে এটি ভাবা খুবই স্বাভাবিক যে, "দারুণ, স্টেজে দেখতে সুন্দর লাগে এমন আরেকটি জিনিস যা আমি কখনও ছুঁয়েও দেখব না।"
এটি নয়!
XR Language Teacher, Google AI Edge Gallery অ্যাপে অন্যান্য অন-ডিভাইস AI অভিজ্ঞতার সাথে শিপ করা হয়।
আপনি আজই এটি ডাউনলোড করে নিজের ফোনে ইনস্টল করতে পারেন এবং কোনও কিছু অন্য ভাষায় কীভাবে বলতে হয় তা জানতে চাইলে, সেটির দিকে ফোন পয়েন্ট করতে পারেন।
এবং এটি কীভাবে তৈরি করা হয়েছে তা জানতে চাইলে, GitHub-এ এই সম্পূর্ণ ইমপ্লিমেন্টেশন ওপেন সোর্স হিসেবে উপলভ্য।
আপনার সব ব্যবহারকারীর কাছে অডিও বা ডিসপ্লে গ্লাস বা কাজ করে এমন ফ্ল্যাগশিপ Android ডিভাইস না থাকলেও চিন্তা করবেন না।
আমাদের কোড বেস থেকে আপনি এই একই অভিজ্ঞতা কীভাবে শুধুমাত্র মোবাইলের জন্য মানানসই করে তোলা যায় তার উদাহরণও দেখতে পাবেন।
এছাড়াও, আপনি যদি অন্যান্য প্ল্যাটফর্মেও পৌঁছাতে চান, তাহলে Google AI Edge Gallery অ্যাপ কোড আপনাকে দেখায় যে কীভাবে Google LiteRT রানটাইম এবং MediaPipe Tasks API ব্যবহার করে এই ফিচারটি তৈরি করা যায়।
অথবা আপনি যদি অন্য ওপেন সোর্স অন-ডিভাইস অডিও বা ল্যাঙ্গুয়েজ মডেল আনতে চান, তাহলে কোড থেকে কীভাবে তা করবেন তাও দেখতে পারেন।
তাই আপনার প্রোটোটাইপিং আরও সহজ করতে আমরা XR ল্যাঙ্গুয়েজ টিচার ওপেন সোর্স করেছি।
এই লাইভ, অন-ডিভাইস, AI-পরিচালিত অভিজ্ঞতাকে অন্য কিছুতে পরিণত করতে আমাদের GitHub রেপোতে আপনার পছন্দের এজেন্টকে নির্দেশ করুন।
এটি মিউজিয়াম গাইড, আপনার কাউন্টার দেখছে এমন রান্নার অ্যাসিস্ট্যান্ট, মেরামতের ওয়াকথ্রু, অ্যাক্সেসিবিলিটি ন্যারেটর, ফিল্ড ইন্সপেকশন টুল হতে পারে।
মডেল, রানটাইম, ওপেন-সোর্স ইমপ্লিমেন্টেশন এবং টুলিং এখন উপলভ্য, যাতে আপনি এই শরতে আসন্ন ইন্টেলিজেন্ট আইওয়্যার লঞ্চের জন্য প্রস্তুত হতে পারেন।
আপনি এটি দিয়ে কী তৈরি করেন তা দেখার জন্য আমরা উৎসুক হয়ে অপেক্ষা করছি।
উপসংহারে, আজ আমরা অন-ডিভাইসে নির্বিঘ্নে চলা AI মডেলের মাধ্যমে কী কী করা সম্ভব তার শক্তিশালী উদাহরণ দেখিয়েছি।
আর শুরু করার জন্য এখানে কয়েকটি উপায় দেওয়া হল।
প্রথমে, স্ক্রিনে রিসোর্সগুলি নিন।
ডেমো ব্যবহার করে দেখতে এবং GitHub-এ রেফারেন্স কোড চেক করতে AI Edge Gallery অ্যাপ ডাউনলোড করুন।
Android XR SDK, ML Kit API এবং Google AI Edge অফার অ্যাক্সেস করতে QR কোড স্ক্যান করুন।
দ্বিতীয়ত, আজই ফোনে এক্সপেরিমেন্ট করা শুরু করুন।
আমরা এমন অনেক বিষয় কভার করেছি যা মোবাইল-ফার্স্ট অভিজ্ঞতার মাধ্যমে আগে থেকেই তৈরি করা যেতে পারে।
এবং তৃতীয়ত, আপনি আগে থেকেই যে মোবাইল অ্যাপ শিপ করছেন সেটি দেখুন এবং নিজেকে জিজ্ঞাসা করুন, কোন মূল ফিচার হ্যান্ডস-ফ্রি হিসেবে আরও ভাল কাজ করবে?
আপনার আগে থেকে থাকা APK-তে প্রোজেক্টেড অ্যাক্টিভিটি যোগ করলেই সেই অভিজ্ঞতা স্মার্টগ্লাসেও পাওয়া যাবে।
আজ আমাদের সাথে যোগ দেওয়ার জন্য ধন্যবাদ।
Android এবং ইন্টেলিজেন্ট আইওয়্যারের জন্য আপনি কী ধরনের অবিশ্বাস্য অডিও-ফার্স্ট অভিজ্ঞতা তৈরি করেন তা দেখার জন্য আমরা অধীর আগ্রহে অপেক্ষা করছি।
ধন্যবাদ।
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": "けん玉をください。", ...}