Android XR

إنشاء تجارب صوتية للنظارات الذكية باستخدام Gemini Nano 4

سماعات

لمحة

لمحة عن المحاضرة

تعرَّف على كيفية إنشاء تجارب صوتية فعّالة بدون لمس الجهاز، وذلك باستخدام الذكاء الاصطناعي على الجهاز فقط. سنغطي كل ما تحتاجه: إعداد نشاط النظارات، والتقاط خلاصات الميكروفون والكاميرا، واستخدام الصوت مع إشارات صوتية أخرى كواجهة مستخدم نهائية. من خلال عرض واقعي ومشوّق لتطبيق XR Language Teacher المفتوح المصدر والمضمّن في تطبيق Google AI Edge Gallery، ستتعرّف على كيفية تنسيق Gemini Nano 4 (المستنِد إلى Gemma 4 والمحسَّن لأجهزة Android) وحزمة تعلّم الآلة، والاستفادة من وقت تشغيل LiteRT لإنشاء تجارب XR سريعة تسمع وترى وتتحدّث بشكل طبيعي.

ملخّص

  • تتيح مكتبة Jetpack Projected (الإصدار الأولي) ربط هواتف Android بالنظارات الذكية. تؤدي إضافة نشاط معروض إلى تطبيقك الحالي إلى عرض المحتوى على النظارات المزوّدة بشاشة أو النظارات الصوتية فقط.
  • توفّر "حزمة تعلّم الآلة" إصدارات أولية من مكتبات الكلام على الجهاز فقط للتفاعلات الصوتية على أجهزة محدّدة. وتوفّر ميزة "تحويل النص إلى كلام" بـ 8 أصوات في 21 منطقة، وميزة "التعرّف على الكلام" مع الوضع المتقدّم Gemini Nano على هاتف Pixel 10.
  • يعالج Gemini Nano 4 لقطات الكاميرا والأسئلة المحوَّلة إلى نص معًا في خطوة واحدة. وتتيح هذه الميزة استخدام التطبيق بلا إنترنت من خلال ربط نماذج الكلام على الجهاز فقط محليًا على الهواتف الرائدة المتوافقة.
  • يمنح التصميم الثنائي الأولوية لتقليل وقت استجابة الصوت في الوقت الفعلي على النظارات الذكية. في المرحلة الأولى، يتم بث النص المنطوق باستخدام علامات XML مضمّنة للتبديل بين اللغات في منتصف الجملة، بينما في المرحلة الثانية، يتم استخراج البيانات المنظَّمة في الخلفية.
  • يتم شحن تطبيق XR Language Teacher المفتوح المصدر في تطبيق Google AI Edge Gallery، ويمكن للمطوّرين تعديل الرمز البرمجي الخاص به لحالات استخدام أخرى للمساعد المتعدد الوسائط.

الموارد

Gemini Notebook

المواضيع

  • intelligent-eyewear
  • android-xr
  • gemini-nano
  • on-device-ai
  • ml-kit
  • speech-recognition
  • تحويل النص إلى كلام

النص

تنزيل النص

مقدمة

  • مرحبًا بالجميع.

  • تسرّني رؤيتكم اليوم.

  • شكرًا على حضورك.

  • اسمي "آيمي".

  • ستنضم إليّ اليوم زميلتي "أليس"، وسنتحدث عن كيفية إنشاء تجارب صوتية مذهلة للنظارات الذكية باستخدام Gemini Nano 4.

  • لِنبدأ الآن.

  • لذا، عندما نتحدث عن النظارات الذكية، فإننا نشير إلى نظارات محمولة وخفيفة الوزن مخصّصة للارتداء طوال اليوم.

  • ستشمل هذه الفئة كلاً من النظارات التي تعرض المحتوى والنظارات الصوتية فقط، ومع هذه الأجهزة، ستظهر لك بعض الأجهزة المتنوعة هنا.

  • أول ما ستلاحظه هو أنّها تحتوي على لوحة لمس يمكن استخدامها لإدخال البيانات، مثل النقر والتمرير السريع.

  • وستتضمّن أيضًا مكبّرات صوت وكاميرا وميكروفون.

  • ويمكن أن تتضمّن أيضًا شاشة أو لا، أو ربما مزيجًا من ذلك.

  • سنتحدّث اليوم عن المحتوى الصوتي وكيفية إنشاء تجارب تفاعلية وذكية أثناء التنقّل.

  • ومن الأمور المثيرة للاهتمام في المحتوى الصوتي فقط هو أنّ الصوت يصبح واجهة المستخدم.

  • ويُعدّ الذكاء الاصطناعي على الجهاز فقط رائعًا للمطوّرين الذين يبحثون عن تجارب بلا إنترنت وأثناء التنقل.

