بعد طلب الأذونات اللازمة والحصول عليها، يمكن لتطبيقك الوصول إلى الأجهزة على النظارات الصوتية أو النظارات بشاشة عرض. للوصول إلى أجهزة النظارات (بدلاً من أجهزة الهاتف)، يجب استخدام سياق معروض.
هناك طريقتان أساسيتان للحصول على سياق معروض، وذلك حسب مكان تنفيذ الرمز البرمجي:
الحصول على سياق متوقّع إذا كان الرمز البرمجي يعمل في نشاط متوقّع
إذا كان رمز تطبيقك يعمل من داخل النشاط المعروض، فإنّ سياق نشاطه يكون سياقًا معروضًا. في هذا السيناريو، يمكن للمكالمات التي يتم إجراؤها ضمن هذا النشاط الوصول إلى أجهزة النظارات.
الحصول على سياق متوقّع للرمز البرمجي الذي يتم تشغيله في أحد مكوّنات تطبيق الهاتف
إذا كان جزء من تطبيقك خارج نشاطك المعروض (مثل نشاط الهاتف أو الخدمة) يحتاج إلى الوصول إلى أجهزة النظارات، فيجب أن يحصل صراحةً على سياق معروض. لإجراء ذلك، استخدِم طريقة
createProjectedDeviceContext:
@RequiresApi(Build.VERSION_CODES.BAKLAVA) @OptIn(ExperimentalProjectedApi::class, ExperimentalCoroutinesApi::class) private fun monitorProjectedConnectivity(activity: ComponentActivity) { activity.lifecycleScope.launch { // Before creating a projected context, check to see if the projected device is connected. // While this method returns true, the projected context remains valid. ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext) .collectLatest { isConnected -> if (isConnected) { // From a phone Activity or Service, get a context for the audio and display glasses. // Re-initialize on reconnect: Obtain another context instance. val projectedContext = try { ProjectedContext.createProjectedDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "Failed to create projected context", e) return@collectLatest } // Use the projectedContext to initialize system services (e.g., CameraManager). Log.i(TAG, "Projected device connected. Initializing hardware...") } else { // The projected context is destroyed when the device disconnects. // Clean up on disconnect: Listen for 'false' and release resources. Log.i(TAG, "Projected device disconnected. Cleaning up hardware resources...") } } } }
التحقّق من الصلاحية
أدرِج طلب createProjectedDeviceContext ضمن
ProjectedContext.isProjectedDeviceConnected. على الرغم من أنّ هذه الطريقة تعرض
true، يظل السياق المتوقّع صالحًا للجهاز المتصل، ويمكن لنشاط تطبيق الهاتف أو الخدمة (مثل CameraManager) الوصول إلى أجهزة النظارات بالذكاء الاصطناعي.
محو البيانات عند قطع الاتصال
يرتبط السياق المعروض بمراحل نشاط الجهاز المتصل، لذا يتم تدميره عند إلغاء اتصال الجهاز. عندما ينقطع اتصال الجهاز،
تعرض السمة ProjectedContext.isProjectedDeviceConnected القيمة false. يجب أن يستمع تطبيقك إلى هذا التغيير وأن يزيل أي خدمات نظام (مثل CameraManager) أو موارد أنشأها تطبيقك باستخدام سياق العرض هذا.
إعادة الإعداد عند إعادة الاتصال
عند إعادة اتصال النظارة، يمكن لتطبيقك الحصول على مثيل سياق معروض آخر باستخدام createProjectedDeviceContext، ثم إعادة إعداد أي خدمات أو موارد للنظام باستخدام السياق المعروض الجديد.
تسجيل الصوت باستخدام ميكروفون النظارة
يمكنك تسجيل الصوت من النظارات باستخدام ملف Bluetooth Hands-Free Profile (HFP). في هذا الوضع، تتصل النظارات بالهاتف باستخدام ملفات تعريف سماعات الرأس العادية وملفات تعريف التوزيع الصوتي المتقدم (A2DP)، وتعمل كجهاز ملحق عادي يعمل بالبلوتوث. إذا كان تطبيقك مصمّمًا للتسجيل عبر البلوتوث العادي، يمكنك استخدام هذه الطريقة لتسجيل الصوت من النظارات بدون أي تطوير إضافي.
تسجيل الصوت باستخدام ملف Bluetooth HFP
لتسجيل الصوت باستخدام Bluetooth HFP، عليك أولاً طلب أذونات التشغيل المطلوبة، ثم تسجيل الصوت باستخدام واجهة برمجة التطبيقات AudioManager، كما هو موضّح في الأقسام التالية.
طلب الحصول على الأذونات
كما هو الحال مع أي جهاز صوتي عادي يتضمّن بلوتوث، يتم التحكّم في RECORD_AUDIO وBLUETOOTH_CONNECT والأذونات الأخرى ذات الصلة من خلال الهاتف وليس الجهاز المتصل (مثل النظارات الصوتية أو النظارات بشاشة عرض).
اتّبِع الخطوات التالية لطلب الأذونات:
أعلِن عن الأذونات التالية في ملف بيان تطبيقك:
اطلب الحصول على إذنَي
RECORD_AUDIOوBLUETOOTH_CONNECTفي وقت التشغيل باستخدام مسار أذونات Android العادي.
استخدام AudioManager لتوجيه الصوت
بعد أن يمنح المستخدم تطبيقك أذونات التشغيل اللازمة، استخدِم واجهة برمجة التطبيقات
AudioManager لضبط جهاز الاتصال على
TYPE_BLUETOOTH_SCO لتوجيه الصوت من خلال ملف HFP للبلوتوث. يوجّه هذا الإجراء النظام لاسترداد الصوت من جهاز البلوتوث الملحق.
val audioManager = context.getSystemService(AudioManager::class.java) ?: return val devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS) val hfpDevice = devices.find { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO } hfpDevice?.let { device -> val audioRecord = AudioRecord.Builder() .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION) .setAudioFormat(audioFormat) .setBufferSizeInBytes(bufferSize) .build() // Route recording to the Bluetooth device audioRecord.setPreferredDevice(device) audioManager.setCommunicationDevice(device) audioRecord.startRecording()
التقاط صورة باستخدام كاميرا النظارات
لالتقاط صورة باستخدام كاميرا النظارات، عليك إعداد وربط ImageCapture حالة استخدام CameraX بكاميرا النظارات باستخدام السياق الصحيح لتطبيقك:
private fun startCameraOnGlasses(activity: ComponentActivity) { activity.lifecycleScope.launch { // Before creating a projected context, check to see if the projected device is connected. ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext) .collectLatest { isConnected -> if (isConnected) { // 1. Get the CameraProvider using the projected context. // When using the projected context, DEFAULT_BACK_CAMERA maps to the audio and display glasses' camera. val projectedContext = try { ProjectedContext.createProjectedDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "Projected context could not be created", e) return@collectLatest } val cameraProviderFuture = ProcessCameraProvider.getInstance(projectedContext) cameraProviderFuture.addListener({ val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA // 2. Check for the presence of a camera. if (!cameraProvider.hasCamera(cameraSelector)) { Log.w(TAG, "The selected camera is not available.") return@addListener } // 3. Query supported streaming resolutions using Camera2 Interop. val cameraInfo = cameraProvider.getCameraInfo(cameraSelector) val camera2CameraInfo = Camera2CameraInfo.from(cameraInfo) val cameraCharacteristics = camera2CameraInfo.getCameraCharacteristic( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP ) // 4. Define the resolution strategy. val targetResolution = Size(1920, 1080) val resolutionStrategy = ResolutionStrategy( targetResolution, ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER ) val resolutionSelector = ResolutionSelector.Builder() .setResolutionStrategy(resolutionStrategy) .build() // 5. If you have other continuous use cases bound, such as Preview or ImageAnalysis, // you can use Camera2 Interop's CaptureRequestOptions to set the FPS val fpsRange = Range(30, 60) val captureRequestOptions = CaptureRequestOptions.Builder() .setCaptureRequestOption(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange) .build() // 6. Initialize the ImageCapture use case with options. val imageCapture = ImageCapture.Builder() // Optional: Configure resolution, format, etc. .setResolutionSelector(resolutionSelector) .build() try { // Unbind use cases before rebinding. cameraProvider.unbindAll() // Bind use cases to camera using the Activity as the LifecycleOwner. cameraProvider.bindToLifecycle( activity, cameraSelector, imageCapture ) } catch (exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(activity)) } } } }
نقاط رئيسية حول الرمز
- يحصل على مثيل من
ProcessCameraProviderباستخدام سياق الجهاز المتوقّع. - ضمن نطاق السياق المعروض، يتم ربط الكاميرا الأساسية الموجّهة للخارج في النظارات بـ "
DEFAULT_BACK_CAMERA" عند اختيار كاميرا. - يستخدم فحص الربط المُسبق
cameraProvider.hasCamera(cameraSelector)للتحقّق من توفّر الكاميرا المحدّدة على الجهاز قبل المتابعة. - يستخدم Camera2 Interop مع
Camera2CameraInfoلقراءةCameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAPالأساسي، والذي يمكن أن يكون مفيدًا لإجراء عمليات تحقّق متقدّمة من درجات الدقة المتوافقة. - تم إنشاء
ResolutionSelectorمخصّص للتحكّم بدقة في درجة دقة الصورة الناتجة فيImageCapture. - تنشئ هذه الطريقة حالة استخدام
ImageCaptureتم ضبطها باستخدامResolutionSelectorمخصّص. - يربط حالة استخدام
ImageCaptureبمراحل النشاط. يؤدي ذلك إلى إدارة فتح الكاميرا وإغلاقها تلقائيًا استنادًا إلى حالة النشاط (على سبيل المثال، إيقاف الكاميرا عند إيقاف النشاط مؤقتًا). - الضبط للأجهزة ذات البث الواحد: يقتصر مسار كاميرا النظارات على بث نشط واحد في كل مرة. عند ربط
ImageCaptureوحده، تُرفِق واجهة CameraX تدفقًا داخليًا متكررًا (MeteringRepeating) للتركيز والقياس تلقائيًا، ما يؤدي إلى توقُّف مسار التدفق الفردي. لربطImageCaptureبمفرده بدون بث معاينة، عليك إيقاف بث التكرار الإجباري من خلال ضبطsetRepeatingStreamForced(false)علىCameraXConfig.Builder(على سبيل المثال، من خلال تنفيذCameraXConfig.Providerفي فئةApplicationأو ضبطProcessCameraProviderقبل الإعداد).
بعد إعداد كاميرا النظارات، يمكنك التقاط صورة باستخدام فئة
ImageCapture في CameraX. يُرجى الرجوع إلى مستندات CameraX للتعرّف على كيفية استخدام
takePicture من أجل التقاط صورة.
التقاط فيديو باستخدام كاميرا النظارات
لالتقاط فيديو بدلاً من صورة باستخدام كاميرا النظارات، استبدِل مكوّنات ImageCapture بمكوّنات VideoCapture المقابلة وعدِّل منطق تنفيذ الالتقاط.
تتضمن التغييرات الرئيسية استخدام حالة استخدام مختلفة، وإنشاء ملف إخراج مختلف، وبدء الالتقاط باستخدام طريقة تسجيل الفيديو المناسبة.
لمزيد من المعلومات حول واجهة برمجة التطبيقات VideoCapture وكيفية استخدامها، يُرجى الاطّلاع على
مستندات التقاط الفيديو في CameraX.
يعرض الجدول التالي درجة الدقة وعدد اللقطات في الثانية المقترَحَين استنادًا إلى حالة استخدام تطبيقك:
| حالة الاستخدام | درجة الدقة | عدد اللقطات في الثانية |
|---|---|---|
| التواصل عبر الفيديو | 1280 x 720 | 15 لقطة في الثانية |
| الرؤية الحاسوبية | 640 × 480 | 10 لقطات في الثانية |
| بث الفيديوهات بالذكاء الاصطناعي | 640 × 480 | لقطة واحدة في الثانية |
الوصول إلى أجهزة الهاتف من نشاط معروض
يمكن أيضًا للنشاط المعروض الوصول إلى أجهزة الهاتف (مثل
الكاميرا أو الميكروفون) باستخدام createHostDeviceContext(context) للحصول على
سياق الجهاز المضيف (الهاتف):
@OptIn(ExperimentalProjectedApi::class) private fun getPhoneContext(activity: ComponentActivity): Context? { return try { // From a projected Activity, get a context for the phone. ProjectedContext.createHostDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "Failed to create host device context", e) null } }
عند الوصول إلى الأجهزة أو الموارد الخاصة بالجهاز المضيف (الهاتف) في تطبيق مختلط (تطبيق يحتوي على تجارب لكل من الأجهزة الجوّالة والنظارات)، يجب تحديد السياق الصحيح بشكل صريح للتأكّد من أنّ تطبيقك يمكنه الوصول إلى الأجهزة الصحيحة:
- استخدِم سياق
Activityمن الهاتفActivityأوProjectedContext.createHostDeviceContextللحصول على سياق الهاتف. - لا تستخدِم
getApplicationContextلأنّ سياق التطبيق يمكن أن يعرض سياق النظارات بشكل غير صحيح إذا كان النشاط المعروض هو المكوّن الذي تم إطلاقه مؤخرًا.