@mentra/bluetooth-sdk 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +16 -14
  2. package/android/build.gradle +2 -9
  3. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +40 -31
  4. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +22 -9
  5. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +25 -36
  6. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +8 -4
  7. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +34 -10
  8. package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +6 -6
  9. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +29 -7
  10. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +4 -3
  11. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +2 -1
  12. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +12 -0
  13. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +1 -1
  14. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +2 -1
  15. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1 -1
  16. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +168 -10
  17. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
  18. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +7 -3
  19. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +3 -1
  20. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +14 -12
  21. package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +4 -2
  22. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +3 -3
  23. package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -3
  24. package/build/BluetoothSdk.types.d.ts +42 -20
  25. package/build/BluetoothSdk.types.d.ts.map +1 -1
  26. package/build/BluetoothSdk.types.js +1 -0
  27. package/build/BluetoothSdk.types.js.map +1 -1
  28. package/build/_private/BluetoothSdkModule.d.ts +6 -5
  29. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  30. package/build/_private/BluetoothSdkModule.js +37 -5
  31. package/build/_private/BluetoothSdkModule.js.map +1 -1
  32. package/build/index.d.ts +1 -1
  33. package/build/index.d.ts.map +1 -1
  34. package/build/index.js +4 -2
  35. package/build/index.js.map +1 -1
  36. package/build/react/useMentraBluetooth.d.ts +5 -3
  37. package/build/react/useMentraBluetooth.d.ts.map +1 -1
  38. package/build/react/useMentraBluetooth.js +15 -4
  39. package/build/react/useMentraBluetooth.js.map +1 -1
  40. package/ios/BluetoothSdkModule.swift +41 -24
  41. package/ios/MentraBluetoothSDK.podspec +0 -3
  42. package/ios/Source/Audio/AudioModels.swift +10 -10
  43. package/ios/Source/Bridge.swift +22 -11
  44. package/ios/Source/Camera/CameraModels.swift +13 -11
  45. package/ios/Source/DeviceManager.swift +20 -97
  46. package/ios/Source/DeviceStore.swift +12 -4
  47. package/ios/Source/Events/BluetoothEvents.swift +34 -0
  48. package/ios/Source/MentraBluetoothSDK.swift +25 -10
  49. package/ios/Source/Status/DeviceStatus.swift +12 -4
  50. package/ios/Source/Status/RuntimeState.swift +22 -13
  51. package/ios/Source/Streaming/StreamModels.swift +5 -5
  52. package/ios/Source/controllers/ControllerManager.swift +3 -3
  53. package/ios/Source/controllers/R1.swift +1 -1
  54. package/ios/Source/sgcs/G1.swift +1 -1
  55. package/ios/Source/sgcs/G2.swift +2 -3
  56. package/ios/Source/sgcs/Mach1.swift +1 -1
  57. package/ios/Source/sgcs/MentraLive.swift +109 -12
  58. package/ios/Source/sgcs/MentraNex.swift +1 -1
  59. package/ios/Source/sgcs/SGCManager.swift +11 -3
  60. package/ios/Source/sgcs/Simulated.swift +2 -2
  61. package/ios/Source/stt/SherpaOnnxTranscriber.swift +1 -4
  62. package/package.json +1 -1
  63. package/src/BluetoothSdk.types.ts +44 -28
  64. package/src/_private/BluetoothSdkModule.ts +44 -21
  65. package/src/index.ts +6 -4
  66. package/src/react/useMentraBluetooth.ts +19 -7
  67. package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +0 -221
  68. package/ios/Packages/VAD/Common/VAD/VADQuality.swift +0 -41
  69. package/ios/Packages/VAD/Common/VAD/VADState.swift +0 -26
  70. package/ios/Packages/VAD/Common/VAD/VADStrategy.swift +0 -29
  71. package/ios/Packages/VAD/Common/VAD/VADType.swift +0 -14
  72. package/ios/Packages/VAD/Data/Configuration.swift +0 -68
  73. package/ios/Packages/VAD/Data/FrameSize.swift +0 -39
  74. package/ios/Packages/VAD/Data/Record.swift +0 -13
  75. package/ios/Packages/VAD/Data/Result.swift +0 -22
  76. package/ios/Packages/VAD/Data/SampleRate.swift +0 -48
  77. package/ios/Packages/VAD/Silero/Model/silero_vad.onnx +0 -0
  78. package/ios/Packages/VAD/Silero/SileroVAD.swift +0 -284
  79. package/ios/Packages/VAD/Silero/SileroVADStrategy.swift +0 -64