إنشاء تطبيقات لنظام التشغيل Android XR

  • ولدينا خبر سار.

  • لذا، إذا كنت تطوّر تطبيقات لنظام Android، فأنت تطوّرها أيضًا لنظام Android XR.

  • تم تصميم تطبيقات Android الحالية لتتوافق بشكل جيد مع النظارات الصوتية، ما يتيح تقديم الإشعارات الشفهية والمحتوى الصوتي الآخر عبر البلوتوث.

  • وإذا أردت الانتقال إلى مستوى أعلى وتمييز تطبيقك في مجال الواقع الممتد، فهناك العديد من الطرق لذلك.

  • في هذه الجلسة، سنتحدّث عن النماذج التي تعمل على الجهاز فقط، ولكنّها ليست قائمة شاملة بأي حال من الأحوال.

  • هناك أيضًا نماذج سحابية.

  • هناك ميزات مثل Gemini Live API.

  • هناك وكلاء نظام يمكنك التكامل معهم، مثل Gemini، ويمكن أن يساعدك كل ذلك في إنشاء التجربة الأفضل لك.

  • فكيف يمكننا إنشاء هذه التجربة التي تسمع وترى وتتحدّث بشكل طبيعي؟

  • اليوم، سنرشدك إلى كيفية إنشاء هذه التجارب الفعّالة التي لا تتطلّب لمس الجهاز وتستند إلى الذكاء الاصطناعي على الجهاز.

  • سأشرح لك الأساسيات: كيفية إنشاء تطبيق للنظارات، وكيفية إعداد نشاطك، والحصول على الأذونات التي تحتاجها، ثم كيفية استخدام حزمة تعلّم الآلة لواجهة المستخدم الحوارية.

  • بعد ذلك، سأترك الكلمة لـ "أليس"، وستشرح لكم بعض الأمثلة الواقعية التي تستخدم Gemini Nano 4.

  • لنبدأ الآن بالاطّلاع على الرمز البرمجي.

  • لذلك، مع النظارات، الهاتف هو الذي يقوم بالمهمة الصعبة، وسيتم عرض محتوى النظارات من الهاتف إلى النظارات.

  • هناك أمران يجب تنفيذهما لربط هذه الأجهزة عند إعدادها لأول مرة.

  • ما ستحتاجه أولاً هو مكتبة Jetpack Projected.

  • عليك إضافة هذه التبعية التي ستحتوي على واجهات برمجة التطبيقات لتشغيل هذه التجربة المتوقّعة.

    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>
  • يتمثل الاختلاف الرئيسي هنا في أنك تحتاج إلى استخدام XR_PROJECTED كـ requiredDisplayCategory.

  • وذلك لكي يعرف النظام أنّ هذا النشاط مخصّص للنظارات.

  • بعد ذلك، عليك استخدام فئة النية هذه هنا، وهي XR_PROJECTED_LAUNCHER.

  • يتيح ذلك لـ Gemini العثور على تطبيقك ليتمكّن المستخدمون من تشغيله بدون لمس الجهاز أثناء التنقل.

  • حسنًا.

  • بعد إعداد هذا النشاط، يمكنك استخدام مكتبة Projected لتشغيل تجربة التطبيق هذه.

  • تحتوي هذه المكتبة على جميع الأدوات التي تحتاجها لإنشاء تجربة النظارات الكاملة.

  • لنتعرّف على بعض واجهات برمجة التطبيقات الرئيسية التي ستستخدمها هنا.

  • أول طريقة أريد الإشارة إليها هي طريقة 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)
  • بهذه الطريقة، يمكنك مراقبة اتصال النظارة باستمرار.

  • يمكنك استخدام هذه الميزة مع دورة حياة الجهاز لمعرفة متى تكون النظارات متاحة فعليًا، وليس فقط عندما تكون مقترنة أو قيد التشغيل، بل أيضًا عندما يتم ارتداؤها وتكون جاهزة للاستخدام.

  • وهذا ما، نعم.

  • لذا، سيُعلمك هذا بالضبط عندما تكون مستعدًا لإطلاق هذا النشاط.

  • للقيام بذلك، عندما تكون مستعدًا لبدء النشاط، هناك بعض الخيارات المحددة التي ستحتاج إلى تعيينها عند بدء النشاط.

  • وستستخدمون createProjectedActivityOptions على ProjectedContext.

  • ومرة أخرى، ستستخدم ذلك مع السياق الخاص بالهدف هناك.

  • هناك أداة أخرى ستحتاج إلى استخدامها، وهي 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 CLI في استوديو Android لإعداد سير عمل الأذونات، ويمكنك الوصول إلى هذه المهارة، ثم جميع مهارات Android الأخرى أيضًا.

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

