@mentra/bluetooth-sdk 0.1.6 → 0.1.8
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 +26 -22
- package/android/.project +28 -0
- package/android/build.gradle +5 -9
- package/android/lc3Lib/.project +28 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +54 -33
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +48 -10
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +27 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +8 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +170 -14
- package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +6 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +69 -10
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +5 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +2 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +149 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +13 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +297 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/MentraPhotoReceiverModule.kt +120 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +13 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +3 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +269 -28
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +8 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +4 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +14 -12
- package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +4 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +131 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +105 -0
- package/build/BluetoothSdk.types.d.ts +107 -48
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js +4 -0
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +8 -6
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +58 -9
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/index.d.ts +2 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +6 -5
- package/build/index.js.map +1 -1
- package/build/photo-receiver/MentraPhotoReceiver.types.d.ts +18 -0
- package/build/photo-receiver/MentraPhotoReceiver.types.d.ts.map +1 -0
- package/build/photo-receiver/MentraPhotoReceiver.types.js +2 -0
- package/build/photo-receiver/MentraPhotoReceiver.types.js.map +1 -0
- package/build/photo-receiver/MentraPhotoReceiverModule.d.ts +10 -0
- package/build/photo-receiver/MentraPhotoReceiverModule.d.ts.map +1 -0
- package/build/photo-receiver/MentraPhotoReceiverModule.js +3 -0
- package/build/photo-receiver/MentraPhotoReceiverModule.js.map +1 -0
- package/build/photo-receiver/MentraPhotoReceiverModule.web.d.ts +10 -0
- package/build/photo-receiver/MentraPhotoReceiverModule.web.d.ts.map +1 -0
- package/build/photo-receiver/MentraPhotoReceiverModule.web.js +12 -0
- package/build/photo-receiver/MentraPhotoReceiverModule.web.js.map +1 -0
- package/build/photo-receiver/index.d.ts +3 -0
- package/build/photo-receiver/index.d.ts.map +1 -0
- package/build/photo-receiver/index.js +3 -0
- package/build/photo-receiver/index.js.map +1 -0
- package/build/react/useMentraBluetooth.d.ts +5 -3
- package/build/react/useMentraBluetooth.d.ts.map +1 -1
- package/build/react/useMentraBluetooth.js +15 -4
- package/build/react/useMentraBluetooth.js.map +1 -1
- package/expo-module.config.json +5 -2
- package/ios/BluetoothSdkModule.swift +59 -26
- package/ios/LocalPhotoUploadServer.swift +411 -0
- package/ios/MentraBluetoothSDK.podspec +8 -5
- package/ios/MentraPhotoReceiverModule.swift +147 -0
- package/ios/Source/Audio/AudioModels.swift +10 -10
- package/ios/Source/Bridge.swift +42 -12
- package/ios/Source/Camera/CameraModels.swift +74 -19
- package/ios/Source/DeviceManager.swift +23 -98
- package/ios/Source/DeviceStore.swift +12 -4
- package/ios/Source/Events/BluetoothEvents.swift +37 -0
- package/ios/Source/MentraBluetoothSDK.swift +135 -14
- package/ios/Source/Status/DeviceStatus.swift +12 -4
- package/ios/Source/Status/RuntimeState.swift +22 -13
- package/ios/Source/Streaming/StreamModels.swift +239 -45
- package/ios/Source/controllers/ControllerManager.swift +3 -3
- package/ios/Source/controllers/R1.swift +9 -3
- package/ios/Source/sgcs/G1.swift +5 -4
- package/ios/Source/sgcs/G2.swift +5 -6
- package/ios/Source/sgcs/Mach1.swift +1 -1
- package/ios/Source/sgcs/MentraLive.swift +183 -22
- package/ios/Source/sgcs/MentraNex.swift +1 -1
- package/ios/Source/sgcs/SGCManager.swift +11 -3
- package/ios/Source/sgcs/Simulated.swift +2 -2
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +1 -4
- package/ios/Source/utils/MessageChunkReassembler.swift +89 -0
- package/package.json +6 -1
- package/src/BluetoothSdk.types.ts +133 -62
- package/src/_private/BluetoothSdkModule.ts +79 -26
- package/src/index.ts +15 -8
- package/src/photo-receiver/MentraPhotoReceiver.types.ts +20 -0
- package/src/photo-receiver/MentraPhotoReceiverModule.ts +14 -0
- package/src/photo-receiver/MentraPhotoReceiverModule.web.ts +20 -0
- package/src/photo-receiver/index.ts +2 -0
- package/src/react/useMentraBluetooth.ts +19 -7
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +0 -221
- package/ios/Packages/VAD/Common/VAD/VADQuality.swift +0 -41
- package/ios/Packages/VAD/Common/VAD/VADState.swift +0 -26
- package/ios/Packages/VAD/Common/VAD/VADStrategy.swift +0 -29
- package/ios/Packages/VAD/Common/VAD/VADType.swift +0 -14
- package/ios/Packages/VAD/Data/Configuration.swift +0 -68
- package/ios/Packages/VAD/Data/FrameSize.swift +0 -39
- package/ios/Packages/VAD/Data/Record.swift +0 -13
- package/ios/Packages/VAD/Data/Result.swift +0 -22
- package/ios/Packages/VAD/Data/SampleRate.swift +0 -48
- package/ios/Packages/VAD/Silero/Model/silero_vad.onnx +0 -0
- package/ios/Packages/VAD/Silero/SileroVAD.swift +0 -284
- package/ios/Packages/VAD/Silero/SileroVADStrategy.swift +0 -64
package/ios/Source/Bridge.swift
CHANGED
|
@@ -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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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) {
|
|
@@ -259,11 +270,12 @@ class Bridge {
|
|
|
259
270
|
}
|
|
260
271
|
|
|
261
272
|
static func sendPhotoError(requestId: String, errorCode: String, errorMessage: String) {
|
|
273
|
+
let timestamp = Int(Date().timeIntervalSince1970 * 1000)
|
|
262
274
|
var event: [String: Any] = [
|
|
263
275
|
"type": "photo_response",
|
|
264
276
|
"state": "error",
|
|
265
277
|
"requestId": requestId,
|
|
266
|
-
"timestamp":
|
|
278
|
+
"timestamp": timestamp,
|
|
267
279
|
]
|
|
268
280
|
if !errorCode.isEmpty {
|
|
269
281
|
event["errorCode"] = errorCode
|
|
@@ -272,6 +284,24 @@ class Bridge {
|
|
|
272
284
|
event["errorMessage"] = errorMessage
|
|
273
285
|
}
|
|
274
286
|
Bridge.sendTypedMessage("photo_response", body: event)
|
|
287
|
+
|
|
288
|
+
var status: [String: Any] = [
|
|
289
|
+
"type": "photo_status",
|
|
290
|
+
"status": "failed",
|
|
291
|
+
"requestId": requestId,
|
|
292
|
+
"timestamp": timestamp,
|
|
293
|
+
]
|
|
294
|
+
if !errorCode.isEmpty {
|
|
295
|
+
status["errorCode"] = errorCode
|
|
296
|
+
}
|
|
297
|
+
if !errorMessage.isEmpty {
|
|
298
|
+
status["errorMessage"] = errorMessage
|
|
299
|
+
}
|
|
300
|
+
Bridge.sendTypedMessage("photo_status", body: status)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
static func sendPhotoStatus(_ status: [String: Any]) {
|
|
304
|
+
Bridge.sendTypedMessage("photo_status", body: status)
|
|
275
305
|
}
|
|
276
306
|
|
|
277
307
|
static func sendMiniappSelected(packageName: String) {
|
|
@@ -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,26 +30,33 @@ public struct ButtonPhotoSettings {
|
|
|
35
30
|
public struct ButtonVideoRecordingSettings {
|
|
36
31
|
public let width: Int
|
|
37
32
|
public let height: Int
|
|
38
|
-
public let
|
|
33
|
+
public let fps: Int
|
|
39
34
|
|
|
40
|
-
public init(width: Int, height: Int,
|
|
35
|
+
public init(width: Int, height: Int, fps: Int) {
|
|
41
36
|
self.width = width
|
|
42
37
|
self.height = height
|
|
43
|
-
self.
|
|
38
|
+
self.fps = fps
|
|
44
39
|
}
|
|
45
40
|
}
|
|
46
41
|
|
|
47
|
-
public
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
public struct CameraFov {
|
|
43
|
+
public static let minFov = 82
|
|
44
|
+
public static let maxFov = 118
|
|
45
|
+
public static let defaultFov = 102
|
|
46
|
+
public static let minRoiPosition = 0
|
|
47
|
+
public static let maxRoiPosition = 2
|
|
48
|
+
public static let defaultRoiPosition = 0
|
|
49
|
+
|
|
50
|
+
public let fov: Int
|
|
51
|
+
public let roiPosition: Int
|
|
52
|
+
|
|
53
|
+
public init(fov: Int = CameraFov.defaultFov, roiPosition: Int = CameraFov.defaultRoiPosition) {
|
|
54
|
+
self.fov = min(max(fov, CameraFov.minFov), CameraFov.maxFov)
|
|
55
|
+
self.roiPosition = min(max(roiPosition, CameraFov.minRoiPosition), CameraFov.maxRoiPosition)
|
|
56
|
+
}
|
|
50
57
|
|
|
51
58
|
var value: [String: Int] {
|
|
52
|
-
|
|
53
|
-
case .standard:
|
|
54
|
-
["fov": 118, "roiPosition": 0]
|
|
55
|
-
case .wide:
|
|
56
|
-
["fov": 118, "roiPosition": 0]
|
|
57
|
-
}
|
|
59
|
+
["fov": fov, "roi_position": roiPosition]
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -65,7 +67,12 @@ public struct PhotoRequest {
|
|
|
65
67
|
public let webhookUrl: String?
|
|
66
68
|
public let authToken: String?
|
|
67
69
|
public let compress: PhotoCompression?
|
|
70
|
+
public let flash: Bool
|
|
68
71
|
public let sound: Bool
|
|
72
|
+
/// Sensor exposure time for this capture only (ns), or nil for auto exposure
|
|
73
|
+
public let exposureTimeNs: Double?
|
|
74
|
+
/// Sensor ISO for this capture only. Only used when exposureTimeNs enables manual exposure.
|
|
75
|
+
public let iso: Int?
|
|
69
76
|
|
|
70
77
|
public init(
|
|
71
78
|
requestId: String,
|
|
@@ -74,7 +81,10 @@ public struct PhotoRequest {
|
|
|
74
81
|
webhookUrl: String? = nil,
|
|
75
82
|
authToken: String? = nil,
|
|
76
83
|
compress: PhotoCompression? = nil,
|
|
77
|
-
|
|
84
|
+
flash: Bool = true,
|
|
85
|
+
sound: Bool,
|
|
86
|
+
exposureTimeNs: Double? = nil,
|
|
87
|
+
iso: Int? = nil
|
|
78
88
|
) {
|
|
79
89
|
self.requestId = requestId
|
|
80
90
|
self.appId = appId
|
|
@@ -82,7 +92,10 @@ public struct PhotoRequest {
|
|
|
82
92
|
self.webhookUrl = webhookUrl
|
|
83
93
|
self.authToken = authToken
|
|
84
94
|
self.compress = compress
|
|
95
|
+
self.flash = flash
|
|
85
96
|
self.sound = sound
|
|
97
|
+
self.exposureTimeNs = exposureTimeNs
|
|
98
|
+
self.iso = iso
|
|
86
99
|
}
|
|
87
100
|
}
|
|
88
101
|
|
|
@@ -227,7 +240,7 @@ public struct PhotoResponseEvent: CustomStringConvertible {
|
|
|
227
240
|
}
|
|
228
241
|
|
|
229
242
|
public init(values: [String: Any]) {
|
|
230
|
-
|
|
243
|
+
response = PhotoResponse(values: values)
|
|
231
244
|
}
|
|
232
245
|
|
|
233
246
|
public var requestId: String {
|
|
@@ -244,3 +257,45 @@ public struct PhotoResponseEvent: CustomStringConvertible {
|
|
|
244
257
|
"PhotoResponseEvent(requestId: \(requestId), state: \(response.state.rawValue))"
|
|
245
258
|
}
|
|
246
259
|
}
|
|
260
|
+
|
|
261
|
+
public struct PhotoStatusEvent: CustomStringConvertible {
|
|
262
|
+
public let values: [String: Any]
|
|
263
|
+
|
|
264
|
+
public init(values: [String: Any]) {
|
|
265
|
+
var values = values
|
|
266
|
+
values["type"] = "photo_status"
|
|
267
|
+
self.values = values
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
public var requestId: String {
|
|
271
|
+
stringValue(values, "requestId") ?? ""
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
public var status: String {
|
|
275
|
+
stringValue(values, "status") ?? ""
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public var timestamp: Int64 {
|
|
279
|
+
if let value = values["timestamp"] as? Int64 { return value }
|
|
280
|
+
if let value = values["timestamp"] as? Int { return Int64(value) }
|
|
281
|
+
if let value = values["timestamp"] as? Double { return Int64(value) }
|
|
282
|
+
if let value = values["timestamp"] as? NSNumber { return value.int64Value }
|
|
283
|
+
return Int64(Date().timeIntervalSince1970 * 1000)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
public var resolvedConfig: [String: Any]? {
|
|
287
|
+
values["resolvedConfig"] as? [String: Any]
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
public var errorCode: String? {
|
|
291
|
+
stringValue(values, "errorCode")
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
public var errorMessage: String? {
|
|
295
|
+
stringValue(values, "errorMessage")
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
public var description: String {
|
|
299
|
+
"PhotoStatusEvent(requestId: \(requestId), status: \(status))"
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -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
|
|
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
|
-
|
|
441
|
-
if bypassVad {
|
|
442
|
-
handleSendingPcm(pcmData)
|
|
443
|
-
|
|
444
|
-
// Send PCM to local transcriber (always needs raw PCM)
|
|
445
|
-
if shouldSendTranscript || offlineCaptionsRunning || localSttFallbackActive {
|
|
446
|
-
transcriber?.acceptAudio(pcm16le: pcmData)
|
|
447
|
-
}
|
|
448
|
-
return
|
|
449
|
-
}
|
|
395
|
+
handleSendingPcm(pcmData)
|
|
450
396
|
|
|
451
|
-
//
|
|
452
|
-
|
|
453
|
-
|
|
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
|
|
|
@@ -821,7 +733,7 @@ struct ViewState {
|
|
|
821
733
|
if !glassesMicEnabled || !glassesConnected {
|
|
822
734
|
return
|
|
823
735
|
}
|
|
824
|
-
|
|
736
|
+
|
|
825
737
|
let timeSinceLastLc3Event = Date().timeIntervalSince(lastLc3Event ?? Date())
|
|
826
738
|
if timeSinceLastLc3Event > 5 {
|
|
827
739
|
Bridge.log("MAN: No audio activity in the last 5 seconds from glasses, reinitializing glasses mic")
|
|
@@ -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,24 @@ struct ViewState {
|
|
|
1280
1194
|
_ webhookUrl: String?,
|
|
1281
1195
|
_ authToken: String?,
|
|
1282
1196
|
_ compress: String?,
|
|
1283
|
-
_
|
|
1197
|
+
_ flash: Bool,
|
|
1198
|
+
_ sound: Bool,
|
|
1199
|
+
exposureTimeNs: Double? = nil,
|
|
1200
|
+
iso: Int? = nil
|
|
1284
1201
|
) {
|
|
1202
|
+
let manualIso = exposureTimeNs != nil ? iso.flatMap { $0 > 0 ? $0 : nil } : nil
|
|
1285
1203
|
Bridge.log(
|
|
1286
|
-
"MAN: requestPhoto
|
|
1204
|
+
"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") iso=\(manualIso.map { String($0) } ?? "auto") sgc=\(sgc != nil ? String(describing: type(of: sgc!)) : "null")"
|
|
1287
1205
|
)
|
|
1288
|
-
sgc
|
|
1206
|
+
guard let sgc else {
|
|
1207
|
+
Bridge.log(
|
|
1208
|
+
"MAN: PHOTO PIPELINE — sgc is null (glasses not connected); dropping requestId=\(requestId)"
|
|
1209
|
+
)
|
|
1210
|
+
return
|
|
1211
|
+
}
|
|
1212
|
+
sgc.requestPhoto(
|
|
1289
1213
|
requestId, appId: appId, size: size, webhookUrl: webhookUrl, authToken: authToken,
|
|
1290
|
-
compress: compress, flash:
|
|
1214
|
+
compress: compress, flash: flash, sound: sound, exposureTimeNs: exposureTimeNs, iso: manualIso
|
|
1291
1215
|
)
|
|
1292
1216
|
}
|
|
1293
1217
|
|
|
@@ -1393,6 +1317,7 @@ struct ViewState {
|
|
|
1393
1317
|
DeviceStore.shared.apply("glasses", "deviceModel", "")
|
|
1394
1318
|
DeviceStore.shared.apply("glasses", "fullyBooted", false)
|
|
1395
1319
|
DeviceStore.shared.apply("glasses", "connected", false)
|
|
1320
|
+
DeviceStore.shared.apply("glasses", "voiceActivityDetectionEnabled", true)
|
|
1396
1321
|
// disconnect the controller as well:
|
|
1397
1322
|
searchingController = false
|
|
1398
1323
|
DeviceStore.shared.apply("glasses", "controllerConnected", false)
|
|
@@ -28,6 +28,7 @@ class DeviceStore {
|
|
|
28
28
|
store.set("glasses", "deviceModel", "")
|
|
29
29
|
store.set("glasses", "firmwareVersion", "")
|
|
30
30
|
store.set("glasses", "micEnabled", false)
|
|
31
|
+
store.set("glasses", "voiceActivityDetectionEnabled", true)
|
|
31
32
|
store.set("glasses", "bluetoothClassicConnected", false)
|
|
32
33
|
store.set("glasses", "caseRemoved", true)
|
|
33
34
|
store.set("glasses", "caseOpen", true)
|
|
@@ -71,21 +72,26 @@ class DeviceStore {
|
|
|
71
72
|
store.set("bluetooth", "pending_wearable", "")
|
|
72
73
|
store.set("bluetooth", "device_name", "")
|
|
73
74
|
store.set("bluetooth", "device_address", "")
|
|
75
|
+
store.set("bluetooth", "default_controller", "")
|
|
76
|
+
store.set("bluetooth", "pending_controller", "")
|
|
77
|
+
store.set("bluetooth", "controller_device_name", "")
|
|
74
78
|
store.set("bluetooth", "screen_disabled", false)
|
|
75
79
|
store.set("bluetooth", "preferred_mic", "auto")
|
|
76
80
|
store.set("bluetooth", "sensing_enabled", true)
|
|
81
|
+
store.set("bluetooth", "power_saving_mode", false)
|
|
77
82
|
store.set("bluetooth", "brightness", 50)
|
|
78
83
|
store.set("bluetooth", "auto_brightness", true)
|
|
79
84
|
store.set("bluetooth", "dashboard_height", 4)
|
|
80
85
|
store.set("bluetooth", "dashboard_depth", 2)
|
|
81
86
|
store.set("bluetooth", "head_up_angle", 30)
|
|
82
87
|
store.set("bluetooth", "contextual_dashboard", true)
|
|
83
|
-
store.set("bluetooth", "
|
|
88
|
+
store.set("bluetooth", "gallery_mode", true)
|
|
89
|
+
store.set("bluetooth", "voice_activity_detection_enabled", true)
|
|
84
90
|
store.set("bluetooth", "screen_disabled", false)
|
|
85
91
|
store.set("bluetooth", "button_photo_size", "medium")
|
|
86
92
|
store.set("bluetooth", "button_camera_led", true)
|
|
87
93
|
store.set("bluetooth", "button_max_recording_time", 10)
|
|
88
|
-
store.set("bluetooth", "camera_fov", ["fov": 118, "
|
|
94
|
+
store.set("bluetooth", "camera_fov", ["fov": 118, "roi_position": 0])
|
|
89
95
|
store.set("bluetooth", "button_video_width", 1280)
|
|
90
96
|
store.set("bluetooth", "button_video_height", 720)
|
|
91
97
|
store.set("bluetooth", "button_video_fps", 30)
|
|
@@ -96,7 +102,6 @@ class DeviceStore {
|
|
|
96
102
|
store.set("bluetooth", "should_send_pcm", false)
|
|
97
103
|
store.set("bluetooth", "should_send_lc3", false)
|
|
98
104
|
store.set("bluetooth", "should_send_transcript", false)
|
|
99
|
-
store.set("bluetooth", "bypass_vad", true)
|
|
100
105
|
store.set("bluetooth", "use_native_dashboard", false)
|
|
101
106
|
}
|
|
102
107
|
|
|
@@ -215,9 +220,12 @@ class DeviceStore {
|
|
|
215
220
|
DeviceManager.shared.sgc?.setDashboardMenu(items)
|
|
216
221
|
}
|
|
217
222
|
|
|
218
|
-
case ("bluetooth", "
|
|
223
|
+
case ("bluetooth", "gallery_mode"):
|
|
219
224
|
DeviceManager.shared.sgc?.sendGalleryMode()
|
|
220
225
|
|
|
226
|
+
case ("bluetooth", "voice_activity_detection_enabled"):
|
|
227
|
+
DeviceManager.shared.sgc?.sendVoiceActivityDetectionSetting()
|
|
228
|
+
|
|
221
229
|
case ("bluetooth", "screen_disabled"):
|
|
222
230
|
if let disabled = value as? Bool {
|
|
223
231
|
if disabled {
|
|
@@ -44,13 +44,44 @@ public struct TouchEvent: CustomStringConvertible {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
public struct VoiceActivityDetectionStatusEvent: CustomStringConvertible {
|
|
48
|
+
public let voiceActivityDetectionEnabled: Bool
|
|
49
|
+
public let values: [String: Any]
|
|
50
|
+
|
|
51
|
+
public init(values: [String: Any]) {
|
|
52
|
+
voiceActivityDetectionEnabled = boolValue(values, "voiceActivityDetectionEnabled") ?? true
|
|
53
|
+
self.values = values
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public var description: String {
|
|
57
|
+
"VoiceActivityDetectionStatusEvent(voiceActivityDetectionEnabled: \(voiceActivityDetectionEnabled))"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public struct SpeakingStatusEvent: CustomStringConvertible {
|
|
62
|
+
public let speaking: Bool
|
|
63
|
+
public let values: [String: Any]
|
|
64
|
+
|
|
65
|
+
public init(values: [String: Any]) {
|
|
66
|
+
speaking = boolValue(values, "speaking") ?? false
|
|
67
|
+
self.values = values
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public var description: String {
|
|
71
|
+
"SpeakingStatusEvent(speaking: \(speaking))"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
47
75
|
public enum BluetoothEvent: CustomStringConvertible {
|
|
48
76
|
case buttonPress(ButtonPressEvent)
|
|
49
77
|
case touch(TouchEvent)
|
|
78
|
+
case voiceActivityDetectionStatus(VoiceActivityDetectionStatusEvent)
|
|
79
|
+
case speakingStatus(SpeakingStatusEvent)
|
|
50
80
|
case wifiStatus(WifiStatusEvent)
|
|
51
81
|
case hotspotStatus(HotspotStatusEvent)
|
|
52
82
|
case hotspotError(HotspotErrorEvent)
|
|
53
83
|
case photoResponse(PhotoResponseEvent)
|
|
84
|
+
case photoStatus(PhotoStatusEvent)
|
|
54
85
|
case streamStatus(StreamStatusEvent)
|
|
55
86
|
case keepAliveAck(KeepAliveAckEvent)
|
|
56
87
|
case localTranscription(LocalTranscriptionEvent)
|
|
@@ -62,6 +93,10 @@ public enum BluetoothEvent: CustomStringConvertible {
|
|
|
62
93
|
event.description
|
|
63
94
|
case let .touch(event):
|
|
64
95
|
event.description
|
|
96
|
+
case let .voiceActivityDetectionStatus(event):
|
|
97
|
+
event.description
|
|
98
|
+
case let .speakingStatus(event):
|
|
99
|
+
event.description
|
|
65
100
|
case let .wifiStatus(event):
|
|
66
101
|
event.description
|
|
67
102
|
case let .hotspotStatus(event):
|
|
@@ -70,6 +105,8 @@ public enum BluetoothEvent: CustomStringConvertible {
|
|
|
70
105
|
event.description
|
|
71
106
|
case let .photoResponse(event):
|
|
72
107
|
event.description
|
|
108
|
+
case let .photoStatus(event):
|
|
109
|
+
event.description
|
|
73
110
|
case let .streamStatus(event):
|
|
74
111
|
event.description
|
|
75
112
|
case let .keepAliveAck(event):
|