@mentra/bluetooth-sdk 0.1.8 → 0.1.10
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.
- package/README.md +116 -18
- package/android/build.gradle +289 -16
- package/android/gradle.properties.example +9 -0
- package/android/lc3Lib/build.gradle +95 -6
- package/android/settings.gradle +3 -0
- package/android/silero/LICENSE.md +17 -0
- package/android/silero/build.gradle +42 -0
- package/android/silero/proguard-rules.pro +24 -0
- package/android/silero/src/androidTest/assets/hello.wav +0 -0
- package/android/silero/src/androidTest/java/com/konovalov/vad/silero/VadSileroTest.kt +79 -0
- package/android/silero/src/main/assets/LICENSE +21 -0
- package/android/silero/src/main/assets/silero_vad.onnx +0 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/Vad.kt +160 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/VadSilero.kt +449 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/FrameSize.kt +15 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/Mode.kt +14 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/SampleRate.kt +12 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/AudioUtils.kt +47 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/TensorMap.kt +41 -0
- package/android/silero/src/test/java/com/konovalov/vad/silero/utils/AudioUtilsTest.kt +44 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +6 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +165 -27
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +89 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +189 -16
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +22 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +787 -85
- package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +6 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +123 -10
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +12 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +8 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +33 -17
- package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +101 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +8 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +399 -74
- package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/MentraPhotoReceiverModule.kt +100 -17
- package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +17 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +569 -220
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +271 -66
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +176 -104
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +12753 -27036
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +54 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +16 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +60 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +12 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/STTTools.kt +87 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +13 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +229 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +180 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/AvifExifStripper.java +573 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +488 -50
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +18 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +64 -18
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +48 -39
- package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +23 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/AvifExifStripperTest.java +30 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +98 -0
- package/android/src/test/resources/avif_with_exif.avif +0 -0
- package/build/BluetoothSdk.types.d.ts +241 -43
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js +11 -10
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +36 -23
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +30 -52
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/photoRequestPayload.d.ts +4 -0
- package/build/_private/photoRequestPayload.d.ts.map +1 -0
- package/build/_private/photoRequestPayload.js +25 -0
- package/build/_private/photoRequestPayload.js.map +1 -0
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +23 -0
- package/build/index.js.map +1 -1
- package/build/react/useMentraBluetooth.d.ts +2 -2
- package/build/react/useMentraBluetooth.d.ts.map +1 -1
- package/build/react/useMentraBluetooth.js +3 -3
- package/build/react/useMentraBluetooth.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +174 -70
- package/ios/LocalPhotoUploadServer.swift +370 -105
- package/ios/MentraBluetoothSDK.podspec +3 -1
- package/ios/MentraPhotoReceiverModule.swift +62 -10
- package/ios/Packages/CoreObjC/include/PcmConverter.h +22 -0
- package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +668 -26
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +13 -9
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4551 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +1745 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/c-api.h +4551 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +1745 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
- package/ios/Source/BluetoothSdkDefaults.swift +6 -0
- package/ios/Source/Bridge.swift +58 -19
- package/ios/Source/DeviceManager.swift +149 -17
- package/ios/Source/DeviceStore.swift +16 -6
- package/ios/Source/MentraBluetoothSDK.swift +803 -47
- package/ios/Source/ObservableStore.swift +8 -2
- package/ios/Source/{Audio → audio}/AudioModels.swift +9 -7
- package/ios/Source/{Camera → camera}/CameraModels.swift +225 -16
- package/ios/Source/controllers/ControllerManager.swift +4 -3
- package/ios/Source/controllers/R1.swift +3 -3
- package/ios/Source/{Events → events}/BluetoothEvents.swift +183 -1
- package/ios/Source/{Internal → internal}/ValueParsing.swift +10 -1
- package/ios/Source/{Requests → requests}/DisplayRequests.swift +28 -4
- package/ios/Source/services/PhoneMic.swift +1 -0
- package/ios/Source/sgcs/G1.swift +2 -2
- package/ios/Source/sgcs/G2.swift +645 -403
- package/ios/Source/sgcs/Mach1.swift +2 -2
- package/ios/Source/sgcs/MentraLive.swift +812 -319
- package/ios/Source/sgcs/MentraNex.swift +412 -182
- package/ios/Source/sgcs/SGCManager.swift +57 -3
- package/ios/Source/sgcs/Simulated.swift +7 -3
- package/ios/Source/sgcs/mentraos_ble.pb.swift +3073 -3134
- package/ios/Source/status/DeviceStatus.swift +974 -0
- package/ios/Source/{Status → status}/WifiHotspotStatus.swift +4 -4
- package/ios/Source/{Streaming → streaming}/StreamModels.swift +24 -9
- package/ios/Source/stt/STTTools.swift +27 -12
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +31 -8
- package/ios/Source/tts/TTSTools.swift +164 -0
- package/ios/Source/utils/JSCExperiment.swift +7 -7
- package/ios/Source/utils/MessageChunkReassembler.swift +20 -1
- package/ios/Source/utils/MessageChunker.swift +78 -21
- package/ios/Source/utils/TarBz2Extractor.swift +53 -2
- package/package.json +1 -1
- package/plugin/build/withAndroid.js +68 -4
- package/src/BluetoothSdk.types.ts +331 -59
- package/src/_private/BluetoothSdkModule.ts +113 -102
- package/src/_private/photoRequestPayload.ts +28 -0
- package/src/index.ts +58 -15
- package/src/react/useMentraBluetooth.ts +6 -4
- package/android/.project +0 -28
- package/android/lc3Lib/.project +0 -28
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/cargs.h +0 -162
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/c-api.h +0 -1852
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/cxx-api.h +0 -674
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/sherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/sherpa-onnx.a +0 -0
- package/ios/Source/Status/DeviceStatus.swift +0 -471
- /package/ios/Source/{Connection → connection}/ScanSession.swift +0 -0
- /package/ios/Source/{Errors → errors}/BluetoothError.swift +0 -0
- /package/ios/Source/{Internal → internal}/BluetoothAvailability.swift +0 -0
- /package/ios/Source/{Status → status}/RuntimeState.swift +0 -0
- /package/ios/Source/{Types → types}/DeviceModels.swift +0 -0
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@ The package includes:
|
|
|
7
7
|
- A React Native / Expo module API exposed as `BluetoothSdk`.
|
|
8
8
|
- React hooks under `@mentra/bluetooth-sdk/react` for common scan,
|
|
9
9
|
connection, status, and event lifecycles.
|
|
10
|
-
- Native Android code published as `com.
|
|
11
|
-
- Native iOS code
|
|
10
|
+
- Native Android code published as `com.mentraglass:bluetooth-sdk`.
|
|
11
|
+
- Native iOS code available as the `MentraBluetoothSDK` Swift package.
|
|
12
12
|
- An Expo config plugin that wires the native dependencies into generated Android and iOS projects.
|
|
13
13
|
|
|
14
14
|
Use a development build or production native build. Expo Go cannot load this package because the SDK contains native code.
|
|
@@ -129,7 +129,8 @@ if (!device) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
await BluetoothSdk.connect(device)
|
|
132
|
-
await BluetoothSdk.requestVersionInfo()
|
|
132
|
+
const versionInfo = await BluetoothSdk.requestVersionInfo()
|
|
133
|
+
console.log(versionInfo.buildNumber)
|
|
133
134
|
```
|
|
134
135
|
|
|
135
136
|
In multi-device environments, present an explicit picker instead of
|
|
@@ -230,19 +231,27 @@ await BluetoothSdk.clearDisplay()
|
|
|
230
231
|
await BluetoothSdk.showDashboard()
|
|
231
232
|
await BluetoothSdk.setDashboardPosition(4, 2)
|
|
232
233
|
|
|
233
|
-
await BluetoothSdk.requestWifiScan()
|
|
234
|
-
|
|
235
|
-
await BluetoothSdk.forgetWifiNetwork('Office WiFi')
|
|
236
|
-
await BluetoothSdk.setHotspotState(true)
|
|
234
|
+
const networks = await BluetoothSdk.requestWifiScan()
|
|
235
|
+
console.log(networks.map((network) => network.ssid))
|
|
237
236
|
|
|
238
|
-
await BluetoothSdk.
|
|
237
|
+
const wifiStatus = await BluetoothSdk.sendWifiCredentials('Office WiFi', 'secret')
|
|
238
|
+
console.log(wifiStatus.state)
|
|
239
|
+
|
|
240
|
+
const forgetStatus = await BluetoothSdk.forgetWifiNetwork('Office WiFi')
|
|
241
|
+
console.log(forgetStatus.state)
|
|
242
|
+
|
|
243
|
+
const hotspotStatus = await BluetoothSdk.setHotspotState(true)
|
|
244
|
+
console.log(hotspotStatus.state)
|
|
245
|
+
|
|
246
|
+
const galleryAck = await BluetoothSdk.setGalleryModeEnabled(true)
|
|
247
|
+
console.log(galleryAck.status)
|
|
239
248
|
await BluetoothSdk.setGalleryModeEnabled(false)
|
|
240
249
|
|
|
241
250
|
await BluetoothSdk.setPreferredMic('auto')
|
|
242
251
|
await BluetoothSdk.setMicState(true)
|
|
243
252
|
await BluetoothSdk.setOwnAppAudioPlaying(false)
|
|
244
253
|
|
|
245
|
-
await BluetoothSdk.rgbLedControl(
|
|
254
|
+
const ledAck = await BluetoothSdk.rgbLedControl(
|
|
246
255
|
`led-${Date.now()}`,
|
|
247
256
|
'com.example.app',
|
|
248
257
|
'on',
|
|
@@ -251,14 +260,59 @@ await BluetoothSdk.rgbLedControl(
|
|
|
251
260
|
500,
|
|
252
261
|
3,
|
|
253
262
|
)
|
|
263
|
+
console.log(ledAck.state)
|
|
254
264
|
```
|
|
255
265
|
|
|
256
|
-
`
|
|
266
|
+
Settings commands that return `SettingsAckSuccessEvent` reject when the ASG reports an error ack. The SDK updates its local settings store only after that ASG ack resolves successfully, so observed SDK state reflects the acknowledged glasses state rather than a queued request. Raw `settings_ack` listener events still use `SettingsAckEvent` because they can include both success and failure statuses. `rgbLedControl(...)` resolves from a successful ASG `rgb_led_control_response` and rejects when the ASG reports `state: "error"`; raw `settings_ack` and `rgb_led_control_response` events remain available through listeners.
|
|
267
|
+
|
|
268
|
+
WiFi, hotspot, and version-info commands resolve from the ASG response path, not local dispatch:
|
|
269
|
+
`requestWifiScan()` resolves from the ASG `wifi_scan_result` completion response with the updated scan list, including `[]` when no networks are found. Intermediate `wifi_scan_result` events can arrive with `scanComplete: false` while the glasses stream discovered networks; the final event uses `scanComplete: true`. `sendWifiCredentials()` resolves when the requested SSID is connected, `forgetWifiNetwork()` resolves when that SSID is no longer connected, `setHotspotState()` resolves when the requested hotspot state is reported, and `requestVersionInfo()` resolves from the ASG `version_info` response instead of local store changes.
|
|
270
|
+
|
|
271
|
+
React Native narrows returned values to success shapes where the raw listener event can also report errors:
|
|
272
|
+
|
|
273
|
+
| API | Returned Value After `await` | Error Path |
|
|
274
|
+
| --- | --- | --- |
|
|
275
|
+
| `requestPhoto(...)` | Terminal `PhotoSuccessResponseEvent` with `state: "success"` after capture and delivery finish. `uploadUrl` is always present; webhook JSON metadata such as `photoUrl`, `statusUrl`, `contentType`, or `fileSizeBytes` is included when the receiver returns it. | Rejects when raw `photo_response.state === "error"`, the SDK cannot send the command, or the terminal photo response times out. |
|
|
276
|
+
| `startVideoRecording(...)` | `VideoRecordingStartedStatusEvent` with `success: true` and `status: "recording_started"`. | Rejects on `success: false` statuses such as `already_recording`, send failure, or timeout. |
|
|
277
|
+
| `stopVideoRecording(...)` | `VideoRecordingStoppedStatusEvent` with `success: true` and `status: "recording_stopped"`. | Rejects on `success: false` statuses such as `not_recording`, send failure, or timeout. |
|
|
278
|
+
| `rgbLedControl(...)` | `RgbLedControlSuccessResponseEvent` with `state: "success"`. | Rejects when raw `rgb_led_control_response.state === "error"` or the response times out. |
|
|
279
|
+
| `checkForOtaUpdate()` / `retryOtaVersionCheck()` | `OtaQueryResult`, either `ota_update_available` or current `ota_status`. | Rejects on command transport timeout/failure. A returned `ota_status.status === "failed"` is glasses OTA state, not a command rejection. |
|
|
280
|
+
|
|
281
|
+
Android and iOS async APIs use `BluetoothException` / `BluetoothError` for the same error paths. Their returned event structs are the successful response in normal `try`/`await` code, while raw listener/delegate events still include both success and error payloads.
|
|
282
|
+
|
|
283
|
+
`setMicState(true)` defaults to continuous microphone PCM from the glasses. The SDK does not apply phone-side Voice Activity Detection gating to microphone audio events. Glasses-side Voice Activity Detection is disabled by default for public SDK consumers; use `setVoiceActivityDetectionEnabled(true)` when you want supported glasses to gate microphone audio and emit live speaking status. `voice_activity_detection_status` reports whether glasses-side Voice Activity Detection is enabled, and `speaking_status` reports speaking/not-speaking when supported. Microphone events include the latest `voiceActivityDetectionEnabled` value.
|
|
284
|
+
|
|
285
|
+
## OTA Updates
|
|
286
|
+
|
|
287
|
+
Mentra Live firmware owns the OTA flow. The SDK mirrors the MentraOS app commands and events:
|
|
288
|
+
|
|
289
|
+
- `checkForOtaUpdate()` sends `ota_query_status` and resolves with the ASG response (`ota_update_available` or the current `ota_status`).
|
|
290
|
+
- `startOtaUpdate()` sends `ota_start` and resolves with the ASG start ack after your app presents the update and the user accepts it.
|
|
291
|
+
- `retryOtaVersionCheck()` sends `ota_retry_version_check` and resolves with the same ASG response shape as `checkForOtaUpdate()`; use it only after fixing a known clock-skew/TLS failure.
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
import BluetoothSdk from '@mentra/bluetooth-sdk'
|
|
295
|
+
|
|
296
|
+
BluetoothSdk.addListener('ota_status', (event) => {
|
|
297
|
+
console.log(`OTA ${event.status}: ${event.overall_percent}%`)
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
const ota = await BluetoothSdk.checkForOtaUpdate()
|
|
301
|
+
if (ota.type === 'ota_update_available') {
|
|
302
|
+
const userAccepted = await promptUserToInstallUpdate(ota) // your app's UI
|
|
303
|
+
if (userAccepted) {
|
|
304
|
+
const startAck = await BluetoothSdk.startOtaUpdate()
|
|
305
|
+
console.log('OTA start acknowledged', startAck.timestamp)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
OTA requires Mentra Live glasses firmware that supports the ASG OTA protocol and network access from the glasses. During install, normal BLE traffic can be interrupted and the glasses may restart; keep the app connected and avoid sending unrelated commands until `ota_status.status` is `complete` or `failed`.
|
|
257
311
|
|
|
258
312
|
## Photo Upload
|
|
259
313
|
|
|
260
314
|
```ts
|
|
261
|
-
await BluetoothSdk.requestPhoto({
|
|
315
|
+
const photo = await BluetoothSdk.requestPhoto({
|
|
262
316
|
requestId: `photo-${Date.now()}`,
|
|
263
317
|
appId: 'com.example.app',
|
|
264
318
|
size: 'medium',
|
|
@@ -269,13 +323,16 @@ await BluetoothSdk.requestPhoto({
|
|
|
269
323
|
exposureTimeNs: null, // auto exposure; pass a positive nanosecond value for manual exposure
|
|
270
324
|
iso: null, // auto ISO; pass a positive ISO only with manual exposureTimeNs
|
|
271
325
|
})
|
|
326
|
+
console.log('photo delivered', photo.photoUrl ?? photo.uploadUrl, photo.fileSizeBytes)
|
|
272
327
|
```
|
|
273
328
|
|
|
274
|
-
The webhook should accept multipart form data with a `photo` file and `requestId`. If `authToken` is provided, the uploader adds `Authorization: Bearer <token>`. The camera light is always enabled for photo capture.
|
|
329
|
+
`requestPhoto(...)` resolves only after the full photo action reaches terminal success: capture completed and the photo was delivered to the webhook, either directly from the glasses over Wi-Fi or through the phone's Bluetooth fallback relay. It rejects if the ASG reports `state: "error"`, if phone-side fallback upload fails, if the SDK cannot send the command, or if no terminal `photo_response` arrives before the command timeout. Use `photo_status` for intermediate stages such as `accepted`, `configuring`, `capturing`, `captured`, `uploading`, `ble_fallback_compression`, `ready_for_transfer`, and `transferring`; `photo_status` is progress, while `photo_response` is terminal success/error. The raw `photo_response` event stream still includes both success and error events for subscribers. The webhook should accept multipart form data with a `photo` file and `requestId`. If `authToken` is provided, the uploader adds `Authorization: Bearer <token>`. The camera light is always enabled for photo capture.
|
|
275
330
|
|
|
276
331
|
For one-shot manual capture tuning, pass `exposureTimeNs` and `iso` together. `exposureTimeNs` is sensor exposure time in nanoseconds; `iso` is sensor ISO. If `exposureTimeNs` is omitted, `null`, invalid, or unsupported by the connected glasses, the camera uses auto exposure and ignores `iso`.
|
|
277
332
|
|
|
278
|
-
Use `setCameraFov({fov, roiPosition})` to configure Mentra Live camera field of view and crop position. FOV is clamped to
|
|
333
|
+
Use `setCameraFov({fov, roiPosition})` to configure Mentra Live camera field of view and crop position. FOV is clamped to 62-118 degrees; ROI position is `"center"`, `"bottom"`, or `"top"`. You can also call `setCameraFov({preset: "narrow" | "standard" | "wide"})`; presets map to 82, 102, and 118 degrees with center ROI. The returned `CameraFovResult` resolves only after the ASG client reports that the setting was applied to camera hardware after the restart cooldown, and the promise rejects if the glasses report an error, persist the setting without hardware application, or time out. Raw `settings_ack` events remain available through `addListener("settings_ack", ...)` for diagnostic fields such as `hardwareApplied`. Treat FOV as a framing/ROI control; output resolution and effective detail can vary by capture path, firmware, and camera mode.
|
|
334
|
+
|
|
335
|
+
`startVideoRecording(...)` and `stopVideoRecording(...)` resolve from successful ASG `video_recording_status` events only when the status matches the requested operation (`recording_started` or `recording_stopped`) and reject when the ASG reports `success: false`. Raw `video_recording_status` events remain available through listeners, including `recording_status` query events with `data.recording`.
|
|
279
336
|
|
|
280
337
|
## Streaming
|
|
281
338
|
|
|
@@ -292,7 +349,7 @@ await BluetoothSdk.startStream({
|
|
|
292
349
|
await BluetoothSdk.stopStream()
|
|
293
350
|
```
|
|
294
351
|
|
|
295
|
-
Use `rtmp://` or `rtmps://` for RTMP, `srt://` for SRT, and `http://` or `https://` for WHIP/WebRTC ingest. The SDK sends stream keep-alives automatically while streaming and reports keep-alive failures through `stream_status`. The camera light is always enabled while streaming.
|
|
352
|
+
Use `rtmp://` or `rtmps://` for RTMP, `srt://` for SRT, and `http://` or `https://` for WHIP/WebRTC ingest. `startStream()` resolves with the correlated `stream_status` event once the glasses report `status: "streaming"`; `stopStream()` resolves when the glasses report `status: "stopped"` or confirms the stream was already stopped / not streaming. `stopStream()` returns a normalized stopped event for that already-stopped case. Stream starts reject if the glasses report an error before streaming; stream stops reject for real stop errors, send failure, another stop in flight, or timeout. The SDK sends stream keep-alives automatically while streaming and reports keep-alive failures through `stream_status`. The camera light is always enabled while streaming.
|
|
296
353
|
`stream_status` events may include `resolvedConfig`, which reports the effective transport, video, and audio settings after glasses defaults, clamps, and camera preflight.
|
|
297
354
|
|
|
298
355
|
## Events
|
|
@@ -305,8 +362,7 @@ import {useBluetoothEvent} from '@mentra/bluetooth-sdk/react'
|
|
|
305
362
|
export function HardwareEventLogger() {
|
|
306
363
|
useBluetoothEvent('button_press', (event) => console.log(event))
|
|
307
364
|
useBluetoothEvent('touch_event', (event) => console.log(event))
|
|
308
|
-
useBluetoothEvent('photo_status', (event) => console.log(event.status, event.resolvedConfig))
|
|
309
|
-
useBluetoothEvent('photo_response', (event) => console.log(event))
|
|
365
|
+
useBluetoothEvent('photo_status', (event) => console.log(event.status, event.resolvedConfig, event.captureMetadata))
|
|
310
366
|
useBluetoothEvent('stream_status', (event) => console.log(event))
|
|
311
367
|
useBluetoothEvent('speaking_status', (event) => console.log(event.speaking))
|
|
312
368
|
useBluetoothEvent('mic_pcm', (event) => {
|
|
@@ -320,9 +376,19 @@ export function HardwareEventLogger() {
|
|
|
320
376
|
|
|
321
377
|
For non-React modules, `BluetoothSdk.addListener(...)` is the low-level subscription API. Keep the returned subscription and call `remove()` when the listener is no longer needed.
|
|
322
378
|
|
|
323
|
-
Common event names include `button_press`, `touch_event`, `head_up`, `battery_status`, `wifi_status_change`, `hotspot_status_change`, `photo_status`, `photo_response`, `gallery_status`, `stream_status`, `mic_pcm`, `mic_lc3`, `local_transcription`, `rgb_led_control_response`, `audio_connected`, `audio_disconnected`, and `log`.
|
|
379
|
+
Common event names include `button_press`, `touch_event`, `head_up`, `battery_status`, `wifi_status_change`, `wifi_scan_result`, `hotspot_status_change`, `photo_status`, `photo_response`, `gallery_status`, `settings_ack`, `version_info`, `stream_status`, `ota_update_available`, `ota_start_ack`, `ota_status`, `mic_pcm`, `mic_lc3`, `local_transcription`, `rgb_led_control_response`, `audio_connected`, `audio_disconnected`, and `log`.
|
|
380
|
+
|
|
381
|
+
React Native event payload fields usually use camelCase. OTA events intentionally mirror the glasses firmware field names, such as `overall_percent` and `version_name`. For example, `touch_event` includes `gestureName`, `wifi_scan_result` includes `networks` and `scanComplete`, `version_info` matches the `requestVersionInfo()` result shape, `photo_response` success includes `uploadUrl` and may include webhook-returned `photoUrl`, `statusUrl`, `contentType`, and `fileSizeBytes`, and `gallery_status` includes `hasContent`, `cameraBusy`, and optional `cameraBusyReason`. `photo_status` reports intermediate photo states such as `accepted`, `queued`, `configuring`, `capturing`, `captured`, `compressing`, `ble_fallback_compression`, `uploading`, `ready_for_transfer`, `transferring`, and `failed`; the `configuring` event includes `resolvedConfig` with the effective JPEG dimensions, quality, requested size, transfer method, compression, and manual exposure fields when present. The `capturing` event may include `requestedCaptureConfig` and `meteredPreview`; the `captured` event may include `captureMetadata` with the HAL-applied exposure, ISO, frame duration, and AE state. `mic_pcm` includes `sampleRate`, `bitsPerSample`, `channels`, and `encoding`; `mic_lc3` includes `sampleRate`, `channels`, `encoding`, `frameDurationMs`, `frameSizeBytes`, `bitrate`, and `packetizedFromGlasses`.
|
|
382
|
+
|
|
383
|
+
Photo status metadata is tied to the capture stage where the glasses know it:
|
|
384
|
+
|
|
385
|
+
| Status | Optional metadata | Meaning |
|
|
386
|
+
| --- | --- | --- |
|
|
387
|
+
| `configuring` | `resolvedConfig` | Effective JPEG size, quality, requested size, source, transfer method, compression, and manual capture settings when present. |
|
|
388
|
+
| `capturing` | `requestedCaptureConfig`, `meteredPreview` | Camera2 still request about to be submitted, plus the latest auto-exposure preview estimate before capture. |
|
|
389
|
+
| `captured` | `captureMetadata` | HAL-applied still capture result, including actual exposure, ISO, frame duration, AE state, and related camera modes when available. |
|
|
324
390
|
|
|
325
|
-
|
|
391
|
+
Upload and transfer statuses such as `uploading`, `compressing`, `ble_fallback_compression`, `ready_for_transfer`, and `transferring` describe transport progress only and do not carry capture metadata. `ble_fallback_compression` means the direct Wi-Fi/webhook upload failed and the glasses are compressing the already-captured photo for Bluetooth fallback delivery. Local action-button photos emitted by the glasses use the same `photo_status` event shape when the phone SDK is connected; those events use `resolvedConfig.source: "button"` and `resolvedConfig.transferMethod: "local"`.
|
|
326
392
|
|
|
327
393
|
Only documented imports are supported for app developers. Undocumented package subpaths or symbols with a leading underscore can change without notice.
|
|
328
394
|
|
|
@@ -337,6 +403,38 @@ MENTRA_BLUETOOTH_SDK_PACKAGE_PATH=/path/to/MentraOS/mobile/modules/bluetooth-sdk
|
|
|
337
403
|
|
|
338
404
|
Use `bunx expo run:android` for Android. Keep local paths in your shell or CI environment, not in committed app config.
|
|
339
405
|
|
|
406
|
+
For bare native iOS apps, use the public SwiftPM repository:
|
|
407
|
+
|
|
408
|
+
```text
|
|
409
|
+
https://github.com/Mentra-Community/mentra-bluetooth-sdk-ios.git
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Add the `MentraBluetoothSDK` product to your app target at version `0.1.7` or newer.
|
|
413
|
+
|
|
414
|
+
For local SDK development, add this package folder directly in Xcode:
|
|
415
|
+
|
|
416
|
+
```text
|
|
417
|
+
/path/to/MentraOS/mobile/modules/bluetooth-sdk
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
The core Swift package intentionally excludes optional local STT, Nex/SwiftProtobuf, Vuzix/Ultralite, and tar.bz2 extraction code paths.
|
|
421
|
+
|
|
422
|
+
Maintainers publishing the public SwiftPM mirror should follow
|
|
423
|
+
[RELEASING_IOS_SPM.md](./RELEASING_IOS_SPM.md).
|
|
424
|
+
|
|
425
|
+
## Android Maven Publishing
|
|
426
|
+
|
|
427
|
+
Maintainers publishing the native Android artifacts to Maven Central should
|
|
428
|
+
follow [RELEASING_ANDROID_MAVEN.md](./RELEASING_ANDROID_MAVEN.md).
|
|
429
|
+
|
|
430
|
+
Public Maven publishing uses a public SDK mode that omits MentraOS-only Android
|
|
431
|
+
integrations from the artifact metadata while normal MentraOS app builds keep
|
|
432
|
+
those integrations enabled.
|
|
433
|
+
|
|
434
|
+
Use `android/gradle.properties.example` as the template for Sonatype Central and
|
|
435
|
+
GPG signing properties. Put real values in `~/.gradle/gradle.properties` or CI
|
|
436
|
+
secrets, not in the repository.
|
|
437
|
+
|
|
340
438
|
## Starter Example App
|
|
341
439
|
|
|
342
440
|
The [Mentra Bluetooth SDK Starter Kit](https://github.com/Mentra-Community/Mentra-Bluetooth-SDK-Starter-Kit) includes starter example apps for Android, iOS, and React Native / Expo. The React Native starter demonstrates scan/connect, display, camera photo upload, RTMP/SRT/WebRTC streaming, Wi-Fi/hotspot, microphone PCM, RGB LED, gallery mode, and console event inspection.
|
package/android/build.gradle
CHANGED
|
@@ -1,13 +1,185 @@
|
|
|
1
1
|
import groovy.json.JsonSlurper
|
|
2
|
+
import java.security.MessageDigest
|
|
2
3
|
|
|
3
4
|
apply plugin: 'com.android.library'
|
|
4
5
|
apply plugin: 'maven-publish'
|
|
5
6
|
apply plugin: 'signing'
|
|
6
7
|
|
|
7
8
|
def packageJson = new JsonSlurper().parse(file("../package.json"))
|
|
9
|
+
def mavenGroupId = 'com.mentraglass'
|
|
10
|
+
def mavenPublishVersion = (findProperty('mentraMavenVersion') ?: System.getenv('MENTRA_MAVEN_VERSION') ?: packageJson.version).toString()
|
|
11
|
+
def publicMavenSdk = (findProperty('mentraPublicSdk') ?: System.getenv('MENTRA_PUBLIC_SDK') ?: 'false').toString().toBoolean()
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
// Download Sherpa-ONNX Android AAR from the upstream GitHub release and stage
|
|
14
|
+
// it as a Maven artifact in a tiny on-disk repository under libs/maven/.
|
|
15
|
+
// Why this shape:
|
|
16
|
+
// - Jitpack stopped publishing for v1.13.2+ (their build step fails because
|
|
17
|
+
// the AAR asset isn't present when its install-file step runs).
|
|
18
|
+
// - We can't just `implementation files("…aar")` because AGP refuses local
|
|
19
|
+
// .aar deps from library modules ("Direct local .aar file dependencies
|
|
20
|
+
// are not supported when building an AAR"). Maven-coord deps are fine.
|
|
21
|
+
// The SHA-256 is pinned (matches the upstream checksum.txt for v1.13.2) so a
|
|
22
|
+
// tampered or partially-written artifact is rejected before being used.
|
|
23
|
+
def sherpaOnnxGroup = "com.k2fsa.sherpa.onnx"
|
|
24
|
+
def sherpaOnnxArtifact = "sherpa-onnx"
|
|
25
|
+
def sherpaOnnxVersion = "1.13.2"
|
|
26
|
+
def sherpaOnnxSha256 = "aa5505c0ec4f8bdaee5f214a64ba3012be64f2aecc022e82a64f33392b8dd245"
|
|
27
|
+
|
|
28
|
+
def sherpaOnnxMavenRoot = file("libs/maven")
|
|
29
|
+
def sherpaOnnxRepoDir = new File(
|
|
30
|
+
sherpaOnnxMavenRoot,
|
|
31
|
+
"${sherpaOnnxGroup.replace('.', '/')}/${sherpaOnnxArtifact}/${sherpaOnnxVersion}"
|
|
32
|
+
)
|
|
33
|
+
def sherpaOnnxAar = new File(sherpaOnnxRepoDir, "${sherpaOnnxArtifact}-${sherpaOnnxVersion}.aar")
|
|
34
|
+
def sherpaOnnxPom = new File(sherpaOnnxRepoDir, "${sherpaOnnxArtifact}-${sherpaOnnxVersion}.pom")
|
|
35
|
+
|
|
36
|
+
def sha256OfFile = { File f ->
|
|
37
|
+
def digest = MessageDigest.getInstance("SHA-256")
|
|
38
|
+
f.withInputStream { input ->
|
|
39
|
+
def buf = new byte[64 * 1024]
|
|
40
|
+
int n
|
|
41
|
+
while ((n = input.read(buf)) != -1) {
|
|
42
|
+
digest.update(buf, 0, n)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
digest.digest().collect { String.format("%02x", it & 0xff) }.join("")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// If a stale AAR exists with the wrong hash (e.g. from a previous failed
|
|
49
|
+
// download or a version bump), drop it so we re-fetch.
|
|
50
|
+
if (sherpaOnnxAar.exists() && sha256OfFile(sherpaOnnxAar) != sherpaOnnxSha256) {
|
|
51
|
+
logger.lifecycle("Sherpa-ONNX AAR checksum mismatch on disk; redownloading.")
|
|
52
|
+
if (!sherpaOnnxAar.delete()) {
|
|
53
|
+
throw new GradleException(
|
|
54
|
+
"Sherpa-ONNX AAR at ${sherpaOnnxAar.absolutePath} has a mismatched SHA-256 " +
|
|
55
|
+
"but could not be deleted. Refusing to continue with a potentially tampered artifact."
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!sherpaOnnxAar.exists()) {
|
|
61
|
+
sherpaOnnxRepoDir.mkdirs()
|
|
62
|
+
def sherpaOnnxUrl = "https://github.com/k2-fsa/sherpa-onnx/releases/download/v${sherpaOnnxVersion}/${sherpaOnnxArtifact}-${sherpaOnnxVersion}.aar"
|
|
63
|
+
logger.lifecycle("Downloading Sherpa-ONNX AAR v${sherpaOnnxVersion} from GitHub release…")
|
|
64
|
+
// Download to a .part sibling and rename only after the SHA-256 matches, so
|
|
65
|
+
// an interrupted or tampered download can't be silently accepted as valid.
|
|
66
|
+
def partial = file("${sherpaOnnxAar.absolutePath}.part")
|
|
67
|
+
try {
|
|
68
|
+
new URL(sherpaOnnxUrl).withInputStream { input ->
|
|
69
|
+
partial.withOutputStream { output -> output << input }
|
|
70
|
+
}
|
|
71
|
+
def actual = sha256OfFile(partial)
|
|
72
|
+
if (actual != sherpaOnnxSha256) {
|
|
73
|
+
throw new GradleException(
|
|
74
|
+
"Sherpa-ONNX AAR v${sherpaOnnxVersion} SHA-256 mismatch.\n" +
|
|
75
|
+
" expected: ${sherpaOnnxSha256}\n" +
|
|
76
|
+
" actual: ${actual}\n" +
|
|
77
|
+
"Refusing to use the downloaded artifact."
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
if (!partial.renameTo(sherpaOnnxAar)) {
|
|
81
|
+
throw new GradleException("Failed to move ${partial.name} to ${sherpaOnnxAar.name}")
|
|
82
|
+
}
|
|
83
|
+
} finally {
|
|
84
|
+
if (partial.exists()) {
|
|
85
|
+
partial.delete()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Minimal POM so the Maven resolver is happy. No transitive deps — the AAR
|
|
91
|
+
// ships its own native libs and we already pin onnxruntime separately below.
|
|
92
|
+
if (!sherpaOnnxPom.exists()) {
|
|
93
|
+
sherpaOnnxPom.text = """<?xml version="1.0" encoding="UTF-8"?>
|
|
94
|
+
<project xmlns="http://maven.apache.org/POM/4.0.0">
|
|
95
|
+
<modelVersion>4.0.0</modelVersion>
|
|
96
|
+
<groupId>${sherpaOnnxGroup}</groupId>
|
|
97
|
+
<artifactId>${sherpaOnnxArtifact}</artifactId>
|
|
98
|
+
<version>${sherpaOnnxVersion}</version>
|
|
99
|
+
<packaging>aar</packaging>
|
|
100
|
+
</project>
|
|
101
|
+
"""
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
repositories {
|
|
105
|
+
maven { url = uri(sherpaOnnxMavenRoot) }
|
|
106
|
+
maven { url 'https://jitpack.io' }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
group = mavenGroupId
|
|
111
|
+
version = mavenPublishVersion
|
|
112
|
+
|
|
113
|
+
def credentialValue = { List<String> propertyNames, List<String> envNames ->
|
|
114
|
+
for (propertyName in propertyNames) {
|
|
115
|
+
def value = findProperty(propertyName)
|
|
116
|
+
if (value != null && value.toString().trim()) {
|
|
117
|
+
return value.toString()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (envName in envNames) {
|
|
121
|
+
def value = System.getenv(envName)
|
|
122
|
+
if (value != null && value.trim()) {
|
|
123
|
+
return value
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
def centralTokenParts = { boolean failOnInvalid = false ->
|
|
130
|
+
def token = credentialValue(['sonatypeTokenBase64', 'mavenCentralTokenBase64'], ['SONATYPE_TOKEN_BASE64', 'MAVEN_CENTRAL_TOKEN_BASE64'])
|
|
131
|
+
if (!token) {
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
def decoded
|
|
135
|
+
try {
|
|
136
|
+
decoded = new String(token.decodeBase64(), 'UTF-8')
|
|
137
|
+
} catch (Exception e) {
|
|
138
|
+
if (failOnInvalid) {
|
|
139
|
+
throw new GradleException('Sonatype token must be valid base64 for username:password', e)
|
|
140
|
+
}
|
|
141
|
+
return null
|
|
142
|
+
}
|
|
143
|
+
def separator = decoded.indexOf(':')
|
|
144
|
+
if (separator <= 0) {
|
|
145
|
+
if (failOnInvalid) {
|
|
146
|
+
throw new GradleException('Sonatype token must decode to username:password')
|
|
147
|
+
}
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
return [decoded.substring(0, separator), decoded.substring(separator + 1)]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
def centralUsername = { boolean failOnInvalidToken = false ->
|
|
154
|
+
credentialValue(['sonatypeUsername', 'mavenCentralUsername'], ['SONATYPE_USERNAME', 'MAVEN_CENTRAL_USERNAME']) ?: centralTokenParts(failOnInvalidToken)?.get(0)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
def centralPassword = { boolean failOnInvalidToken = false ->
|
|
158
|
+
credentialValue(['sonatypePassword', 'mavenCentralPassword'], ['SONATYPE_PASSWORD', 'MAVEN_CENTRAL_PASSWORD']) ?: centralTokenParts(failOnInvalidToken)?.get(1)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
def signingKey = {
|
|
162
|
+
credentialValue(['signingKey'], ['SIGNING_KEY'])
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
def signingPassword = {
|
|
166
|
+
credentialValue(['signingPassword'], ['SIGNING_PASSWORD'])
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
def shouldUseGpgCmd = {
|
|
170
|
+
(credentialValue(['signing.useGpgCmd'], ['SIGNING_USE_GPG_CMD']) ?: 'false').toBoolean()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
def isCentralPublishTask = {
|
|
174
|
+
gradle.taskGraph.allTasks.any { it.name.toLowerCase().contains('sonatypecentral') }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
gradle.taskGraph.whenReady {
|
|
178
|
+
if (isCentralPublishTask()) {
|
|
179
|
+
centralUsername(true)
|
|
180
|
+
centralPassword(true)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
11
183
|
|
|
12
184
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
13
185
|
apply from: expoModulesCorePlugin
|
|
@@ -40,7 +212,7 @@ android {
|
|
|
40
212
|
namespace "com.mentra.bluetoothsdk"
|
|
41
213
|
defaultConfig {
|
|
42
214
|
versionCode 1
|
|
43
|
-
versionName
|
|
215
|
+
versionName project.version
|
|
44
216
|
}
|
|
45
217
|
publishing {
|
|
46
218
|
singleVariant("release") {
|
|
@@ -51,6 +223,11 @@ android {
|
|
|
51
223
|
lintOptions {
|
|
52
224
|
abortOnError false
|
|
53
225
|
}
|
|
226
|
+
testOptions {
|
|
227
|
+
unitTests {
|
|
228
|
+
includeAndroidResources = true
|
|
229
|
+
}
|
|
230
|
+
}
|
|
54
231
|
packagingOptions {
|
|
55
232
|
pickFirst '**/libjsc.so'
|
|
56
233
|
pickFirst '**/libc++_shared.so'
|
|
@@ -82,6 +259,21 @@ dependencies {
|
|
|
82
259
|
|
|
83
260
|
// OkHttp for HTTP requests
|
|
84
261
|
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
|
262
|
+
implementation 'androidx.exifinterface:exifinterface:1.3.7'
|
|
263
|
+
|
|
264
|
+
// AVIF decode for BLE photo fallback. Public Maven artifacts omit this
|
|
265
|
+
// JitPack-only decoder from runtime metadata; Android 12+ can still use the
|
|
266
|
+
// platform decoder, while MentraOS app builds keep HeifCoder for broader
|
|
267
|
+
// fallback support.
|
|
268
|
+
// Pinned to the JitPack 1.7.3 build (same as asg_client): the Maven Central
|
|
269
|
+
// 2.2.x releases transitively pull kotlin-stdlib 2.3.0, which breaks this
|
|
270
|
+
// project's Kotlin 2.1 compile of :expo.
|
|
271
|
+
dependencies.add(publicMavenSdk ? 'compileOnly' : 'implementation', 'com.github.awxkee:avif-coder:1.7.3')
|
|
272
|
+
|
|
273
|
+
testImplementation 'junit:junit:4.13.2'
|
|
274
|
+
testImplementation 'org.robolectric:robolectric:4.11.1'
|
|
275
|
+
testImplementation 'org.assertj:assertj-core:3.24.2'
|
|
276
|
+
testImplementation 'androidx.test:core:1.5.0'
|
|
85
277
|
|
|
86
278
|
// Local photo upload receiver
|
|
87
279
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1"
|
|
@@ -89,28 +281,57 @@ dependencies {
|
|
|
89
281
|
// LC3 audio codec library
|
|
90
282
|
implementation project(':lc3Lib')
|
|
91
283
|
|
|
92
|
-
//
|
|
93
|
-
|
|
284
|
+
// These integrations are used by the full MentraOS app, but are not exposed
|
|
285
|
+
// through the public SDK API. Public Maven artifacts compile against them
|
|
286
|
+
// without publishing them as runtime transitives, so consumers only need
|
|
287
|
+
// Maven Central/Google repositories for normal SDK use.
|
|
288
|
+
def optionalMentraOsIntegrationDependency = publicMavenSdk ? 'compileOnly' : 'implementation'
|
|
289
|
+
|
|
290
|
+
// Sherpa-ONNX for local transcription. Jitpack's v1.13.2 build failed
|
|
291
|
+
// upstream, so we stage the prebuilt AAR into libs/maven/ (see top of file)
|
|
292
|
+
// and consume it as a Maven coord — AGP rejects raw files() .aar deps in
|
|
293
|
+
// library modules.
|
|
294
|
+
dependencies.add(optionalMentraOsIntegrationDependency, "${sherpaOnnxGroup}:${sherpaOnnxArtifact}:${sherpaOnnxVersion}@aar")
|
|
94
295
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
296
|
+
if (!publicMavenSdk) {
|
|
297
|
+
// Silero VAD — vendored source under silero/, wired as :silero
|
|
298
|
+
// sub-project (see settings.gradle). This replaces the previous Jitpack AAR
|
|
299
|
+
// (com.github.gkonovalov:android-vad:2.0.9) whose prebuilt JNI was bound to
|
|
300
|
+
// an ORT version incompatible with the one bundled by sherpa-onnx, causing a
|
|
301
|
+
// dlopen UnsatisfiedLinkError for OrtGetApiBase at VAD startup. By owning the
|
|
302
|
+
// source we control both the Java API surface and the ORT version it links
|
|
303
|
+
// against; today both consumers (Silero VAD and sherpa-onnx) load their own
|
|
304
|
+
// libonnxruntime.so — see packagingOptions pickFirst above.
|
|
305
|
+
implementation project(':silero')
|
|
306
|
+
|
|
307
|
+
// Vuzix Z100
|
|
308
|
+
implementation 'com.vuzix:ultralite-sdk-android:1.8'
|
|
309
|
+
implementation 'com.squareup.picasso:picasso:2.8'
|
|
310
|
+
} else {
|
|
311
|
+
compileOnly project(':silero')
|
|
312
|
+
compileOnly 'com.vuzix:ultralite-sdk-android:1.8'
|
|
313
|
+
compileOnly 'com.squareup.picasso:picasso:2.8'
|
|
314
|
+
}
|
|
98
315
|
|
|
99
316
|
implementation 'com.google.protobuf:protobuf-java:4.32.0'
|
|
100
317
|
implementation 'com.google.protobuf:protobuf-java-util:4.32.0'
|
|
101
318
|
}
|
|
102
319
|
|
|
103
|
-
//
|
|
320
|
+
// Pin onnxruntime-android to 1.24.3 — the version sherpa-onnx 1.13.2 was built
|
|
321
|
+
// against (its libonnxruntime.so exports OrtGetApiBase@@VERS_1.24.3). Matching
|
|
322
|
+
// this lets the :silero VAD module share a single libonnxruntime.so with
|
|
323
|
+
// sherpa-onnx, resolving the ELF symbol-versioning conflict that crashed VAD
|
|
324
|
+
// startup on previous mismatched versions.
|
|
104
325
|
configurations.all {
|
|
105
326
|
resolutionStrategy {
|
|
106
327
|
eachDependency { DependencyResolveDetails details ->
|
|
107
328
|
if (details.requested.group == 'com.microsoft.onnxruntime') {
|
|
108
|
-
details.useVersion '1.
|
|
109
|
-
details.because('
|
|
329
|
+
details.useVersion '1.24.3'
|
|
330
|
+
details.because('Match sherpa-onnx 1.13.2 ORT 1.24.3')
|
|
110
331
|
}
|
|
111
332
|
if (details.requested.name == 'onnxruntime-android') {
|
|
112
|
-
details.useVersion '1.
|
|
113
|
-
details.because('
|
|
333
|
+
details.useVersion '1.24.3'
|
|
334
|
+
details.because('Match sherpa-onnx 1.13.2 ORT 1.24.3')
|
|
114
335
|
}
|
|
115
336
|
}
|
|
116
337
|
}
|
|
@@ -120,7 +341,7 @@ afterEvaluate {
|
|
|
120
341
|
publishing {
|
|
121
342
|
publications {
|
|
122
343
|
release(MavenPublication) {
|
|
123
|
-
groupId =
|
|
344
|
+
groupId = mavenGroupId
|
|
124
345
|
artifactId = 'bluetooth-sdk'
|
|
125
346
|
version = project.version
|
|
126
347
|
|
|
@@ -151,13 +372,65 @@ afterEvaluate {
|
|
|
151
372
|
}
|
|
152
373
|
}
|
|
153
374
|
}
|
|
375
|
+
repositories {
|
|
376
|
+
maven {
|
|
377
|
+
name = 'sonatypeCentral'
|
|
378
|
+
url = uri('https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/')
|
|
379
|
+
credentials {
|
|
380
|
+
username = centralUsername()
|
|
381
|
+
password = centralPassword()
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
154
385
|
}
|
|
155
386
|
|
|
156
387
|
signing {
|
|
388
|
+
def inMemorySigningKey = signingKey()
|
|
389
|
+
if (inMemorySigningKey) {
|
|
390
|
+
useInMemoryPgpKeys(inMemorySigningKey, signingPassword())
|
|
391
|
+
} else if (shouldUseGpgCmd()) {
|
|
392
|
+
useGpgCmd()
|
|
393
|
+
}
|
|
157
394
|
required {
|
|
158
|
-
|
|
159
|
-
(findProperty("signing.keyId") || findProperty("signingKey"))
|
|
395
|
+
isCentralPublishTask()
|
|
160
396
|
}
|
|
161
397
|
sign publishing.publications.release
|
|
162
398
|
}
|
|
163
399
|
}
|
|
400
|
+
|
|
401
|
+
tasks.withType(Sign).configureEach {
|
|
402
|
+
onlyIf { isCentralPublishTask() }
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
tasks.register('uploadSonatypeCentralDeployment') {
|
|
406
|
+
group = 'publishing'
|
|
407
|
+
description = "Uploads the ${mavenGroupId} OSSRH compatibility repository to the Central Portal."
|
|
408
|
+
def releasePublishTaskPath = project.path == ':'
|
|
409
|
+
? ':publishReleasePublicationToSonatypeCentralRepository'
|
|
410
|
+
: "${project.path}:publishReleasePublicationToSonatypeCentralRepository"
|
|
411
|
+
dependsOn ':lc3Lib:publishReleasePublicationToSonatypeCentralRepository',
|
|
412
|
+
releasePublishTaskPath
|
|
413
|
+
|
|
414
|
+
doLast {
|
|
415
|
+
def username = centralUsername(true)
|
|
416
|
+
def password = centralPassword(true)
|
|
417
|
+
if (!username || !password) {
|
|
418
|
+
throw new GradleException('Set sonatypeTokenBase64 or sonatypeUsername/sonatypePassword before uploading to Central.')
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
def publishingType = (findProperty('sonatypePublishingType') ?: System.getenv('SONATYPE_PUBLISHING_TYPE') ?: 'user_managed').toString()
|
|
422
|
+
def endpoint = new URL("https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/${mavenGroupId}?publishing_type=${publishingType}")
|
|
423
|
+
def connection = endpoint.openConnection()
|
|
424
|
+
connection.requestMethod = 'POST'
|
|
425
|
+
connection.setRequestProperty('Authorization', "Bearer ${"${username}:${password}".bytes.encodeBase64()}")
|
|
426
|
+
connection.setRequestProperty('Content-Length', '0')
|
|
427
|
+
|
|
428
|
+
def statusCode = connection.responseCode
|
|
429
|
+
def responseBody = statusCode >= 400 ? connection.errorStream?.text : connection.inputStream?.text
|
|
430
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
431
|
+
throw new GradleException("Central deployment upload failed with HTTP ${statusCode}: ${responseBody ?: ''}")
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
println "Sonatype Central deployment upload requested for ${mavenGroupId} (${publishingType})."
|
|
435
|
+
}
|
|
436
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Copy these values into ~/.gradle/gradle.properties or inject them as CI
|
|
2
|
+
# secrets before running Android Maven publishing tasks. Do not commit real
|
|
3
|
+
# Sonatype tokens, GPG key names, or GPG passphrases.
|
|
4
|
+
sonatypeTokenBase64=...
|
|
5
|
+
sonatypePublishingType=user_managed
|
|
6
|
+
signing.useGpgCmd=true
|
|
7
|
+
signing.gnupg.executable=/opt/homebrew/bin/gpg
|
|
8
|
+
signing.gnupg.keyName=...
|
|
9
|
+
signing.gnupg.passphrase=...
|