تصميم تجارب تركّز على الصوت

  • بعد أن أصبحنا متصلين بالإنترنت، وعلى دراية بإمكانات الأجهزة، ولدينا الأذونات المناسبة، لنتحدث عن الأدوات التي يجب أن تستخدمها لإنشاء تجربة الصوت هذه.

  • لذلك، عند تصميم تجربة تركّز على الصوت، نريد التأكّد من أنّنا نقدّم تجربة عالية الجودة للمستخدمين.

  • ويمكن أن يشمل ذلك بعض التفاعلات الرئيسية.

  • أولاً، استخدام الصوت كمخرجات، أي السماح لصوتك بالتحدّث بشكل طبيعي مع المستخدم.

  • بعد ذلك، سيتم استخدام الرموز الصوتية لإرسال إشارات صوتية محددة إلى المستخدم.

  • ثم التعامل أيضًا مع الإدخال من المستخدم، سواء كان ذلك من خلال لوحة اللمس باستخدام النقرات والتمريرات السريعة، أو أيضًا إذا كنت تريد السماح للمستخدم بالتحدث إلى تطبيقك.

  • لذا، فإنّ أول ما سنتحدث عنه هو استخدام الصوت كمخرجات.

  • لذلك، أريد أن أقدّم لكم مكتبة "تحويل النص إلى كلام" الجديدة المستندة إلى الذكاء الاصطناعي التوليدي في "حزمة تعلّم الآلة".

  • تتوفّر هذه الميزة الآن في إصدار أولي على أجهزة Pixel وSamsung محدّدة.

  • نحن متحمّسون جدًا لهذه الميزة، لا سيّما في ما يتعلّق بالتجارب التي تركّز على الصوت.

  • إذا سبق لك استخدام ميزة "تحويل النص إلى كلام"، من المحتمل أنّك تعرف أنّ إحدى المشاكل التي قد تواجهها هي أنّ صوت النظام لا يبدو دائمًا طبيعيًا كما تريد.

  • وقد يؤدي الاتصال بنماذج السحابة الإلكترونية لأصوات أخرى إلى حدوث تأخير محتمل أو تكاليف إضافية.

  • لذلك، تمنحك "حزمة تعلّم الآلة" كلامًا يبدو طبيعيًا أكثر مقارنةً بميزة "تحويل النص إلى كلام" في النظام.

  • وتوفّر "حزمة تعلّم الآلة" ثمانية أصوات فريدة يمكنك الاختيار من بينها، وتتميّز بخصائص مختلفة لتناسب تطبيقك.

  • وتوفّر "حزمة تعلّم الآلة" أيضًا دعمًا قويًا للّغات من خلال 21 لغة مختلفة يمكنك الاختيار من بينها.

  • بالإضافة إلى ذلك، من المزايا الرائعة هنا أنّه بعد عملية التنزيل الأولية، سيعمل هذا النموذج بالكامل على الجهاز.

  • سيتم تنزيل هذا النموذج على النظام ومشاركته على مستوى التطبيقات، لذا لن يؤدي ذلك إلى زيادة حجم حزمة APK.

  • وهي ليست متاحة بلا إنترنت فحسب، وهو أمر مهم بشكل خاص لمستخدمي النظارات أثناء التنقل، فقد لا يكون لديهم شبكة Wi-Fi أو قد يكونون في مناطق تغطية ضعيفة، ولكن من المرجح أيضًا أن ترى أداءً أفضل، لأنك لست مضطرًا إلى إجراء مكالمة ذهابًا وإيابًا إلى الخادم.

  • حسنًا.

  • لنتعرّف على تفاصيل المكتبة وكيفية استخدامها في إنشاء التطبيقات.

  • أولاً، عليك إضافة التبعية.

    dependencies {
       // ML Kit GenAI TTS SDK
       implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
    }
  • لذا، كل ما تحتاج إليه هنا هو حزمة تطوير البرامج (SDK) الخاصة بميزة "تحويل النص إلى كلام" في حزمة تعلّم الآلة.

  • وبعد ذلك، يمكنك البدء في كتابة الرمز.

  • لذلك، تتضمّن مكتبة حزمة تعلّم الآلة للذكاء الاصطناعي التوليدي واجهات برمجة التطبيقات Speech Synthesizer.

    // 1. Initializing the SpeechSynthesizer client.
    val options = SpeechSynthesizerOptions.Builder(Locale.US).build()
    val client = SpeechSynthesis.getClient(options)
    
    // 2. Check and download the voice model.
    val voiceInfo = client.getSupportedVoices().firstOrNull()
    
    if (voiceInfo != null && client.checkStatus(voiceInfo) == FeatureStatus.DOWNLOADABLE) {
       client.download(voiceInfo).collect { status -> println("Download status: $status") }
    } else if (voiceInfo == null) {
       // Tip: Fallback to system TTS if ML Kit voice is unavailable
    }
  • يوفّر لك ذلك طريقة مباشرة لتنفيذ الأصوات التي تختارها.

  • والخطوة الأولى التي ستتخذها هنا هي تهيئة العميل باستخدام أداة تركيب الكلام مرة أخرى، مع ضبط اللغة التي تختارها.

  • بعد ذلك، عليك التحقّق من حالة التنزيل واختيار الصوت.

  • في هذا المثال، سنختار الصوت الأول، ولكن أريد أيضًا أن أشير إلى أنّنا نستخدم firstOrNull.

  • كما ذكرت، تتطلّب "حزمة تعلّم الآلة" هاتفًا معيّنًا يتوافق معها، مثل أجهزة Samsung أو Pixel.

  • لذا، إذا حصلت على قيمة فارغة، فقد يعني ذلك أن المستخدم لا يستخدم هاتفًا متوافقًا.

  • بعد ذلك، عليك التأكّد من توفّر خيار احتياطي، سواء كان ذلك بالعودة إلى ميزة "تحويل النص إلى كلام" في النظام أو تشغيل رسالة مسجّلة أو ما شابه.

  • حسنًا، بعد ذلك، ستستخدمون Speech Synthe- 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.

  • يسرّني لقاؤكم جميعًا اليوم.

  • نأمل أن يكون عرضنا التقديمي حول التجارب الصوتية قد نال إعجابكم.

  • أفضل بكثير، أليس كذلك؟

  • حسنًا

  • مكتبة "تحويل النص إلى كلام" في "حزمة تعلّم الآلة" هي أداة فعّالة للغاية، ولكن لا يقتصر استخدامها على هذه الأصوات، بل يمكنك أيضًا تشغيل رموز صوتية باستخدام هذه المكتبة.

    // 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، ويمكنك استخدامها وتنزيلها مباشرةً من هناك واستخدامها في إجراءات مثل فتح التطبيق وإغلاقه وإشعارات الأخطاء وكتم الصوت أو إعادة الصوت.

  • ولكن لديك أيضًا خيار إنشاء رموز أذن مخصصة خاصة بك أيضًا.

  • لذلك، مع الرموز الصوتية، هناك بعض أفضل الممارسات التي نوصي باتباعها سواء كنت تستخدم الرموز التي تم تنزيلها من مركز التصميم، أو كنت تقوم بإنشاء الرموز الخاصة بك.

  • لذلك عليك أن تكون مدروسًا في طريقة استخدامك للرموز الصوتية، وكيفية استخدامها.

  • لذا، هناك بعض الأمور التي يجب وضعها في الاعتبار، مثل المبتدئين، حاول ألا تفرط في استخدام الرموز الصوتية.

  • لا تكرّروا الأصوات نفسها مرارًا وتكرارًا، فقد يؤدي ذلك إلى ما يُسمّى "إرهاق الأذن".

  • وتذكَّر أنّ الرموز الصوتية يجب أن تكون رائعة وبديهية، واستخدِم أيضًا نغمات وإيقاعات مألوفة عند تنفيذها.

  • وبعد ذلك، حاول تجنب الرموز السمعية المعقدة للغاية التي تحتاج إلى شرح، فقد تكون هذه الرموز أكثر ملاءمة كإشعار صوتي.

  • تذكَّروا دائمًا أنّ الصوت الذي قد يبدو خافتًا في الاستوديو أو على نظامكم أو على مكبرات الصوت في الكمبيوتر، قد يصبح مزعجًا بعد تكرار تشغيله للمستخدمين.

  • لذا ابذل قصارى جهدك لعدم إثقال كاهل المستخدمين بأصوات قد تربكهم أو تؤدي بهم إلى تجاهل التنبيهات المهمة.

  • حسنًا.

  • لذلك لدينا الآن القدرة على تقديم إشارات صوتية وملاحظات سواء كان ذلك من خلال الكلام الطبيعي أو باستخدام الإشارات الصوتية.

  • ولكن ماذا عن الإدخال؟

  • لذلك، هناك بالطبع لوحة لمس أو أزرار مخصّصة على الجهاز أيضًا، والتي يمكنك استخدامها لإنشاء تفاعلات النقر والتمرير السريع.

  • قد ترغب أيضًا في السماح بواجهة المستخدم الحوارية هذه، ولهذا يمكنك استخدام واجهة برمجة التطبيقات GenAI Speech Recognition API من "حزمة تعلّم الآلة".

  • تتوفّر هذه الميزة حاليًا في إصدار أولي وعلى أجهزة محدّدة، ولكنّها تعمل بشكل أساسي في وضعَين مختلفَين.

  • هناك الوضع الأساسي الذي يستخدم ميزة التعرّف على الكلام التقليدية على الجهاز، وهو متاح على أجهزة Android التي تعمل بالمستوى 31 من واجهة برمجة التطبيقات والإصدارات الأحدث.

  • وهناك أيضًا الوضع المتقدّم الذي يستخدم Gemini Nano، وهو متاح على أجهزة Pixel 10 والإصدارات الأحدث، ويوفّر جودة أفضل وتغطية لغوية أوسع أيضًا.

  • لذلك، يتّبع SpeechRecognizer نمط رمز مشابهًا جدًا لـ SpeechSynthesizer.

  • يمكنك إنشاء العميل والتحقّق من توفّر النموذج وتنزيله إذا لزم الأمر وإنشاء الطلب وإصدار الموارد.

  • لذا، لنركّز هنا على الاختلافات الرئيسية في إنشاء الطلب وتسجيل الصوت من الميكروفون ثم معالجة النتيجة.

  • مرة أخرى، ستحتاج إلى إضافة التبعيات اللازمة.

    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() }
    ...
  • بعد ذلك، حدِّد خيارات التعرّف على الكلام، واضبط اللغة و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، لديك أربعة ردود محتملة: جزئي ونهائي وخطأ ومكتمل.

  • ستنظر الجزئية في البث في الوقت الفعلي، لذلك يتم استخدامها للتفكير مثل الكلمات الرئيسية أو الكلمات الرئيسية.

  • تأخذ الحالة "نهائي" البث الصوتي النهائي بعد تحليله بالكامل وتحديثه، ثم تكتمل بعد اكتمال الكلام، ثم تجمع الحالة "خطأ" أي أخطاء قد تحدث.

  • وإليك كيف نجمع كل ذلك معًا.

    // 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" أنّ المستخدم قد استخدم إحدى هذه العبارات، سيتوقف عن حفظ المحتوى ثم يوقف ميزة التعرّف على الكلام، ويشغّل إشارة صوتية مخصّصة، ويستخدم ميزة "تحويل النص إلى كلام" المستندة إلى الذكاء الاصطناعي التوليدي في "حزمة تعلّم الآلة" لإعلام المستخدم بأنّه تم حفظ المحتوى.