@@ -1 +1 @@
1
- {"version":3,"file":"useMentraBluetooth.js","sourceRoot":"","sources":["../../src/react/useMentraBluetooth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,OAAO,CAAA;AAE9B,OAAO,YAAY,MAAM,UAAU,CAAA;AACnC,OAAO,EACL,YAAY,EACZ,+BAA+B,EAC/B,kCAAkC,EAClC,8BAA8B,GAC/B,MAAM,uBAAuB,CAAA;AAe9B,OAAO,EACL,oBAAoB,GAGrB,MAAM,wBAAwB,CAAA;AAgH/B,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AAC1E,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;AAC3E,CAAC;AAED,SAAS,YAAY,CAAC,MAAoC;IACxD,MAAM,eAAe,GAAG;QACtB,CAAC,iBAAiB,EAAE,UAAU,CAAC;QAC/B,CAAC,uBAAuB,EAAE,QAAQ,CAAC;QACnC,CAAC,sBAAsB,EAAE,OAAO,CAAC;QACjC,CAAC,qBAAqB,EAAE,MAAM,CAAC;QAC/B,CAAC,oBAAoB,EAAE,KAAK,CAAC;QAC7B,CAAC,oBAAoB,EAAE,KAAK,CAAC;QAC7B,CAAC,YAAY,EAAE,KAAK,CAAC;KACb,CAAA;IAEV,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,eAAe,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,WAAW,CAAE,MAAkC,CAAC,GAAG,CAAC,CAAC,CAAA;QACnE,IAAI,KAAK,EAAE,CAAC;YACV,OAAO;gBACL,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC;gBAC1C,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC;gBAC5C,MAAM;gBACN,OAAO,EAAE,KAAK;aACf,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC;QAC1C,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC;QAC5C,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE,IAAI;KACd,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CAAC,MAAoC;IACxD,MAAM,KAAK,GAAG,OAAO,MAAM,CAAC,YAAY,KAAK,QAAQ,IAAI,MAAM,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAA;IAC9G,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,KAAK;QAClC,KAAK;KACN,CAAA;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,MAAoC;IAChE,OAAO;QACL,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC;QAC1C,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,aAAa,CAAC;QAChD,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC;QAC5C,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC;QAChC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC;QAC5C,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,eAAe,CAAC;QACpD,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,YAAY,CAAC;QAC9C,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC;KACjC,CAAA;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,MAAoC;IAC/D,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,+BAA+B,EAAE,CAAC,UAAU,IAAI,EAAC,KAAK,EAAE,cAAc,EAAC,CAAA;IAE/G,IAAI,CAAC,kCAAkC,CAAC,UAAU,CAAC,EAAE,CAAC;QACpD,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,UAAU;YACV,KAAK,EAAE,KAAK;SACb,CAAA;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE,YAAY,CAAC,MAAM,CAAC;QAC7B,SAAS,EAAE,IAAI;QACf,UAAU;QACV,MAAM,EAAE,oBAAoB,CAAC,MAAM,CAAC;QACpC,QAAQ,EAAE,YAAY,CAAC,MAAM,CAAC;QAC9B,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAC,KAAK,EAAE,UAAU,EAAC;QAC9C,KAAK,EAAE,8BAA8B,CAAC,UAAU,CAAC;QACjD,MAAM,EAAE;YACN,WAAW,EAAE,WAAW,CAAE,MAAkC,CAAC,cAAc,CAAC;YAC5E,SAAS,EAAE,WAAW,CAAE,MAAkC,CAAC,uBAAuB,CAAC;SACpF;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,EAAC,KAAK,EAAE,cAAc,EAAC;KAC7C,CAAA;AACH,CAAC;AAED,SAAS,aAAa,CACpB,MAAsC,EACtC,aAA4B,EAC5B,WAA6B;IAE7B,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI;QACrC,aAAa;QACb,WAAW;QACX,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;QAC7B,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,EAAE;QACnC,uBAAuB,EAAE,MAAM,CAAC,gBAAgB,IAAI,KAAK;QACzD,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,KAAK;QACpC,mBAAmB,EAAE,MAAM,CAAC,mBAAmB,IAAI,KAAK;QACxD,oBAAoB,EAAE,MAAM,CAAC,oBAAoB,IAAI,KAAK;QAC1D,eAAe,EAAE,MAAM,CAAC,eAAe,IAAI,EAAE;KAC9C,CAAA;AACH,CAAC;AAED,SAAS,cAAc,CAAC,UAAuC;IAC7D,OAAO;QACL,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,QAAQ;QAChC,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,YAAY;QACnC,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,OAAO;QAChC,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,KAAK;QAC5B,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,KAAK;QAC5B,cAAc,EAAE,UAAU,CAAC,IAAI,CAAC,cAAc;QAC9C,YAAY,EAAE,UAAU,CAAC,IAAI,CAAC,YAAY;QAC1C,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,QAAQ;QAClC,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,SAAS;QAChC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,QAAQ;KAC/B,CAAA;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,UAAqC,EAAE;IACxE,MAAM,UAAU,GAAG,oBAAoB,CAAC;QACtC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;QAC9C,oBAAoB,EAAE,OAAO,CAAC,oBAAoB;QAClD,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,SAAS,EAAE,OAAO,CAAC,YAAY,IAAI,YAAY,CAAC,UAAU;QAC1D,aAAa,EAAE,OAAO,CAAC,aAAa;KACrC,CAAC,CAAA;IACF,MAAM,CAAC,mBAAmB,EAAE,sBAAsB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACrE,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAiB,IAAI,CAAC,CAAA;IAE9E,KAAK,UAAU,cAAc,CAAC,IAAiB;QAC7C,sBAAsB,CAAC,IAAI,CAAC,CAAA;QAC5B,mBAAmB,CAAC,IAAI,CAAC,CAAA;QACzB,IAAI,CAAC;YACH,MAAM,YAAY,CAAC,cAAc,CAAC,IAAI,CAAC,CAAA;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,mBAAmB,CAAC,KAAK,CAAC,CAAA;YAC1B,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAA;YACxB,MAAM,KAAK,CAAA;QACb,CAAC;gBAAS,CAAC;YACT,sBAAsB,CAAC,KAAK,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAqB;QACpC,QAAQ,EAAE,mBAAmB;QAC7B,OAAO,EAAE,UAAU,CAAC,eAAe,CAAC,eAAe,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM;QACjF,KAAK,EAAE,gBAAgB;KACxB,CAAA;IAED,OAAO;QACL,IAAI,EAAE,UAAU,CAAC,IAAI,IAAI,mBAAmB;QAC5C,kBAAkB,EAAE,UAAU,CAAC,kBAAkB;QACjD,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,cAAc,EAAE,UAAU,CAAC,cAAc;QACzC,aAAa,EAAE,UAAU,CAAC,aAAa;QACvC,UAAU,EAAE,UAAU,CAAC,UAAU;QACjC,KAAK,EAAE,gBAAgB,IAAI,UAAU,CAAC,KAAK;QAC3C,OAAO,EAAE,mBAAmB,CAAC,UAAU,CAAC,aAAa,CAAC;QACtD,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC;QAChC,GAAG,EAAE,aAAa,CAAC,UAAU,CAAC,eAAe,EAAE,UAAU,CAAC,aAAa,EAAE,WAAW,CAAC;QACrF,gBAAgB,EAAE,UAAU,CAAC,gBAAgB;QAC7C,cAAc;KACf,CAAA;AACH,CAAC","sourcesContent":["import {useState} from \"react\"\n\nimport BluetoothSdk from \"../index\"\nimport {\n DeviceModels,\n createDisconnectedGlassesStatus,\n isConnectedGlassesConnectionStatus,\n isReadyGlassesConnectionStatus,\n} from \"../BluetoothSdk.types\"\nimport type {\n ConnectOptions,\n Device,\n DeviceModel,\n GalleryMode,\n GlassesConnectionStatus,\n HotspotStatus,\n PublicBluetoothStatus,\n PublicGlassesStatus,\n MicMode,\n WifiSearchResult,\n WifiStatus,\n} from \"../BluetoothSdk.types\"\n\nimport {\n useGlassesConnection,\n type DefaultDeviceStorage,\n type GlassesConnectionHookResult,\n} from \"./useGlassesConnection\"\n\nexport type BatteryState = {\n charging: boolean\n level: number | null\n}\n\nexport type ConnectedGlassesInfo = {\n appVersion?: string\n bluetoothName?: string\n buildNumber?: string\n color?: string\n deviceModel?: string\n firmwareVersion?: string\n serialNumber?: string\n style?: string\n}\n\nexport type FirmwareInfo = {\n appVersion?: string\n buildNumber?: string\n source:\n | \"app\"\n | \"bes\"\n | \"device\"\n | \"firmware\"\n | \"left\"\n | \"mtk\"\n | \"right\"\n | \"unknown\"\n version: string | null\n}\n\nexport type SignalState = {\n strengthDbm: number | null\n updatedAt: number | null\n}\n\nexport type GlassesRuntimeState =\n | {\n connected: false\n connection: Exclude<GlassesConnectionStatus, {state: \"connected\"}>\n ready: false\n }\n | {\n battery: BatteryState\n connected: true\n connection: Extract<GlassesConnectionStatus, {state: \"connected\"}>\n device: ConnectedGlassesInfo\n firmware: FirmwareInfo\n hotspot: HotspotStatus\n ready: boolean\n signal: SignalState\n wifi: WifiStatus\n }\n\nexport type GalleryModeState = {\n applying: boolean\n desired: GalleryMode\n error: unknown | null\n}\n\nexport type PhoneSdkRuntimeState = {\n currentMic: MicMode | null\n defaultDevice: Device | null\n galleryMode: GalleryModeState\n lastLog: string[]\n micRanking: MicMode[]\n otherBluetoothConnected: boolean\n searching: boolean\n searchingController: boolean\n systemMicUnavailable: boolean\n wifiScanResults: WifiSearchResult[]\n}\n\nexport type ScanController = {\n active: boolean\n clear: () => void\n devices: Device[]\n error: unknown | null\n model: DeviceModel\n selectedDevice: Device | null\n selectDevice: (device: Device | null) => void\n setModel: (model: DeviceModel) => void\n start: (model?: DeviceModel) => Promise<Device[]>\n stop: () => Promise<void>\n}\n\nexport type UseMentraBluetoothOptions = {\n autoConnectDefault?: boolean\n defaultDeviceStorage?: DefaultDeviceStorage\n defaultModel?: DeviceModel\n onError?: (error: unknown) => void\n scanTimeoutMs?: number\n}\n\nexport type MentraBluetoothSession = {\n busy: boolean\n clearDefaultDevice: () => Promise<void>\n connect: (device?: Device, options?: ConnectOptions) => Promise<void>\n connectDefault: (options?: ConnectOptions) => Promise<void>\n defaultDevice: Device | null\n disconnect: () => Promise<void>\n error: unknown | null\n glasses: GlassesRuntimeState\n refresh: () => Promise<void>\n scan: ScanController\n sdk: PhoneSdkRuntimeState\n setDefaultDevice: (device: Device | null) => Promise<void>\n setGalleryMode: (mode: GalleryMode) => Promise<void>\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined\n}\n\nfunction numberValue(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null\n}\n\nfunction firmwareInfo(status: Partial<PublicGlassesStatus>): FirmwareInfo {\n const firmwareSources = [\n [\"firmwareVersion\", \"firmware\"],\n [\"deviceFirmwareVersion\", \"device\"],\n [\"rightFirmwareVersion\", \"right\"],\n [\"leftFirmwareVersion\", \"left\"],\n [\"besFirmwareVersion\", \"bes\"],\n [\"mtkFirmwareVersion\", \"mtk\"],\n [\"appVersion\", \"app\"],\n ] as const\n\n for (const [key, source] of firmwareSources) {\n const value = stringValue((status as Record<string, unknown>)[key])\n if (value) {\n return {\n appVersion: stringValue(status.appVersion),\n buildNumber: stringValue(status.buildNumber),\n source,\n version: value,\n }\n }\n }\n\n return {\n appVersion: stringValue(status.appVersion),\n buildNumber: stringValue(status.buildNumber),\n source: \"unknown\",\n version: null,\n }\n}\n\nfunction batteryState(status: Partial<PublicGlassesStatus>): BatteryState {\n const level = typeof status.batteryLevel === \"number\" && status.batteryLevel >= 0 ? status.batteryLevel : null\n return {\n charging: status.charging ?? false,\n level,\n }\n}\n\nfunction connectedGlassesInfo(status: Partial<PublicGlassesStatus>): ConnectedGlassesInfo {\n return {\n appVersion: stringValue(status.appVersion),\n bluetoothName: stringValue(status.bluetoothName),\n buildNumber: stringValue(status.buildNumber),\n color: stringValue(status.color),\n deviceModel: stringValue(status.deviceModel),\n firmwareVersion: stringValue(status.firmwareVersion),\n serialNumber: stringValue(status.serialNumber),\n style: stringValue(status.style),\n }\n}\n\nfunction runtimeGlassesState(status: Partial<PublicGlassesStatus>): GlassesRuntimeState {\n const connection = status.connection ?? createDisconnectedGlassesStatus().connection ?? {state: \"disconnected\"}\n\n if (!isConnectedGlassesConnectionStatus(connection)) {\n return {\n connected: false,\n connection,\n ready: false,\n }\n }\n\n return {\n battery: batteryState(status),\n connected: true,\n connection,\n device: connectedGlassesInfo(status),\n firmware: firmwareInfo(status),\n hotspot: status.hotspot ?? {state: \"disabled\"},\n ready: isReadyGlassesConnectionStatus(connection),\n signal: {\n strengthDbm: numberValue((status as Record<string, unknown>).signalStrength),\n updatedAt: numberValue((status as Record<string, unknown>).signalStrengthUpdatedAt),\n },\n wifi: status.wifi ?? {state: \"disconnected\"},\n }\n}\n\nfunction phoneSdkState(\n status: Partial<PublicBluetoothStatus>,\n defaultDevice: Device | null,\n galleryMode: GalleryModeState,\n): PhoneSdkRuntimeState {\n return {\n currentMic: status.currentMic || null,\n defaultDevice,\n galleryMode,\n lastLog: status.lastLog ?? [],\n micRanking: status.micRanking ?? [],\n otherBluetoothConnected: status.otherBtConnected ?? false,\n searching: status.searching ?? false,\n searchingController: status.searchingController ?? false,\n systemMicUnavailable: status.systemMicUnavailable ?? false,\n wifiScanResults: status.wifiScanResults ?? [],\n }\n}\n\nfunction scanController(connection: GlassesConnectionHookResult): ScanController {\n return {\n active: connection.scan.scanning,\n clear: connection.scan.clearResults,\n devices: connection.scan.devices,\n error: connection.scan.error,\n model: connection.scan.model,\n selectedDevice: connection.scan.selectedDevice,\n selectDevice: connection.scan.selectDevice,\n setModel: connection.scan.setModel,\n start: connection.scan.startScan,\n stop: connection.scan.stopScan,\n }\n}\n\nexport function useMentraBluetooth(options: UseMentraBluetoothOptions = {}): MentraBluetoothSession {\n const connection = useGlassesConnection({\n autoConnectDefault: options.autoConnectDefault,\n defaultDeviceStorage: options.defaultDeviceStorage,\n onError: options.onError,\n scanModel: options.defaultModel ?? DeviceModels.MentraLive,\n scanTimeoutMs: options.scanTimeoutMs,\n })\n const [galleryModeApplying, setGalleryModeApplying] = useState(false)\n const [galleryModeError, setGalleryModeError] = useState<unknown | null>(null)\n\n async function setGalleryMode(mode: GalleryMode) {\n setGalleryModeApplying(true)\n setGalleryModeError(null)\n try {\n await BluetoothSdk.setGalleryMode(mode)\n } catch (error) {\n setGalleryModeError(error)\n options.onError?.(error)\n throw error\n } finally {\n setGalleryModeApplying(false)\n }\n }\n\n const galleryMode: GalleryModeState = {\n applying: galleryModeApplying,\n desired: connection.bluetoothStatus.galleryModeAuto === false ? \"manual\" : \"auto\",\n error: galleryModeError,\n }\n\n return {\n busy: connection.busy || galleryModeApplying,\n clearDefaultDevice: connection.clearDefaultDevice,\n connect: connection.connect,\n connectDefault: connection.connectDefault,\n defaultDevice: connection.defaultDevice,\n disconnect: connection.disconnect,\n error: galleryModeError ?? connection.error,\n glasses: runtimeGlassesState(connection.glassesStatus),\n refresh: connection.refresh,\n scan: scanController(connection),\n sdk: phoneSdkState(connection.bluetoothStatus, connection.defaultDevice, galleryMode),\n setDefaultDevice: connection.setDefaultDevice,\n setGalleryMode,\n }\n}\n"]}
1
+ {"version":3,"file":"useMentraBluetooth.js","sourceRoot":"","sources":["../../src/react/useMentraBluetooth.ts"],"names":[],"mappings":"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","sourcesContent":["import {useState} from \"react\"\n\nimport BluetoothSdk from \"../index\"\nimport {\n DeviceModels,\n createDisconnectedGlassesStatus,\n isConnectedGlassesConnectionStatus,\n isReadyGlassesConnectionStatus,\n} from \"../BluetoothSdk.types\"\nimport type {\n ConnectOptions,\n Device,\n DeviceModel,\n GlassesConnectionStatus,\n HotspotStatus,\n PublicBluetoothStatus,\n PublicGlassesStatus,\n MicMode,\n WifiSearchResult,\n WifiStatus,\n} from \"../BluetoothSdk.types\"\n\nimport {\n useGlassesConnection,\n type DefaultDeviceStorage,\n type GlassesConnectionHookResult,\n} from \"./useGlassesConnection\"\n\nexport type BatteryState = {\n charging: boolean\n level: number | null\n}\n\nexport type ConnectedGlassesInfo = {\n appVersion?: string\n bluetoothName?: string\n buildNumber?: string\n color?: string\n deviceModel?: string\n firmwareVersion?: string\n serialNumber?: string\n style?: string\n}\n\nexport type FirmwareInfo = {\n appVersion?: string\n buildNumber?: string\n source:\n | \"app\"\n | \"bes\"\n | \"device\"\n | \"firmware\"\n | \"left\"\n | \"mtk\"\n | \"right\"\n | \"unknown\"\n version: string | null\n}\n\nexport type SignalState = {\n strengthDbm: number | null\n updatedAt: number | null\n}\n\nexport type GlassesRuntimeState =\n | {\n connected: false\n connection: Exclude<GlassesConnectionStatus, {state: \"connected\"}>\n ready: false\n }\n | {\n battery: BatteryState\n connected: true\n connection: Extract<GlassesConnectionStatus, {state: \"connected\"}>\n device: ConnectedGlassesInfo\n firmware: FirmwareInfo\n hotspot: HotspotStatus\n ready: boolean\n signal: SignalState\n voiceActivityDetectionEnabled: boolean\n wifi: WifiStatus\n }\n\nexport type GalleryModeState = {\n applying: boolean\n enabled: boolean\n error: unknown | null\n}\n\nexport type PhoneSdkRuntimeState = {\n currentMic: MicMode | null\n defaultDevice: Device | null\n galleryMode: GalleryModeState\n lastLog: string[]\n micRanking: MicMode[]\n otherBluetoothConnected: boolean\n searching: boolean\n searchingController: boolean\n systemMicUnavailable: boolean\n wifiScanResults: WifiSearchResult[]\n}\n\nexport type ScanController = {\n active: boolean\n clear: () => void\n devices: Device[]\n error: unknown | null\n model: DeviceModel\n selectedDevice: Device | null\n selectDevice: (device: Device | null) => void\n setModel: (model: DeviceModel) => void\n start: (model?: DeviceModel) => Promise<Device[]>\n stop: () => Promise<void>\n}\n\nexport type UseMentraBluetoothOptions = {\n autoConnectDefault?: boolean\n defaultDeviceStorage?: DefaultDeviceStorage\n defaultModel?: DeviceModel\n onError?: (error: unknown) => void\n scanTimeoutMs?: number\n}\n\nexport type MentraBluetoothSession = {\n busy: boolean\n clearDefaultDevice: () => Promise<void>\n connect: (device?: Device, options?: ConnectOptions) => Promise<void>\n connectDefault: (options?: ConnectOptions) => Promise<void>\n defaultDevice: Device | null\n disconnect: () => Promise<void>\n error: unknown | null\n glasses: GlassesRuntimeState\n refresh: () => Promise<void>\n scan: ScanController\n sdk: PhoneSdkRuntimeState\n setDefaultDevice: (device: Device | null) => Promise<void>\n setGalleryModeEnabled: (enabled: boolean) => Promise<void>\n setVoiceActivityDetectionEnabled: (enabled: boolean) => Promise<void>\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined\n}\n\nfunction numberValue(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null\n}\n\nfunction firmwareInfo(status: Partial<PublicGlassesStatus>): FirmwareInfo {\n const firmwareSources = [\n [\"firmwareVersion\", \"firmware\"],\n [\"deviceFirmwareVersion\", \"device\"],\n [\"rightFirmwareVersion\", \"right\"],\n [\"leftFirmwareVersion\", \"left\"],\n [\"besFirmwareVersion\", \"bes\"],\n [\"mtkFirmwareVersion\", \"mtk\"],\n [\"appVersion\", \"app\"],\n ] as const\n\n for (const [key, source] of firmwareSources) {\n const value = stringValue((status as Record<string, unknown>)[key])\n if (value) {\n return {\n appVersion: stringValue(status.appVersion),\n buildNumber: stringValue(status.buildNumber),\n source,\n version: value,\n }\n }\n }\n\n return {\n appVersion: stringValue(status.appVersion),\n buildNumber: stringValue(status.buildNumber),\n source: \"unknown\",\n version: null,\n }\n}\n\nfunction batteryState(status: Partial<PublicGlassesStatus>): BatteryState {\n const level = typeof status.batteryLevel === \"number\" && status.batteryLevel >= 0 ? status.batteryLevel : null\n return {\n charging: status.charging ?? false,\n level,\n }\n}\n\nfunction connectedGlassesInfo(status: Partial<PublicGlassesStatus>): ConnectedGlassesInfo {\n return {\n appVersion: stringValue(status.appVersion),\n bluetoothName: stringValue(status.bluetoothName),\n buildNumber: stringValue(status.buildNumber),\n color: stringValue(status.color),\n deviceModel: stringValue(status.deviceModel),\n firmwareVersion: stringValue(status.firmwareVersion),\n serialNumber: stringValue(status.serialNumber),\n style: stringValue(status.style),\n }\n}\n\nfunction runtimeGlassesState(status: Partial<PublicGlassesStatus>): GlassesRuntimeState {\n const connection = status.connection ?? createDisconnectedGlassesStatus().connection ?? {state: \"disconnected\"}\n\n if (!isConnectedGlassesConnectionStatus(connection)) {\n return {\n connected: false,\n connection,\n ready: false,\n }\n }\n\n return {\n battery: batteryState(status),\n connected: true,\n connection,\n device: connectedGlassesInfo(status),\n firmware: firmwareInfo(status),\n hotspot: status.hotspot ?? {state: \"disabled\"},\n ready: isReadyGlassesConnectionStatus(connection),\n signal: {\n strengthDbm: numberValue((status as Record<string, unknown>).signalStrength),\n updatedAt: numberValue((status as Record<string, unknown>).signalStrengthUpdatedAt),\n },\n voiceActivityDetectionEnabled: status.voiceActivityDetectionEnabled ?? true,\n wifi: status.wifi ?? {state: \"disconnected\"},\n }\n}\n\nfunction phoneSdkState(\n status: Partial<PublicBluetoothStatus>,\n defaultDevice: Device | null,\n galleryMode: GalleryModeState,\n): PhoneSdkRuntimeState {\n return {\n currentMic: status.currentMic || null,\n defaultDevice,\n galleryMode,\n lastLog: status.lastLog ?? [],\n micRanking: status.micRanking ?? [],\n otherBluetoothConnected: status.otherBtConnected ?? false,\n searching: status.searching ?? false,\n searchingController: status.searchingController ?? false,\n systemMicUnavailable: status.systemMicUnavailable ?? false,\n wifiScanResults: status.wifiScanResults ?? [],\n }\n}\n\nfunction scanController(connection: GlassesConnectionHookResult): ScanController {\n return {\n active: connection.scan.scanning,\n clear: connection.scan.clearResults,\n devices: connection.scan.devices,\n error: connection.scan.error,\n model: connection.scan.model,\n selectedDevice: connection.scan.selectedDevice,\n selectDevice: connection.scan.selectDevice,\n setModel: connection.scan.setModel,\n start: connection.scan.startScan,\n stop: connection.scan.stopScan,\n }\n}\n\nexport function useMentraBluetooth(options: UseMentraBluetoothOptions = {}): MentraBluetoothSession {\n const connection = useGlassesConnection({\n autoConnectDefault: options.autoConnectDefault,\n defaultDeviceStorage: options.defaultDeviceStorage,\n onError: options.onError,\n scanModel: options.defaultModel ?? DeviceModels.MentraLive,\n scanTimeoutMs: options.scanTimeoutMs,\n })\n const [galleryModeApplying, setGalleryModeApplying] = useState(false)\n const [galleryModeError, setGalleryModeError] = useState<unknown | null>(null)\n\n async function setGalleryModeEnabled(enabled: boolean) {\n setGalleryModeApplying(true)\n setGalleryModeError(null)\n try {\n await BluetoothSdk.setGalleryModeEnabled(enabled)\n } catch (error) {\n setGalleryModeError(error)\n options.onError?.(error)\n throw error\n } finally {\n setGalleryModeApplying(false)\n }\n }\n\n async function setVoiceActivityDetectionEnabled(enabled: boolean) {\n try {\n await BluetoothSdk.setVoiceActivityDetectionEnabled(enabled)\n } catch (error) {\n options.onError?.(error)\n throw error\n }\n }\n\n const galleryMode: GalleryModeState = {\n applying: galleryModeApplying,\n enabled: connection.bluetoothStatus.galleryModeEnabled !== false,\n error: galleryModeError,\n }\n\n return {\n busy: connection.busy || galleryModeApplying,\n clearDefaultDevice: connection.clearDefaultDevice,\n connect: connection.connect,\n connectDefault: connection.connectDefault,\n defaultDevice: connection.defaultDevice,\n disconnect: connection.disconnect,\n error: galleryModeError ?? connection.error,\n glasses: runtimeGlassesState(connection.glassesStatus),\n refresh: connection.refresh,\n scan: scanController(connection),\n sdk: phoneSdkState(connection.bluetoothStatus, connection.defaultDevice, galleryMode),\n setDefaultDevice: connection.setDefaultDevice,\n setGalleryModeEnabled,\n setVoiceActivityDetectionEnabled,\n }\n}\n"]}
@@ -19,7 +19,8 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
19
19
  "button_press",
20
20
  "touch_event",
21
21
  "head_up",
22
- "vad_status",
22
+ "voice_activity_detection_status",
23
+ "speaking_status",
23
24
  "battery_status",
24
25
  "wifi_status_change",
25
26
  "hotspot_status_change",
@@ -291,21 +292,14 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
291
292
 
292
293
  // MARK: - Gallery Commands
293
294
 
294
- AsyncFunction("setGalleryMode") { (mode: String) in
295
- let galleryMode: GalleryMode
296
- switch mode.lowercased() {
297
- case "auto":
298
- galleryMode = .auto
299
- case "manual":
300
- galleryMode = .manual
301
- default:
302
- throw BluetoothError(
303
- code: "invalid_gallery_mode",
304
- message: "setGalleryMode mode must be \"auto\" or \"manual\"."
305
- )
306
- }
295
+ AsyncFunction("setGalleryModeEnabled") { (enabled: Bool) in
296
+ let sdk = await MainActor.run { self.bluetoothSdk() }
297
+ try await sdk.setGalleryModeEnabled(enabled)
298
+ }
299
+
300
+ AsyncFunction("setVoiceActivityDetectionEnabled") { (enabled: Bool) in
307
301
  let sdk = await MainActor.run { self.bluetoothSdk() }
308
- try await sdk.setGalleryMode(galleryMode)
302
+ try await sdk.setVoiceActivityDetectionEnabled(enabled)
309
303
  }
310
304
 
311
305
  AsyncFunction("queryGalleryStatus") {
@@ -314,11 +308,30 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
314
308
  }
315
309
  }
316
310
 
317
- AsyncFunction("requestPhoto") {
318
- (
319
- requestId: String, appId: String, size: String, webhookUrl: String?,
320
- authToken: String?, compress: String?, sound: Bool
321
- ) in
311
+ AsyncFunction("requestPhoto") { (params: [String: Any]) in
312
+ let requestId = params["requestId"] as? String ?? ""
313
+ let appId = params["appId"] as? String ?? ""
314
+ Bridge.log(
315
+ "NATIVE: PHOTO PIPELINE [3/6] BluetoothSdk.requestPhoto requestId=\(requestId) appId=\(appId)"
316
+ )
317
+ let size = params["size"] as? String ?? "medium"
318
+ let webhookUrl = params["webhookUrl"] as? String ?? ""
319
+ let authToken = params["authToken"] as? String ?? ""
320
+ let compress = params["compress"] as? String ?? "none"
321
+ let flash = params["flash"] as? Bool ?? true
322
+ let sound = params["sound"] as? Bool ?? true
323
+ let exposureTimeNs: Double?
324
+ switch params["exposureTimeNs"] {
325
+ case let value as Double:
326
+ exposureTimeNs = value
327
+ case let value as Int:
328
+ exposureTimeNs = Double(value)
329
+ case let value as NSNumber:
330
+ exposureTimeNs = value.doubleValue
331
+ default:
332
+ exposureTimeNs = nil
333
+ }
334
+
322
335
  await MainActor.run {
323
336
  self.bluetoothSdk().requestPhoto(
324
337
  PhotoRequest(
@@ -327,8 +340,10 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
327
340
  size: PhotoSize(rawValue: size) ?? .medium,
328
341
  webhookUrl: webhookUrl,
329
342
  authToken: authToken,
330
- compress: compress.flatMap(PhotoCompression.init(rawValue:)),
331
- sound: sound
343
+ compress: PhotoCompression(rawValue: compress),
344
+ flash: flash,
345
+ sound: sound,
346
+ exposureTimeNs: exposureTimeNs
332
347
  )
333
348
  )
334
349
  }
@@ -449,7 +464,6 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
449
464
  AsyncFunction("setMicState") { (
450
465
  enabled: Bool,
451
466
  useGlassesMic: Bool?,
452
- bypassVad: Bool?,
453
467
  sendTranscript: Bool?,
454
468
  sendLc3Data: Bool?
455
469
  ) in
@@ -457,7 +471,6 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
457
471
  self.bluetoothSdk().setMicState(
458
472
  enabled: enabled,
459
473
  useGlassesMic: useGlassesMic ?? true,
460
- bypassVad: bypassVad ?? true,
461
474
  sendTranscript: sendTranscript ?? false,
462
475
  sendLc3Data: sendLc3Data ?? false
463
476
  )
@@ -569,6 +582,10 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
569
582
  )
570
583
  case let .touch(touch):
571
584
  sendEvent("touch_event", touch.values)
585
+ case let .voiceActivityDetectionStatus(status):
586
+ sendEvent("voice_activity_detection_status", status.values)
587
+ case let .speakingStatus(status):
588
+ sendEvent("speaking_status", status.values)
572
589
  case let .wifiStatus(status):
573
590
  sendEvent("wifi_status_change", status.values)
574
591
  case let .hotspotStatus(status):
@@ -45,8 +45,6 @@ Pod::Spec.new do |s|
45
45
  # Vendored frameworks
46
46
  s.vendored_frameworks = 'Packages/SherpaOnnx/sherpa-onnx.xcframework'
47
47
 
48
- # Resources (model files)
49
- s.resources = 'Packages/VAD/Silero/Model/*.onnx'
50
48
  s.resource_bundles = {
51
49
  'BluetoothSDKPrivacy' => ['Source/PrivacyInfo.xcprivacy']
52
50
  }
@@ -56,7 +54,6 @@ Pod::Spec.new do |s|
56
54
  "Packages/CoreObjC/**/*.{h,m,mm,hpp,cpp,c}",
57
55
  "Packages/SherpaOnnx/SherpaOnnx.swift",
58
56
  "Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/**/*.{h,hpp}",
59
- "Packages/VAD/**/*.swift",
60
57
  "Packages/libbz2/shim.h"
61
58
  ]
62
59
  native_source_files << "BluetoothSdkModule.swift" if include_expo_adapter
@@ -34,7 +34,7 @@ public struct MicPcmEvent: CustomStringConvertible {
34
34
  public let bitsPerSample: Int
35
35
  public let channels: Int
36
36
  public let encoding: String
37
- public let vadGated: Bool
37
+ public let voiceActivityDetectionEnabled: Bool
38
38
  public let values: [String: Any]
39
39
 
40
40
  public init(values: [String: Any]) {
@@ -43,7 +43,7 @@ public struct MicPcmEvent: CustomStringConvertible {
43
43
  let bitsPerSample = intValue(values["bitsPerSample"]) ?? Self.bitsPerSample
44
44
  let channels = intValue(values["channels"]) ?? Self.channels
45
45
  let encoding = values["encoding"] as? String ?? Self.encoding
46
- let vadGated = boolValue(values, "vadGated") ?? false
46
+ let voiceActivityDetectionEnabled = boolValue(values, "voiceActivityDetectionEnabled") ?? true
47
47
 
48
48
  var normalized = values
49
49
  normalized["type"] = "mic_pcm"
@@ -52,19 +52,19 @@ public struct MicPcmEvent: CustomStringConvertible {
52
52
  normalized["bitsPerSample"] = bitsPerSample
53
53
  normalized["channels"] = channels
54
54
  normalized["encoding"] = encoding
55
- normalized["vadGated"] = vadGated
55
+ normalized["voiceActivityDetectionEnabled"] = voiceActivityDetectionEnabled
56
56
 
57
57
  self.pcm = pcm
58
58
  self.sampleRate = sampleRate
59
59
  self.bitsPerSample = bitsPerSample
60
60
  self.channels = channels
61
61
  self.encoding = encoding
62
- self.vadGated = vadGated
62
+ self.voiceActivityDetectionEnabled = voiceActivityDetectionEnabled
63
63
  self.values = normalized
64
64
  }
65
65
 
66
66
  public var description: String {
67
- "MicPcmEvent(bytes: \(pcm.count), sampleRate: \(sampleRate), bitsPerSample: \(bitsPerSample), channels: \(channels), encoding: \(encoding), vadGated: \(vadGated))"
67
+ "MicPcmEvent(bytes: \(pcm.count), sampleRate: \(sampleRate), bitsPerSample: \(bitsPerSample), channels: \(channels), encoding: \(encoding), voiceActivityDetectionEnabled: \(voiceActivityDetectionEnabled))"
68
68
  }
69
69
  }
70
70
 
@@ -83,7 +83,7 @@ public struct MicLc3Event: CustomStringConvertible {
83
83
  public let frameSizeBytes: Int
84
84
  public let bitrate: Int
85
85
  public let packetizedFromGlasses: Bool
86
- public let vadGated: Bool
86
+ public let voiceActivityDetectionEnabled: Bool
87
87
  public let values: [String: Any]
88
88
 
89
89
  public init(values: [String: Any]) {
@@ -95,7 +95,7 @@ public struct MicLc3Event: CustomStringConvertible {
95
95
  let frameSizeBytes = intValue(values["frameSizeBytes"]) ?? Self.defaultFrameSizeBytes
96
96
  let bitrate = intValue(values["bitrate"]) ?? frameSizeBytes * 8 * (1000 / frameDurationMs)
97
97
  let packetizedFromGlasses = boolValue(values, "packetizedFromGlasses") ?? false
98
- let vadGated = boolValue(values, "vadGated") ?? false
98
+ let voiceActivityDetectionEnabled = boolValue(values, "voiceActivityDetectionEnabled") ?? true
99
99
 
100
100
  var normalized = values
101
101
  normalized["type"] = "mic_lc3"
@@ -107,7 +107,7 @@ public struct MicLc3Event: CustomStringConvertible {
107
107
  normalized["frameSizeBytes"] = frameSizeBytes
108
108
  normalized["bitrate"] = bitrate
109
109
  normalized["packetizedFromGlasses"] = packetizedFromGlasses
110
- normalized["vadGated"] = vadGated
110
+ normalized["voiceActivityDetectionEnabled"] = voiceActivityDetectionEnabled
111
111
 
112
112
  self.lc3 = lc3
113
113
  self.sampleRate = sampleRate
@@ -117,12 +117,12 @@ public struct MicLc3Event: CustomStringConvertible {
117
117
  self.frameSizeBytes = frameSizeBytes
118
118
  self.bitrate = bitrate
119
119
  self.packetizedFromGlasses = packetizedFromGlasses
120
- self.vadGated = vadGated
120
+ self.voiceActivityDetectionEnabled = voiceActivityDetectionEnabled
121
121
  self.values = normalized
122
122
  }
123
123
 
124
124
  public var description: String {
125
- "MicLc3Event(bytes: \(lc3.count), sampleRate: \(sampleRate), channels: \(channels), frameDurationMs: \(frameDurationMs), frameSizeBytes: \(frameSizeBytes), bitrate: \(bitrate), packetizedFromGlasses: \(packetizedFromGlasses), vadGated: \(vadGated))"
125
+ "MicLc3Event(bytes: \(lc3.count), sampleRate: \(sampleRate), channels: \(channels), frameDurationMs: \(frameDurationMs), frameSizeBytes: \(frameSizeBytes), bitrate: \(bitrate), packetizedFromGlasses: \(packetizedFromGlasses), voiceActivityDetectionEnabled: \(voiceActivityDetectionEnabled))"
126
126
  }
127
127
  }
128
128
 
@@ -89,18 +89,22 @@ class Bridge {
89
89
 
90
90
  @MainActor
91
91
  private static func micPcmEventBody(_ data: Data) -> [String: Any] {
92
- [
92
+ let voiceActivityDetectionEnabled =
93
+ DeviceStore.shared.get("glasses", "voiceActivityDetectionEnabled") as? Bool ?? true
94
+ return [
93
95
  "pcm": data,
94
96
  "sampleRate": micSampleRate,
95
97
  "bitsPerSample": pcmBitsPerSample,
96
98
  "channels": micChannels,
97
99
  "encoding": "pcm_s16le",
98
- "vadGated": isVadGated(),
100
+ "voiceActivityDetectionEnabled": voiceActivityDetectionEnabled,
99
101
  ]
100
102
  }
101
103
 
102
104
  @MainActor
103
105
  private static func micLc3EventBody(_ data: Data) -> [String: Any] {
106
+ let voiceActivityDetectionEnabled =
107
+ DeviceStore.shared.get("glasses", "voiceActivityDetectionEnabled") as? Bool ?? true
104
108
  let frameSizeBytes = DeviceStore.shared.get("bluetooth", "lc3_frame_size") as? Int ?? defaultLc3FrameSizeBytes
105
109
  return [
106
110
  "lc3": data,
@@ -111,23 +115,30 @@ class Bridge {
111
115
  "frameSizeBytes": frameSizeBytes,
112
116
  "bitrate": frameSizeBytes * 8 * (1000 / lc3FrameDurationMs),
113
117
  "packetizedFromGlasses": false,
114
- "vadGated": isVadGated(),
118
+ "voiceActivityDetectionEnabled": voiceActivityDetectionEnabled,
115
119
  ]
116
120
  }
117
121
 
118
- @MainActor
119
- private static func isVadGated() -> Bool {
120
- !(DeviceStore.shared.get("bluetooth", "bypass_vad") as? Bool ?? true)
121
- }
122
-
123
122
  static func saveSetting(_ key: String, _ value: Any) {
124
123
  let body = ["key": key, "value": value]
125
124
  Bridge.sendTypedMessage("save_setting", body: body)
126
125
  }
127
126
 
128
- static func sendVadEvent(_ isSpeaking: Bool) {
129
- let body: [String: Any] = ["status": isSpeaking]
130
- Bridge.sendTypedMessage("vad_status", body: body)
127
+ @MainActor
128
+ static func sendVoiceActivityDetectionStatus(_ enabled: Bool) {
129
+ DeviceStore.shared.set("glasses", "voiceActivityDetectionEnabled", enabled)
130
+ let body: [String: Any] = [
131
+ "voiceActivityDetectionEnabled": enabled,
132
+ ]
133
+ Bridge.sendTypedMessage("voice_activity_detection_status", body: body)
134
+ }
135
+
136
+ static func sendSpeakingStatus(_ speaking: Bool) {
137
+ let body: [String: Any] = [
138
+ "speaking": speaking,
139
+ "timestamp": Int(Date().timeIntervalSince1970 * 1000),
140
+ ]
141
+ Bridge.sendTypedMessage("speaking_status", body: body)
131
142
  }
132
143
 
133
144
  static func sendBatteryStatus(level: Int, charging: Bool) {
@@ -1,10 +1,5 @@
1
1
  import Foundation
2
2
 
3
- public enum GalleryMode {
4
- case auto
5
- case manual
6
- }
7
-
8
3
  public enum PhotoSize: String {
9
4
  case small
10
5
  case medium
@@ -35,12 +30,12 @@ public struct ButtonPhotoSettings {
35
30
  public struct ButtonVideoRecordingSettings {
36
31
  public let width: Int
37
32
  public let height: Int
38
- public let frameRate: Int
33
+ public let fps: Int
39
34
 
40
- public init(width: Int, height: Int, frameRate: Int) {
35
+ public init(width: Int, height: Int, fps: Int) {
41
36
  self.width = width
42
37
  self.height = height
43
- self.frameRate = frameRate
38
+ self.fps = fps
44
39
  }
45
40
  }
46
41
 
@@ -51,9 +46,9 @@ public enum CameraFov {
51
46
  var value: [String: Int] {
52
47
  switch self {
53
48
  case .standard:
54
- ["fov": 118, "roiPosition": 0]
49
+ ["fov": 118, "roi_position": 0]
55
50
  case .wide:
56
- ["fov": 118, "roiPosition": 0]
51
+ ["fov": 118, "roi_position": 0]
57
52
  }
58
53
  }
59
54
  }
@@ -65,7 +60,10 @@ public struct PhotoRequest {
65
60
  public let webhookUrl: String?
66
61
  public let authToken: String?
67
62
  public let compress: PhotoCompression?
63
+ public let flash: Bool
68
64
  public let sound: Bool
65
+ /// Sensor exposure time for this capture only (ns), or nil for auto exposure
66
+ public let exposureTimeNs: Double?
69
67
 
70
68
  public init(
71
69
  requestId: String,
@@ -74,7 +72,9 @@ public struct PhotoRequest {
74
72
  webhookUrl: String? = nil,
75
73
  authToken: String? = nil,
76
74
  compress: PhotoCompression? = nil,
77
- sound: Bool
75
+ flash: Bool = true,
76
+ sound: Bool,
77
+ exposureTimeNs: Double? = nil
78
78
  ) {
79
79
  self.requestId = requestId
80
80
  self.appId = appId
@@ -82,7 +82,9 @@ public struct PhotoRequest {
82
82
  self.webhookUrl = webhookUrl
83
83
  self.authToken = authToken
84
84
  self.compress = compress
85
+ self.flash = flash
85
86
  self.sound = sound
87
+ self.exposureTimeNs = exposureTimeNs
86
88
  }
87
89
  }
88
90
 
@@ -167,11 +167,6 @@ struct ViewState {
167
167
  set { DeviceStore.shared.apply("bluetooth", "sensing_enabled", newValue) }
168
168
  }
169
169
 
170
- private var bypassVad: Bool {
171
- get { DeviceStore.shared.get("bluetooth", "bypass_vad") as? Bool ?? true }
172
- set { DeviceStore.shared.apply("bluetooth", "bypass_vad", newValue) }
173
- }
174
-
175
170
  private var offlineCaptionsRunning: Bool {
176
171
  get { DeviceStore.shared.get("bluetooth", "offline_captions_running") as? Bool ?? false }
177
172
  set { DeviceStore.shared.apply("bluetooth", "offline_captions_running", newValue) }
@@ -285,11 +280,6 @@ struct ViewState {
285
280
  private var lastLc3Event: Date?
286
281
  private var micReinitTimer: Timer?
287
282
 
288
- // VAD:
289
- private var vad: SileroVADStrategy?
290
- private var vadBuffer = [Data]()
291
- private var isSpeaking = false
292
-
293
283
  /// STT:
294
284
  private var transcriber: SherpaOnnxTranscriber?
295
285
 
@@ -314,7 +304,6 @@ struct ViewState {
314
304
 
315
305
  override init() {
316
306
  Bridge.log("MAN: init()")
317
- vad = SileroVADStrategy()
318
307
  super.init()
319
308
 
320
309
  // Start memory monitoring (logs every 30s to help detect leaks)
@@ -336,16 +325,6 @@ struct ViewState {
336
325
  Bridge.log("SherpaOnnxTranscriber fully initialized")
337
326
  }
338
327
 
339
- Task {
340
- self.vad?.setup(
341
- sampleRate: .rate_16k,
342
- frameSize: .size_1024,
343
- quality: .normal,
344
- silenceTriggerDurationMs: 4000,
345
- speechTriggerDurationMs: 50
346
- )
347
- }
348
-
349
328
  // Initialize persistent LC3 converter for unified audio encoding
350
329
  lc3Converter = PcmConverter()
351
330
  Bridge.log("LC3 converter initialized for unified audio encoding")
@@ -362,13 +341,6 @@ struct ViewState {
362
341
 
363
342
  // MARK: - AUX Voice Data Handling
364
343
 
365
- private func checkSetVadStatus(speaking: Bool) {
366
- if speaking != isSpeaking {
367
- isSpeaking = speaking
368
- Bridge.sendVadEvent(isSpeaking)
369
- }
370
- }
371
-
372
344
  private func convertAndSendMicLc3(_ pcmData: Data) {
373
345
  guard let lc3Converter = lc3Converter else {
374
346
  Bridge.log("MAN: ERROR - LC3 converter not initialized but format is LC3")
@@ -393,23 +365,6 @@ struct ViewState {
393
365
  }
394
366
  }
395
367
 
396
- private func emptyVadBuffer() {
397
- // go through the buffer, popping from the first element in the array (FIFO):
398
- while !vadBuffer.isEmpty {
399
- let chunk = vadBuffer.removeFirst()
400
- handleSendingPcm(chunk)
401
- }
402
- }
403
-
404
- private func addToVadBuffer(_ chunk: Data) {
405
- let MAX_BUFFER_SIZE = 20
406
- vadBuffer.append(chunk)
407
- while vadBuffer.count > MAX_BUFFER_SIZE {
408
- // pop from the front of the array:
409
- vadBuffer.removeFirst()
410
- }
411
- }
412
-
413
368
  /**
414
369
  * Handle raw LC3 audio data from glasses.
415
370
  * Decodes the glasses LC3 to PCM, then forwards to handlePcm for processing.
@@ -432,59 +387,16 @@ struct ViewState {
432
387
  Bridge.log("MAN: Failed to decode glasses LC3 audio")
433
388
  return
434
389
  }
435
- // Forward to handlePcm which handles VAD and encoding
390
+ // Forward to handlePcm which handles SDK audio events and encoding.
436
391
  handlePcm(pcmData)
437
392
  }
438
393
 
439
394
  func handlePcm(_ pcmData: Data) {
440
- // handle incoming PCM data from the microphone manager and feed to the VAD:
441
- if bypassVad {
442
- handleSendingPcm(pcmData)
395
+ handleSendingPcm(pcmData)
443
396
 
444
- // Send PCM to local transcriber (always needs raw PCM)
445
- if shouldSendTranscript || offlineCaptionsRunning || localSttFallbackActive {
446
- transcriber?.acceptAudio(pcm16le: pcmData)
447
- }
448
- return
449
- }
450
-
451
- // feed PCM to the VAD:
452
- guard let vad = vad else {
453
- Bridge.log("VAD not initialized")
454
- return
455
- }
456
-
457
- // convert audioData to Int16 array for VAD:
458
- let pcmDataArray = pcmData.withUnsafeBytes { pointer -> [Int16] in
459
- Array(
460
- UnsafeBufferPointer(
461
- start: pointer.bindMemory(to: Int16.self).baseAddress,
462
- count: pointer.count / MemoryLayout<Int16>.stride
463
- )
464
- )
465
- }
466
-
467
- vad.checkVAD(pcm: pcmDataArray) { [weak self] state in
468
- guard let self = self else { return }
469
- Bridge.log("VAD State: \(state)")
470
- }
471
-
472
- let vadState = vad.currentState()
473
- if vadState == .speeching {
474
- checkSetVadStatus(speaking: true)
475
- // first send out whatever's in the vadBuffer (if there is anything):
476
- emptyVadBuffer()
477
-
478
- handleSendingPcm(pcmData)
479
-
480
- // Send PCM to local transcriber (always needs raw PCM)
481
- if shouldSendTranscript || offlineCaptionsRunning || localSttFallbackActive {
482
- transcriber?.acceptAudio(pcm16le: pcmData)
483
- }
484
- } else {
485
- checkSetVadStatus(speaking: false)
486
- // add to the vadBuffer (stores PCM for potential later sending):
487
- addToVadBuffer(pcmData)
397
+ // Send PCM to local transcriber.
398
+ if shouldSendTranscript || offlineCaptionsRunning || localSttFallbackActive {
399
+ transcriber?.acceptAudio(pcm16le: pcmData)
488
400
  }
489
401
  }
490
402
 
@@ -1013,6 +925,8 @@ struct ViewState {
1013
925
 
1014
926
  func handleDeviceDisconnected() {
1015
927
  Bridge.log("MAN: Device disconnected")
928
+ DeviceStore.shared.apply("glasses", "headUp", false)
929
+ DeviceStore.shared.apply("glasses", "voiceActivityDetectionEnabled", true)
1016
930
  // shouldSendBootingMessage = true // Reset for next first connect
1017
931
  }
1018
932
 
@@ -1280,14 +1194,22 @@ struct ViewState {
1280
1194
  _ webhookUrl: String?,
1281
1195
  _ authToken: String?,
1282
1196
  _ compress: String?,
1283
- _ sound: Bool
1197
+ _ flash: Bool,
1198
+ _ sound: Bool,
1199
+ exposureTimeNs: Double? = nil
1284
1200
  ) {
1285
1201
  Bridge.log(
1286
- "MAN: requestPhoto: \(requestId), \(appId), \(webhookUrl), size=\(size), compress=\(compress ?? "none"), flash=true, sound=\(sound)"
1202
+ "MAN: PHOTO PIPELINE [4/6] DeviceManager.requestPhoto requestId=\(requestId) appId=\(appId) webhookUrl=\(webhookUrl ?? "nil") size=\(size) compress=\(compress ?? "none") flash=\(flash) sound=\(sound) exposureTimeNs=\(exposureTimeNs.map { String($0) } ?? "nil") sgc=\(sgc != nil ? String(describing: type(of: sgc!)) : "null")"
1287
1203
  )
1288
- sgc?.requestPhoto(
1204
+ guard let sgc else {
1205
+ Bridge.log(
1206
+ "MAN: PHOTO PIPELINE — sgc is null (glasses not connected); dropping requestId=\(requestId)"
1207
+ )
1208
+ return
1209
+ }
1210
+ sgc.requestPhoto(
1289
1211
  requestId, appId: appId, size: size, webhookUrl: webhookUrl, authToken: authToken,
1290
- compress: compress, flash: true, sound: sound
1212
+ compress: compress, flash: flash, sound: sound, exposureTimeNs: exposureTimeNs
1291
1213
  )
1292
1214
  }
1293
1215
 
@@ -1393,6 +1315,7 @@ struct ViewState {
1393
1315
  DeviceStore.shared.apply("glasses", "deviceModel", "")
1394
1316
  DeviceStore.shared.apply("glasses", "fullyBooted", false)
1395
1317
  DeviceStore.shared.apply("glasses", "connected", false)
1318
+ DeviceStore.shared.apply("glasses", "voiceActivityDetectionEnabled", true)
1396
1319
  // disconnect the controller as well:
1397
1320
  searchingController = false
1398
1321
  DeviceStore.shared.apply("glasses", "controllerConnected", false)