دراسة حالة "معلّم اللغات بتقنية الواقع الممتد"

  • حسنًا

  • لقد تناولنا الكثير من المعلومات.

  • قد يبدو الأمر مجردًا بعض الشيء.

  • إذا كنت مثلي، فأنت تريد أن ترى بعض الأمثلة الواقعية.

  • وأنا و"أليس" نريد أن نوضّح لك ذلك بالتفصيل.

  • ستشرح لك كيفية استخدام XR Language Teacher في تطبيق Google AI Edge Gallery.

  • صمّم فريق "أليس" هذه الميزة، وستشرح لكم طريقة عملها.

  • والآن، حان دورك.

  • شكرًا، إيمي.

  • أجل

  • لذا، أود أن أبدأ بشيء يعرفه الكثير منكم.

  • أنا أتعلّم اللغة اليابانية، وقد قضيت ساعات في تطبيقات تعلُّم اللغات والبطاقات التعليمية وسلاسل الإنجازات وكل ما يتعلق بذلك.

  • بعد ذلك، وصلتُ إلى طوكيو ودخلتُ إلى سوق، وأدركتُ أنّني لا أعرف أيًّا من الكلمات التي أراها أمامي تقريبًا.

  • هناك لعبة خشبية من نوع "الكأس والكرة" على كشك في السوق أتعرّف عليها من خلال النظر إليها، ولكنني لا أعرف كيف أتحدث باللغة اليابانية.

  • هناك زينة في نافذة متجر أريد شراءها ولا يمكنني تسميتها.

  • هذه هي اللحظة التي أكون فيها أكثر حماسًا لتعلُّم كلمة جديدة.

  • وليس على أريكتي وأنا أنظر إلى تطبيق لغة.

  • في تلك اللحظة، أبحث عن الشيء الذي أريد تعلّمه.

  • إليك ما أريده.

  • أريد ارتداء نظاراتي الذكية طوال اليوم والتجوّل بها.

  • وإذا لفت انتباهي أي شيء، يمكنني طرح سؤال عنه بصوت عالٍ.

  • وعندما أعود إلى الفندق في الليل، أجد كل ما سألت عنه في انتظاري على هاتفي كبطاقات تعليمية للمفردات.

  • وهذا ما أنشأناه.

  • لنشاهد مقطعًا قصيرًا.

  • لذلك، نطلق على هذا التطبيق اسم "معلّم اللغات بتقنية الواقع الممتد".

  • مرحبًا.

  • ما الذي يهمّك معرفته اليوم؟

  • ويمكنك تخصيص تجربة التعلّم في إعدادات المعلّم.

  • على سبيل المثال، يمكنك اختيار اللغة التي تريد تعلّمها واختيار شخصية المعلّم المحدّدة التي تناسبك.

  • يا للهول

  • ويمكنك التبديل بين وضع النظارة ووضع الهاتف.

  • كيف أقول "هذا العنب" باللغة الإسبانية؟

  • بالنظر إلى هذا، يُطلق على هذا العنب اللذيذ اسم uvas.

  • In Spanish, you can say, "Estas uvas son deliciosas," to describe them.

  • أريد أن أقول العنب الأخضر، على وجه التحديد.

  • دعني ألقي نظرة عليها.

  • لتحديد العنب الأخضر، يمكنك قول "Uvas verdes".

  • Me gustan las uvas verdes," is a great way to tell someone you like them.

  • كيف أقول هذا بالفرنسية، وكيف يمكنني طلب ثلاثة منه؟

  • لنتحقق من ذلك.

  • تُسمّى هذه المعجنات اللذيذة "مادلين".

  • In French, to ask for three, you can say, "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 يعمل بشكل كامل بلا إنترنت وعلى الجهاز.

ربط النماذج على الجهاز فقط

  • فكيف تم إنشاء هذا؟

  • من الناحية الهندسية، أوضحت لك "إيمي" كيفية استخدام إطار كاميرا معروض من النظارات على الهاتف.

  • لقد شرحت لك كيفية استخدام "حزمة تعلّم الآلة" وميزة "التعرّف على الكلام" لتحويل النص إلى كلام، بالإضافة إلى ميزة "تحويل النص إلى كلام" في "حزمة تعلّم الآلة" لتحويل النص إلى كلام.

  • ما زلنا بحاجة إلى عقل في الوسط، شيء يمكنه إلقاء نظرة على إطار الكاميرا والسؤال معًا، ومعرفة ما يسأل عنه المستخدم بالفعل.

  • هذا العقل هو Gemini Nano 4، المستنِد إلى Gemma 4، وهو نموذج Google المفتوح المصدر الذي يعمل محليًا على الهاتف.

  • لذلك، يمكنك الحصول على أداء محسّن على أجهزة Android الرائدة المتوافقة باستخدام واجهات برمجة تطبيقات حزمة تعلّم الآلة مباشرةً.

  • سيتولّى نظام Android تسليم النموذج نيابةً عنك، لذا لن تحتاج إلى تجميع أي شيء في تطبيقك.

  • لجمع كل هذه العناصر معًا، يتم إدخال الميكروفون في ميزة التعرّف على الكلام، ويتم إدخال إطار الكاميرا والنص معًا في Gemini Nano 4، ويكتب Gemini Nano 4 في ميزة تركيب الكلام ثم يخرج من مكبّر الصوت.

  • ثلاثة نماذج متسلسلة تعمل جميعها محليًا على هاتفك.

  • في ما يلي بعض الأسباب التي تجعل Gemini Nano 4 مناسبًا بشكل خاص لهذه المشكلة.

  • أولاً، يرى ويقرأ في تمريرة واحدة.

  • من خلال "حزمة تعلّم الآلة"، يعالج Gemini Nano 4 النصوص والصور بشكلٍ أصلي، لذا لن تحتاج إلى استخدام أداة رصد، ثم أداة تصنيف، ثم أداة ترجمة.

  • وهو نموذج واحد يحلّل إطار الكاميرا والسؤال معًا.

  • والثاني هو دعمها الواسع للغات.

  • تم تدريب Gemini Nano 4 على أكثر من 140 لغة.

  • بالإضافة إلى ذلك، لا تكفي الترجمة وحدها لتعليم اللغات.

  • أريد تعبيرات اصطلاحية، وأريد أساليب رسمية مقابل أساليب غير رسمية، وأريد أيضًا أمثلة على جمل قد يقولها شخص حقيقي.

  • وأخيرًا، يمكن توجيهه بما يكفي ليكون له شخصية.

  • قد يكون التدرّب على النطق بصوت عالٍ أمرًا مخيفًا.

  • يتّبع هذا النموذج طلب النظام بشكل جيد، ما يتيح للمستخدم اختيار شخصية المعلّم الخاصة به.

  • وربما يمكن أن يجعل المريض والمشجع الناس أكثر استعدادًا للمحاولة.

  • وهناك شيء آخر أودّ تسليط الضوء عليه بشأن التصميم.

  • أثناء إجراء المحادثة في وضع النظارات، يكتب التطبيق بهدوء بطاقات المفردات في الخلفية.

  • الكلمة والترجمة والجملة النموذجية والصورة من لحظة طرح سؤالك.

  • ولن يحتاج المستخدم إلى النقر يدويًا على أي شيء.

  • إنّهما يتحدّثان أثناء المشي.

  • وفي وقت لاحق من ذلك اليوم، يجدون كل ما يحتاجون إليه على هواتفهم، وهذا بالضبط ما كنت أريده عندما كنت في طوكيو.

  • بالتالي، لا يعني التركيز على الصوت أولاً أن يكون المحتوى صوتيًا فقط.

  • النظارات هي السطح المناسب للفضول، وهاتفك هو السطح المناسب للتفكير بعد ذلك.

  • التقِط صورًا رائعة بدون لمس الجهاز.

  • والآن، لنلقِ نظرة على بعض النصائح العملية لإنشاء تجارب في الوقت الفعلي باستخدام Gemini Nano 4 وحزمة تعلّم الآلة، والتي تعلّمناها من إنشاء هذا العرض التوضيحي.

إعطاء الأولوية لوقت الاستجابة في الوقت الفعلي من خلال تصميم ثنائي المسار

  • النصيحة الأولى: إعطاء الأولوية لوقت الاستجابة في الوقت الفعلي من خلال تصميم ثنائي المسار

  • على النظارات الصوتية، عندما تسأل "ما هذا؟"

  • كل لحظة صمت تكون ملحوظة.

  • في مثال "معلّم اللغات بالواقع الممتد"، نحتاج إلى شيئين من النموذج في كل دور من تفاعل المستخدم: إجابة شفهية فورية، وبطاقة مفردات منظَّمة لحفظها في التطبيق.

  • إذا طلبنا من النموذج تنفيذ مهمتَين معًا في عملية واحدة، سيؤدي كل رمز مميّز يتم إنفاقه على إنشاء بيانات الخلفية إلى تأخير الجملة الأولى المنطوقة.

  • لذلك، نفصل بين ما يحتاجه المستخدم الآن وما يحتاجه التطبيق بعد بضع ثوانٍ.

  • لذلك قمنا بتقسيم كل دور إلى تمريرتين.

  • في المرحلة الأولى، نرسل إطار الكاميرا والسؤال للحصول على الردّ الصوتي فقط، ما يؤدي إلى تفعيل ميزة "تحويل النص إلى كلام" فور اكتمال الجملة الأولى.

  • وبما أنّ وحدة TPU أو NPU تنتهي من إنشاء هذا الردّ القصير قبل وقت طويل من انتهاء ميزة "تحويل النص إلى كلام" من قراءته بصوت عالٍ، نحصل على نافذة من عدّة ثوانٍ يستمر فيها المستخدم في الاستماع، وتكون الشريحة في وضع الخمول.

  • وبما أنّ المرحلة الأولى قد حدّدت العنصر في ردّها النصي، لا تحتاج المرحلة الثانية إلى إدخال الصورة.

  • نرسل ردّ Pass 1 إلى الشريحة غير النشطة لاستخراج البطاقة المنظَّمة وحفظها قبل أن ينتهي المعلّم من الحديث.

  • والآن، لنلقِ نظرة عن كثب على المرحلة الأولى، ما ينقلنا إلى النصيحة الثانية.

  • استخدِم علامات XML خفيفة عند البث.

  • بما أنّ المسار الأول يبث مباشرةً إلى ميزة "تحويل النص إلى كلام"، من الأفضل عدم استخدام عنصر JSON ثقيل لتأخير أول كلمة منطوقة.

  • بدلاً من ذلك، تحتاج إلى نص عادي يمكن قراءته على الفور باستخدام علامات مضمّنة خفيفة الوزن كلما احتاج التطبيق إلى اتخاذ الإجراءات في منتصف البث.

  • لذا، دعوني أريكم المشكلة التي أدّت إلى ذلك.

  • هل تتذكّر لعبة الكأس والكرة الخشبية التي ذكرتُها في كشك السوق؟

  • باليابانية، تُسمّى "كينداما".

  • في البداية، تعرّف النموذج على اللعبة من إطار الكاميرا وقدّم الإجابة الصحيحة: "هذه لعبة كينداما، وهي لعبة خشبية".

  • لذا، أدخلنا هذه الجملة الكاملة في جلسة تحويل النص إلى كلام باللغة الإنجليزية، وإليكم النتيجة.

  • هذه لعبة "كين-مي"، وهي لعبة خشبية.

  • هذا لا يبدو صحيحًا تمامًا.

  • بدلاً من ذلك، يجب أن تكون هذه الجملة…

  • هذه لعبة الكينداما، وهي لعبة خشبية.

  • لماذا حدث ذلك؟

  • تُكتب كلمة "كينداما" باستخدام حروف الهيراغانا "ken" وحرف الكانجي "dama".

  • ويتعامل محرّك تحويل النص إلى كلام باللغة الإنجليزية مع الهيراغانا الصوتية مثل "ken" بشكل جيد، ولكنه ينهار تمامًا عندما يصل إلى جزء الكانجي ويحول داما إلى شيء يبدو مثلي.

  • ويحدث ذلك في الواقع في العديد من اللغات المختلفة بطرق مختلفة إذا لم يتم تبديل اللغة.

  • على سبيل المثال، قد تسمع باللغة الإسبانية…

  • سوه-بوه-لا…

  • عندما تتوقع فعليًا…

  • Cebolla.

  • وبالمثل، قد تسمع باللغة الفرنسية…

  • PAIN oh chock-uh-lat…

  • بينما تتوقع في الواقع…

  • خبز الشوكولاتة

  • نعم، عادةً ما يتم تصميم جلسة تحويل النص إلى كلام للغة واحدة في كل مرة، ولكن يحتاج معلّم اللغة إلى التبديل بين اللغات في كل جملة.

  • لذلك، في الخطوة الأولى، نطلب من النموذج تضمين كل كلمة أو عبارة باللغة الهدف داخل علامة 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 إلى generateContentStream في "حزمة تعلّم الآلة"، ونقسّم النصوص المستهدَفة إلى "تحويل النص إلى كلام" مع وصول كل جملة.

  • وهذا يقودنا إلى الخطوة الثانية والنصيحة الثالثة.

  • على الرغم من أنّ استخدام علامات XML البسيطة يُعدّ رائعًا للبث الصوتي المباشر، ننصحك باستخدام النتائج المنظَّمة المصمَّمة لعرض عنصر واضح ومكتوب عند حفظ البيانات في تطبيقك.

  • بما أنّ تطبيق "معلّم اللغات بتقنية الواقع الممتد" يعتمد على المحادثات، يمكن للمستخدمين أيضًا طرح أسئلة متابعة لا يتم فيها تعليم كلمة جديدة.

  • وعندما يتم تدريس كلمة، تحتاج قاعدة البيانات إلى حقول قاموس نظيفة لإنشاء بطاقات المفردات.

  • لذلك، بدلاً من كتابة تعبيرات عادية غير مرنة لتحليل نص المرحلة الأولى، نسمح للمرحلة الثانية باستخراج كائن يمنع أخطاء الكتابة في الخلفية.

  • ولفرض المخطط في الخطوة الثانية، نستخدم واجهة برمجة التطبيقات Structured Output 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 إلى جانب تجارب الذكاء الاصطناعي الأخرى على الجهاز فقط.

  • يمكنك تنزيله اليوم وتثبيته على هاتفك وتوجيهه نحو أي شيء تريد معرفة كيفية قوله بلغة مختلفة اليوم.

  • وإذا أردت معرفة كيفية إنشائه، يمكنك الاطّلاع على عملية التنفيذ الكاملة المفتوحة المصدر على GitHub.

  • لا تقلق إذا لم يكن لدى جميع المستخدمين نظارات صوتية أو بشاشة عرض أو أجهزة Android رائدة متوافقة.

  • تعرض لك قاعدة الرموز البرمجية أيضًا أمثلة على كيفية تكييف هذه التجربة نفسها لتناسب تجربة الأجهزة الجوّالة فقط.

  • وإذا أردت الوصول إلى منصات أخرى أيضًا، يوضّح لك رمز تطبيق Google AI Edge Gallery أيضًا كيفية إنشاء هذه الميزة باستخدام وقت تشغيل Google LiteRT وواجهات برمجة التطبيقات MediaPipe Tasks.

  • وإذا أردت استخدام نماذج لغوية أو صوتية أخرى مفتوحة المصدر على الجهاز، يمكنك أيضًا الاطّلاع على كيفية إجراء ذلك من خلال الرمز البرمجي.

التكيّف مع حالات الاستخدام الأخرى باستخدام وكلاء الذكاء الاصطناعي

  • لذلك، جعلنا تطبيق XR Language Teacher مفتوح المصدر لتسهيل إنشاء النماذج الأولية.

  • يمكنك توجيه الوكيل الذي تختاره إلى مستودع GitHub لتحويل هذه التجربة المباشرة المستندة إلى الذكاء الاصطناعي على الجهاز فقط إلى تجربة أخرى.

  • يمكن أن يكون دليلاً في متحف، أو مساعدًا في الطهي ينظر إلى طاولة المطبخ، أو دليلاً تفصيليًا للإصلاح، أو راويًا لتسهيل الاستخدام، أو أداة فحص ميداني.

  • يتوفّر الآن النموذج ووقت التشغيل والتنفيذ المفتوح المصدر والأدوات، ما يتيح لك الاستعداد لإطلاق النظارات الذكية المرتقبة في الخريف المقبل.

  • نحن متحمّسون لرؤية أعمالك الإبداعية باستخدام هذه الميزة.

  • في الختام، عرضنا اليوم أمثلة قوية على ما يمكن تحقيقه من خلال نماذج الذكاء الاصطناعي التي تعمل بسلاسة على الجهاز فقط.

  • وإليك بعض الطرق للبدء.

  • أولاً، عليك الحصول على الموارد المعروضة على الشاشة.

  • نزِّل تطبيق AI Edge Gallery لتجربة العرض التوضيحي والاطّلاع على الرمز المرجعي على GitHub.

  • ويمكنك مسح رموز الاستجابة السريعة ضوئيًا للوصول إلى حزمة تطوير البرامج (SDK) لنظام Android XR وواجهات برمجة التطبيقات في حزمة تعلّم الآلة وعروض Google AI Edge.

  • ثانيًا، ابدأ التجربة على الهاتف اليوم.

  • هناك الكثير من الأمور التي تناولناها والتي يمكن إنشاؤها من خلال تجربة تعطي الأولوية للأجهزة الجوّالة.

  • ثالثًا، ألقِ نظرة على تطبيق الأجهزة الجوّالة الذي تطرحه حاليًا واسأل نفسك: ما هي الميزات الأساسية التي سيكون من الأفضل استخدامها بدون لمس الجهاز؟

  • لإتاحة هذه التجربة على النظارات أيضًا، ما عليك سوى إضافة نشاط معروض إلى حزمة APK الحالية.

  • شكرًا جزيلاً على انضمامكم إلينا اليوم.

  • نحن متحمّسون لرؤية التجارب الصوتية الرائعة التي ستبتكرها على Android والنظارات الذكية.

  • شكرًا لك.

الرمز

مقتطفات الرمز البرمجي

مقتطف Kotlin في الدقيقة 02:39 في فيديو "التطوير لنظام Android XR"
dependencies {
   implementation("androidx.xr.projected:projected:1.0.0-alpha13")
}
مقتطف Kotlin في الدقيقة 02:45 في فيديو "إنشاء تطبيقات لنظام التشغيل Android XR"
class ProjectedMainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           // Glasses content
       }
   }
}
مقتطف XML في الدقيقة 03:02 في فيديو "التطوير لنظام التشغيل Android XR"
<activity
   android:name=".ProjectedMainActivity"
   android:exported="false"
   android:requiredDisplayCategory="android.hardware.display.category.XR_PROJECTED"
   android:label="Example activity for audio glasses">
   <intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.XR_PROJECTED_LAUNCHER"/>
   </intent-filter>
</activity>
مقتطف Kotlin في الدقيقة 03:44 في فيديو "الإنشاء لنظام التشغيل Android XR"
// Continuously monitor whether the glasses are connected
val isGlassesConnected by ProjectedContext.isProjectedDeviceConnected(
   context,
   scope.coroutineContext
).collectAsStateWithLifecycle(initialValue = false)

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

// Check if the device is audio only or has a display
val projectedDeviceController = ProjectedDeviceController.create(this@ProjectedMainActivity)
isVisualUiSupported = projectedDeviceController.capabilities.contains(CAPABILITY_VISUAL_UI)
مقتطف Kotlin في الدقيقة 05:05 في فيديو "إنشاء تطبيقات لنظام التشغيل Android XR"
// In a projected activity
private fun requestProjectedPermissions() {
   lifecycleScope.launch {
       try {
           ProjectedActivityCompat.requestPermissions(
               this@PermissionsFromProjectedActivity,
               arrayOf(Manifest.permission.CAMERA),
               // CAMERA_PERMISSION_REQUEST_CODE is a developer defined constant
               requestCode = CAMERA_PERMISSION_REQUEST_CODE,
           )
       } catch (e: IllegalStateException) {
           // Thrown when the projected system service can't be bound.
           Log.e(TAG, "Request failed: projected service unavailable.", e)
       }
   }
}
مقتطف Shell في الدقيقة 05:46 في فيديو "التطوير لنظام التشغيل Android XR"
android skills add projected-hardware
android skills add --all
مقتطف Kotlin في الدقيقة 08:14 في فيديو "تصميم تجارب تركّز على الصوت"
dependencies {
   // ML Kit GenAI TTS SDK
   implementation("com.google.mlkit:genai-text-to-speech:1.0.0-alpha1")
}
مقتطف Kotlin في الدقيقة 08:26 في فيديو "تصميم تجارب تركّز على الصوت"
// 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
}
مقتطف Kotlin في الدقيقة 09:26 في فيديو "تصميم تجارب تركّز على الصوت"
// 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}")
   }
}
مقتطف Kotlin في الدقيقة 09:47 في فيديو "تصميم تجارب تركّز على الصوت"
// 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()
}
مقتطف Kotlin في الدقيقة 10:29 في فيديو "تصميم تجارب تركّز على الصوت"
// 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()
)
مقتطف Kotlin في الدقيقة 13:42 في فيديو "تصميم تجارب تركّز على الصوت"
dependencies {
   // ML Kit Speech Recognition
   implementation("com.google.mlkit:genai-speech-recognition:1.0.0-alpha1")
}
مقتطف Kotlin في الدقيقة 13:52 في فيديو "تصميم تجارب تركّز على الصوت"
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() }
...
مقتطف Kotlin في الدقيقة 14:12 في فيديو "تصميم تجارب تركّز على الصوت"
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()
       }
...
مقتطف Kotlin في الدقيقة 14:41 في فيديو "تصميم تجارب تركّز على الصوت"
// 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")
   }
}
مقتطف Kotlin في الدقيقة 26:27 في "تحديد أولوية وقت الاستجابة في الوقت الفعلي باستخدام تصميم ثنائي المسار"
// 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>"
مقتطف Kotlin في الدقيقة 27:33 في "إعطاء الأولوية لزمن الاستجابة في الوقت الفعلي من خلال تصميم ثنائي المسار"
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?
)
مقتطف Kotlin في الدقيقة 28:00 في فيديو "إعطاء الأولوية لزمن الاستجابة في الوقت الفعلي باستخدام تصميم ثنائي المسار"
// 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": "けん玉をください。", ...}