@mentra/bluetooth-sdk 0.1.5 → 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.
- package/README.md +45 -27
- package/android/build.gradle +2 -9
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +53 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +22 -9
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +25 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +9 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +68 -56
- package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +125 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +209 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/connection/ConnectionModels.kt +78 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +4 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +2 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +72 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +132 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +39 -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 +2 -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 +168 -10
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +7 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +3 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +607 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +201 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +173 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +348 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +75 -0
- package/build/BluetoothSdk.types.d.ts +46 -20
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js +1 -0
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +6 -5
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +40 -5
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -2
- package/build/index.js.map +1 -1
- 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/ios/BluetoothSdkModule.swift +45 -28
- package/ios/MentraBluetoothSDK.podspec +0 -3
- package/ios/Source/Audio/AudioModels.swift +159 -0
- package/ios/Source/Bridge.swift +22 -11
- package/ios/Source/Camera/CameraModels.swift +248 -0
- package/ios/Source/Connection/ScanSession.swift +27 -0
- package/ios/Source/DeviceManager.swift +20 -97
- package/ios/Source/DeviceStore.swift +13 -4
- package/ios/Source/Errors/BluetoothError.swift +19 -0
- package/ios/Source/Events/BluetoothEvents.swift +149 -0
- package/ios/Source/Internal/BluetoothAvailability.swift +58 -0
- package/ios/Source/Internal/ValueParsing.swift +90 -0
- package/ios/Source/MentraBluetoothSDK.swift +50 -2150
- package/ios/Source/Requests/DisplayRequests.swift +58 -0
- package/ios/Source/Status/DeviceStatus.swift +471 -0
- package/ios/Source/Status/RuntimeState.swift +359 -0
- package/ios/Source/Status/WifiHotspotStatus.swift +326 -0
- package/ios/Source/Streaming/StreamModels.swift +436 -0
- package/ios/Source/Types/DeviceModels.swift +134 -0
- package/ios/Source/controllers/ControllerManager.swift +3 -3
- package/ios/Source/controllers/R1.swift +1 -1
- package/ios/Source/sgcs/G1.swift +1 -1
- package/ios/Source/sgcs/G2.swift +99 -39
- package/ios/Source/sgcs/Mach1.swift +1 -1
- package/ios/Source/sgcs/MentraLive.swift +109 -12
- 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/package.json +1 -1
- package/src/BluetoothSdk.types.ts +48 -28
- package/src/_private/BluetoothSdkModule.ts +47 -21
- package/src/index.ts +6 -4
- package/src/react/useMentraBluetooth.ts +19 -7
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +0 -1810
- 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
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk
|
|
2
|
+
|
|
3
|
+
data class StreamVideoConfig @JvmOverloads constructor(
|
|
4
|
+
val width: Int? = null,
|
|
5
|
+
val height: Int? = null,
|
|
6
|
+
val bitrate: Int? = null,
|
|
7
|
+
val fps: Int? = null,
|
|
8
|
+
) {
|
|
9
|
+
fun toMap(): Map<String, Any> =
|
|
10
|
+
listOfNotNull(
|
|
11
|
+
width?.let { "width" to it },
|
|
12
|
+
height?.let { "height" to it },
|
|
13
|
+
bitrate?.let { "bitrate" to it },
|
|
14
|
+
fps?.let { "frameRate" to it },
|
|
15
|
+
).toMap()
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
@JvmStatic
|
|
19
|
+
fun fromMap(values: Map<String, Any>?): StreamVideoConfig? {
|
|
20
|
+
values ?: return null
|
|
21
|
+
return StreamVideoConfig(
|
|
22
|
+
width = numberValue(values, "width"),
|
|
23
|
+
height = numberValue(values, "height"),
|
|
24
|
+
bitrate = numberValue(values, "bitrate"),
|
|
25
|
+
fps = numberValue(values, "fps"),
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
data class StreamAudioConfig @JvmOverloads constructor(
|
|
32
|
+
val bitrate: Int? = null,
|
|
33
|
+
val sampleRate: Int? = null,
|
|
34
|
+
val echoCancellation: Boolean? = null,
|
|
35
|
+
val noiseSuppression: Boolean? = null,
|
|
36
|
+
) {
|
|
37
|
+
fun toMap(): Map<String, Any> =
|
|
38
|
+
listOfNotNull(
|
|
39
|
+
bitrate?.let { "bitrate" to it },
|
|
40
|
+
sampleRate?.let { "sampleRate" to it },
|
|
41
|
+
echoCancellation?.let { "echoCancellation" to it },
|
|
42
|
+
noiseSuppression?.let { "noiseSuppression" to it },
|
|
43
|
+
).toMap()
|
|
44
|
+
|
|
45
|
+
companion object {
|
|
46
|
+
@JvmStatic
|
|
47
|
+
fun fromMap(values: Map<String, Any>?): StreamAudioConfig? {
|
|
48
|
+
values ?: return null
|
|
49
|
+
return StreamAudioConfig(
|
|
50
|
+
bitrate = numberValue(values, "bitrate"),
|
|
51
|
+
sampleRate = numberValue(values, "sampleRate"),
|
|
52
|
+
echoCancellation = values["echoCancellation"] as? Boolean,
|
|
53
|
+
noiseSuppression = values["noiseSuppression"] as? Boolean,
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
data class StreamRequest @JvmOverloads constructor(
|
|
60
|
+
val streamUrl: String,
|
|
61
|
+
val streamId: String = "",
|
|
62
|
+
val keepAlive: Boolean = true,
|
|
63
|
+
val keepAliveIntervalSeconds: Int = 15,
|
|
64
|
+
val sound: Boolean = true,
|
|
65
|
+
val video: StreamVideoConfig? = null,
|
|
66
|
+
val audio: StreamAudioConfig? = null,
|
|
67
|
+
val extraValues: Map<String, Any> = emptyMap(),
|
|
68
|
+
) {
|
|
69
|
+
fun toMap(): Map<String, Any> {
|
|
70
|
+
val values = extraValues.toMutableMap()
|
|
71
|
+
values["type"] = "start_stream"
|
|
72
|
+
values["streamUrl"] = streamUrl
|
|
73
|
+
values["streamId"] = streamId
|
|
74
|
+
values["keepAlive"] = keepAlive
|
|
75
|
+
values["keepAliveIntervalSeconds"] = keepAliveIntervalSeconds
|
|
76
|
+
// The camera light is a privacy indicator and cannot be disabled by SDK callers.
|
|
77
|
+
values["flash"] = true
|
|
78
|
+
values["sound"] = sound
|
|
79
|
+
video?.toMap()?.takeIf { it.isNotEmpty() }?.let { values["video"] = it }
|
|
80
|
+
audio?.toMap()?.takeIf { it.isNotEmpty() }?.let { values["audio"] = it }
|
|
81
|
+
return values
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
companion object {
|
|
85
|
+
@JvmStatic
|
|
86
|
+
fun fromMap(values: Map<String, Any>): StreamRequest =
|
|
87
|
+
StreamRequest(
|
|
88
|
+
streamUrl =
|
|
89
|
+
(values["streamUrl"] ?: values["rtmpUrl"] ?: values["srtUrl"] ?: values["whipUrl"]) as? String
|
|
90
|
+
?: "",
|
|
91
|
+
streamId = values["streamId"] as? String ?: "",
|
|
92
|
+
keepAlive = values["keepAlive"] as? Boolean ?: true,
|
|
93
|
+
keepAliveIntervalSeconds = (values["keepAliveIntervalSeconds"] as? Number)?.toInt() ?: 15,
|
|
94
|
+
sound = values["sound"] as? Boolean ?: true,
|
|
95
|
+
video = StreamVideoConfig.fromMap(stringMapValue(values["video"])),
|
|
96
|
+
audio = StreamAudioConfig.fromMap(stringMapValue(values["audio"])),
|
|
97
|
+
extraValues = values,
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
data class StreamKeepAliveRequest @JvmOverloads constructor(
|
|
103
|
+
val streamId: String,
|
|
104
|
+
val ackId: String,
|
|
105
|
+
val extraValues: Map<String, Any> = emptyMap(),
|
|
106
|
+
) {
|
|
107
|
+
fun toMap(): Map<String, Any> {
|
|
108
|
+
val values = extraValues.toMutableMap()
|
|
109
|
+
values["type"] = "keep_stream_alive"
|
|
110
|
+
values["streamId"] = streamId
|
|
111
|
+
values["ackId"] = ackId
|
|
112
|
+
return values
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
companion object {
|
|
116
|
+
@JvmStatic
|
|
117
|
+
fun fromMap(values: Map<String, Any>): StreamKeepAliveRequest =
|
|
118
|
+
StreamKeepAliveRequest(
|
|
119
|
+
streamId = values["streamId"] as? String ?: "",
|
|
120
|
+
ackId = values["ackId"] as? String ?: "",
|
|
121
|
+
extraValues = values,
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
enum class StreamState(val value: String) {
|
|
127
|
+
INITIALIZING("initializing"),
|
|
128
|
+
STREAMING("streaming"),
|
|
129
|
+
STOPPING("stopping"),
|
|
130
|
+
STOPPED("stopped"),
|
|
131
|
+
RECONNECTING("reconnecting"),
|
|
132
|
+
RECONNECTED("reconnected"),
|
|
133
|
+
RECONNECT_FAILED("reconnect_failed"),
|
|
134
|
+
ERROR("error");
|
|
135
|
+
|
|
136
|
+
companion object {
|
|
137
|
+
@JvmStatic
|
|
138
|
+
fun fromValue(value: String?): StreamState? =
|
|
139
|
+
when (value?.lowercase()) {
|
|
140
|
+
"initializing", "starting", "connecting" -> INITIALIZING
|
|
141
|
+
"streaming", "streaming_started", "active" -> STREAMING
|
|
142
|
+
"stopping" -> STOPPING
|
|
143
|
+
"stopped", "not_streaming", "disconnected", "timeout" -> STOPPED
|
|
144
|
+
"reconnecting" -> RECONNECTING
|
|
145
|
+
"reconnected" -> RECONNECTED
|
|
146
|
+
"reconnect_failed" -> RECONNECT_FAILED
|
|
147
|
+
"error", "error_not_streaming" -> ERROR
|
|
148
|
+
else -> null
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
enum class StreamStatusKind(val value: String) {
|
|
154
|
+
LIFECYCLE("lifecycle"),
|
|
155
|
+
RECONNECT("reconnect"),
|
|
156
|
+
ERROR("error"),
|
|
157
|
+
SNAPSHOT("snapshot"),
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
sealed interface StreamStatus {
|
|
161
|
+
val kind: StreamStatusKind
|
|
162
|
+
val state: StreamState
|
|
163
|
+
val streamId: String?
|
|
164
|
+
val timestamp: Long?
|
|
165
|
+
|
|
166
|
+
fun toMap(): Map<String, Any> {
|
|
167
|
+
val values = mutableMapOf<String, Any>(
|
|
168
|
+
"kind" to kind.value,
|
|
169
|
+
"status" to state.value,
|
|
170
|
+
)
|
|
171
|
+
streamId?.takeIf { it.isNotBlank() }?.let { values["streamId"] = it }
|
|
172
|
+
timestamp?.let { values["timestamp"] = it }
|
|
173
|
+
|
|
174
|
+
when (this) {
|
|
175
|
+
is Lifecycle -> Unit
|
|
176
|
+
is Reconnecting -> {
|
|
177
|
+
values["attempt"] = attempt
|
|
178
|
+
values["maxAttempts"] = maxAttempts
|
|
179
|
+
values["reason"] = reason
|
|
180
|
+
}
|
|
181
|
+
is Reconnected -> values["attempt"] = attempt
|
|
182
|
+
is ReconnectFailed -> values["maxAttempts"] = maxAttempts
|
|
183
|
+
is Error -> values["errorDetails"] = errorDetails
|
|
184
|
+
is Snapshot -> {
|
|
185
|
+
values["streaming"] = streaming
|
|
186
|
+
values["reconnecting"] = reconnecting
|
|
187
|
+
attempt?.let { values["attempt"] = it }
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return values
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fun toEventMap(): Map<String, Any> = toMap() + mapOf("type" to "stream_status")
|
|
195
|
+
|
|
196
|
+
data class Lifecycle(
|
|
197
|
+
override val state: StreamState,
|
|
198
|
+
override val streamId: String?,
|
|
199
|
+
override val timestamp: Long?,
|
|
200
|
+
) : StreamStatus {
|
|
201
|
+
override val kind: StreamStatusKind = StreamStatusKind.LIFECYCLE
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
data class Reconnecting(
|
|
205
|
+
override val streamId: String?,
|
|
206
|
+
val attempt: Int,
|
|
207
|
+
val maxAttempts: Int,
|
|
208
|
+
val reason: String,
|
|
209
|
+
override val timestamp: Long?,
|
|
210
|
+
) : StreamStatus {
|
|
211
|
+
override val kind: StreamStatusKind = StreamStatusKind.RECONNECT
|
|
212
|
+
override val state: StreamState = StreamState.RECONNECTING
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
data class Reconnected(
|
|
216
|
+
override val streamId: String?,
|
|
217
|
+
val attempt: Int,
|
|
218
|
+
override val timestamp: Long?,
|
|
219
|
+
) : StreamStatus {
|
|
220
|
+
override val kind: StreamStatusKind = StreamStatusKind.RECONNECT
|
|
221
|
+
override val state: StreamState = StreamState.RECONNECTED
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
data class ReconnectFailed(
|
|
225
|
+
override val streamId: String?,
|
|
226
|
+
val maxAttempts: Int,
|
|
227
|
+
override val timestamp: Long?,
|
|
228
|
+
) : StreamStatus {
|
|
229
|
+
override val kind: StreamStatusKind = StreamStatusKind.RECONNECT
|
|
230
|
+
override val state: StreamState = StreamState.RECONNECT_FAILED
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
data class Error(
|
|
234
|
+
override val streamId: String?,
|
|
235
|
+
val errorDetails: String,
|
|
236
|
+
override val timestamp: Long?,
|
|
237
|
+
) : StreamStatus {
|
|
238
|
+
override val kind: StreamStatusKind = StreamStatusKind.ERROR
|
|
239
|
+
override val state: StreamState = StreamState.ERROR
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
data class Snapshot(
|
|
243
|
+
override val state: StreamState,
|
|
244
|
+
val streaming: Boolean,
|
|
245
|
+
val reconnecting: Boolean,
|
|
246
|
+
override val streamId: String?,
|
|
247
|
+
val attempt: Int?,
|
|
248
|
+
override val timestamp: Long?,
|
|
249
|
+
) : StreamStatus {
|
|
250
|
+
override val kind: StreamStatusKind = StreamStatusKind.SNAPSHOT
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
companion object {
|
|
254
|
+
@JvmStatic
|
|
255
|
+
fun fromMap(values: Map<String, Any>): StreamStatus {
|
|
256
|
+
val rawState = stringValue(values, "status")
|
|
257
|
+
val streaming = boolValue(values, "streaming")
|
|
258
|
+
val reconnecting = boolValue(values, "reconnecting") ?: false
|
|
259
|
+
val streamId = stringValue(values, "streamId")
|
|
260
|
+
val timestamp = longValue(values, "timestamp")
|
|
261
|
+
val attempt = numberValue(values, "attempt")
|
|
262
|
+
val maxAttempts = numberValue(values, "maxAttempts") ?: 0
|
|
263
|
+
|
|
264
|
+
if (streaming != null || hasAnyKey(values, "reconnecting")) {
|
|
265
|
+
return Snapshot(
|
|
266
|
+
state = when {
|
|
267
|
+
reconnecting -> StreamState.RECONNECTING
|
|
268
|
+
streaming == true -> StreamState.STREAMING
|
|
269
|
+
else -> StreamState.STOPPED
|
|
270
|
+
},
|
|
271
|
+
streaming = streaming == true,
|
|
272
|
+
reconnecting = reconnecting,
|
|
273
|
+
streamId = streamId,
|
|
274
|
+
attempt = attempt,
|
|
275
|
+
timestamp = timestamp,
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
val state = StreamState.fromValue(rawState)
|
|
280
|
+
?: return Error(
|
|
281
|
+
streamId = streamId,
|
|
282
|
+
errorDetails = rawState?.let { "Unknown stream status: $it" } ?: "Missing stream status",
|
|
283
|
+
timestamp = timestamp,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
return when (state) {
|
|
287
|
+
StreamState.RECONNECTING -> Reconnecting(
|
|
288
|
+
streamId = streamId,
|
|
289
|
+
attempt = attempt ?: 0,
|
|
290
|
+
maxAttempts = maxAttempts,
|
|
291
|
+
reason = stringValue(values, "reason") ?: "",
|
|
292
|
+
timestamp = timestamp,
|
|
293
|
+
)
|
|
294
|
+
StreamState.RECONNECTED -> Reconnected(
|
|
295
|
+
streamId = streamId,
|
|
296
|
+
attempt = attempt ?: 0,
|
|
297
|
+
timestamp = timestamp,
|
|
298
|
+
)
|
|
299
|
+
StreamState.RECONNECT_FAILED -> ReconnectFailed(
|
|
300
|
+
streamId = streamId,
|
|
301
|
+
maxAttempts = maxAttempts,
|
|
302
|
+
timestamp = timestamp,
|
|
303
|
+
)
|
|
304
|
+
StreamState.ERROR -> Error(
|
|
305
|
+
streamId = streamId,
|
|
306
|
+
errorDetails = stringValue(values, "errorDetails")
|
|
307
|
+
?: if (rawState == "error_not_streaming") "not_streaming" else "Unknown stream error",
|
|
308
|
+
timestamp = timestamp,
|
|
309
|
+
)
|
|
310
|
+
else -> Lifecycle(
|
|
311
|
+
state = state,
|
|
312
|
+
streamId = streamId,
|
|
313
|
+
timestamp = timestamp,
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
data class StreamStatusEvent(
|
|
321
|
+
val status: StreamStatus,
|
|
322
|
+
) {
|
|
323
|
+
constructor(values: Map<String, Any>) : this(StreamStatus.fromMap(values))
|
|
324
|
+
|
|
325
|
+
val state: StreamState get() = status.state
|
|
326
|
+
val streamId: String? get() = status.streamId
|
|
327
|
+
val values: Map<String, Any> get() = status.toEventMap()
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
data class KeepAliveAckEvent(
|
|
331
|
+
val streamId: String,
|
|
332
|
+
val ackId: String,
|
|
333
|
+
val timestamp: Long?,
|
|
334
|
+
) {
|
|
335
|
+
constructor(values: Map<String, Any>) : this(
|
|
336
|
+
streamId = stringValue(values, "streamId").orEmpty(),
|
|
337
|
+
ackId = stringValue(values, "ackId").orEmpty(),
|
|
338
|
+
timestamp = longValue(values, "timestamp"),
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
val values: Map<String, Any>
|
|
342
|
+
get() = buildMap {
|
|
343
|
+
put("type", "keep_alive_ack")
|
|
344
|
+
put("streamId", streamId)
|
|
345
|
+
put("ackId", ackId)
|
|
346
|
+
timestamp?.let { put("timestamp", it) }
|
|
347
|
+
}
|
|
348
|
+
}
|
|
@@ -226,8 +226,7 @@ class SherpaOnnxTranscriber(private val context: Context) {
|
|
|
226
226
|
* Feed PCM audio data (16-bit little endian) into the transcriber. This method should be called
|
|
227
227
|
* continuously with short chunks (e.g., 100-300ms).
|
|
228
228
|
*
|
|
229
|
-
*
|
|
230
|
-
* directly queued for processing without additional VAD checks.
|
|
229
|
+
* Audio is queued directly; microphone VAD gating is not applied in the SDK.
|
|
231
230
|
*/
|
|
232
231
|
fun acceptAudio(pcm16le: ByteArray) {
|
|
233
232
|
if (!running.get()) {
|
|
@@ -235,7 +234,6 @@ class SherpaOnnxTranscriber(private val context: Context) {
|
|
|
235
234
|
return
|
|
236
235
|
}
|
|
237
236
|
|
|
238
|
-
// Directly queue the audio data for processing
|
|
239
237
|
if (!pcmQueue.offer(pcm16le.copyOf())) {
|
|
240
238
|
// Queue is full, drop oldest and try again
|
|
241
239
|
pcmQueue.poll()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk
|
|
2
|
+
|
|
3
|
+
import com.mentra.bluetoothsdk.utils.ControllerTypes
|
|
4
|
+
import com.mentra.bluetoothsdk.utils.DeviceTypes
|
|
5
|
+
|
|
6
|
+
data class MentraBluetoothSdkConfig(
|
|
7
|
+
val deliverCallbacksOnMainThread: Boolean = true,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
class BluetoothException(
|
|
11
|
+
val code: String,
|
|
12
|
+
message: String,
|
|
13
|
+
cause: Throwable? = null,
|
|
14
|
+
) : IllegalStateException(message, cause)
|
|
15
|
+
|
|
16
|
+
enum class DeviceModel(val deviceType: String) {
|
|
17
|
+
G1(DeviceTypes.G1),
|
|
18
|
+
G2(DeviceTypes.G2),
|
|
19
|
+
MENTRA_LIVE(DeviceTypes.LIVE),
|
|
20
|
+
MENTRA_NEX(DeviceTypes.NEX),
|
|
21
|
+
MACH1(DeviceTypes.MACH1),
|
|
22
|
+
Z100(DeviceTypes.Z100),
|
|
23
|
+
FRAME(DeviceTypes.FRAME),
|
|
24
|
+
SIMULATED(DeviceTypes.SIMULATED),
|
|
25
|
+
R1(ControllerTypes.R1);
|
|
26
|
+
|
|
27
|
+
companion object {
|
|
28
|
+
@JvmStatic
|
|
29
|
+
fun fromDeviceType(deviceType: String?): DeviceModel =
|
|
30
|
+
values().firstOrNull { it.deviceType == deviceType } ?: MENTRA_LIVE
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
data class Device(
|
|
35
|
+
val model: DeviceModel,
|
|
36
|
+
val name: String,
|
|
37
|
+
/** Android Bluetooth address when available. */
|
|
38
|
+
val address: String? = null,
|
|
39
|
+
val rssi: Int? = null,
|
|
40
|
+
/** Stable app-facing scan-result key. Do not parse; use typed fields instead. */
|
|
41
|
+
val id: String = address?.takeIf { it.isNotBlank() } ?: "${model.deviceType}:$name",
|
|
42
|
+
) {
|
|
43
|
+
internal fun toMap(): Map<String, Any> =
|
|
44
|
+
buildMap {
|
|
45
|
+
put("id", id)
|
|
46
|
+
put("model", model.deviceType)
|
|
47
|
+
put("name", name)
|
|
48
|
+
address?.takeIf { it.isNotBlank() }?.let {
|
|
49
|
+
put("address", it)
|
|
50
|
+
}
|
|
51
|
+
rssi?.let { put("rssi", it) }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
companion object {
|
|
55
|
+
internal fun fromMap(values: Map<String, Any>): Device? {
|
|
56
|
+
val model = stringValue(values, "model") ?: return null
|
|
57
|
+
val name = stringValue(values, "name") ?: return null
|
|
58
|
+
val address = stringValue(values, "address")?.takeIf { it.isNotBlank() }
|
|
59
|
+
val rssi = numberValue(values, "rssi")
|
|
60
|
+
val id = stringValue(values, "id")?.takeIf { it.isNotBlank() } ?: address ?: "${model}:$name"
|
|
61
|
+
return Device(
|
|
62
|
+
model = DeviceModel.fromDeviceType(model),
|
|
63
|
+
name = name,
|
|
64
|
+
address = address,
|
|
65
|
+
rssi = rssi,
|
|
66
|
+
id = id,
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
data class ConnectOptions(
|
|
73
|
+
val saveAsDefault: Boolean = true,
|
|
74
|
+
val cancelExistingConnectionAttempt: Boolean = true,
|
|
75
|
+
)
|
|
@@ -17,9 +17,14 @@ export type TouchEvent = {
|
|
|
17
17
|
export type HeadUpEvent = {
|
|
18
18
|
up: boolean;
|
|
19
19
|
};
|
|
20
|
-
export type
|
|
21
|
-
type: "
|
|
22
|
-
|
|
20
|
+
export type VoiceActivityDetectionStatusEvent = {
|
|
21
|
+
type: "voice_activity_detection_status";
|
|
22
|
+
voiceActivityDetectionEnabled: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type SpeakingStatusEvent = {
|
|
25
|
+
type: "speaking_status";
|
|
26
|
+
speaking: boolean;
|
|
27
|
+
timestamp: number;
|
|
23
28
|
};
|
|
24
29
|
export type BatteryStatusEvent = {
|
|
25
30
|
type: "battery_status";
|
|
@@ -159,8 +164,6 @@ export type RgbLedControlResponseEvent = {
|
|
|
159
164
|
};
|
|
160
165
|
export type RgbLedAction = "on" | "off";
|
|
161
166
|
export type RgbLedColor = "red" | "green" | "blue" | "orange" | "white";
|
|
162
|
-
/** `"auto"` enables local button photo/video capture; `"manual"` reports button events without local gallery capture. */
|
|
163
|
-
export type GalleryMode = "auto" | "manual";
|
|
164
167
|
export type PhotoSize = "small" | "medium" | "large" | "full";
|
|
165
168
|
export type ButtonPhotoSize = "small" | "medium" | "large";
|
|
166
169
|
export type PhotoCompression = "none" | "medium" | "heavy";
|
|
@@ -187,13 +190,27 @@ export type CameraFovSetting = {
|
|
|
187
190
|
fov: number;
|
|
188
191
|
roiPosition: number;
|
|
189
192
|
};
|
|
193
|
+
type NativeCameraFovSetting = {
|
|
194
|
+
fov: number;
|
|
195
|
+
roi_position: number;
|
|
196
|
+
};
|
|
190
197
|
export type MicPreference = "auto" | "phone" | "glasses" | "bluetooth";
|
|
191
198
|
export type MicMode = "phone" | "glasses" | "bluetoothClassic" | "bluetooth";
|
|
199
|
+
export type PhotoRequestParams = {
|
|
200
|
+
requestId: string;
|
|
201
|
+
appId: string;
|
|
202
|
+
size: PhotoSize;
|
|
203
|
+
webhookUrl: string | null;
|
|
204
|
+
authToken: string | null;
|
|
205
|
+
compress: PhotoCompression;
|
|
206
|
+
sound: boolean;
|
|
207
|
+
exposureTimeNs?: number | null;
|
|
208
|
+
};
|
|
192
209
|
export type StreamVideoConfig = {
|
|
193
210
|
width?: number;
|
|
194
211
|
height?: number;
|
|
195
212
|
bitrate?: number;
|
|
196
|
-
|
|
213
|
+
fps?: number;
|
|
197
214
|
};
|
|
198
215
|
export type StreamAudioConfig = {
|
|
199
216
|
bitrate?: number;
|
|
@@ -251,7 +268,7 @@ export type MicPcmEvent = {
|
|
|
251
268
|
bitsPerSample: 16;
|
|
252
269
|
channels: 1;
|
|
253
270
|
encoding: "pcm_s16le";
|
|
254
|
-
|
|
271
|
+
voiceActivityDetectionEnabled: boolean;
|
|
255
272
|
};
|
|
256
273
|
export type MicLc3Event = {
|
|
257
274
|
type: "mic_lc3";
|
|
@@ -263,7 +280,7 @@ export type MicLc3Event = {
|
|
|
263
280
|
frameSizeBytes: number;
|
|
264
281
|
bitrate: number;
|
|
265
282
|
packetizedFromGlasses: boolean;
|
|
266
|
-
|
|
283
|
+
voiceActivityDetectionEnabled: boolean;
|
|
267
284
|
};
|
|
268
285
|
export type StreamStatusLifecycleState = "initializing" | "streaming" | "stopping" | "stopped";
|
|
269
286
|
export type StreamStatusReconnectState = "reconnecting" | "reconnected" | "reconnect_failed";
|
|
@@ -383,7 +400,8 @@ export type BluetoothSdkModuleEvents = {
|
|
|
383
400
|
button_press: (event: ButtonPressEvent) => void;
|
|
384
401
|
touch_event: (event: TouchEvent) => void;
|
|
385
402
|
head_up: (event: HeadUpEvent) => void;
|
|
386
|
-
|
|
403
|
+
voice_activity_detection_status: (event: VoiceActivityDetectionStatusEvent) => void;
|
|
404
|
+
speaking_status: (event: SpeakingStatusEvent) => void;
|
|
387
405
|
battery_status: (event: BatteryStatusEvent) => void;
|
|
388
406
|
local_transcription: (event: LocalTranscriptionEvent) => void;
|
|
389
407
|
wifi_status_change: (event: WifiStatusChangeEvent) => void;
|
|
@@ -417,7 +435,7 @@ export type BluetoothSdkModuleEvents = {
|
|
|
417
435
|
miniapp_selected: (event: MiniappSelectedEvent) => void;
|
|
418
436
|
};
|
|
419
437
|
export type PublicGlassesStatus = Omit<GlassesStatus, "otaUpdateAvailable" | "otaProgress" | "otaInProgress" | "otaVersionUrl">;
|
|
420
|
-
export type PublicBluetoothStatus = Pick<BluetoothStatus, "searching" | "searchingController" | "systemMicUnavailable" | "micRanking" | "currentMic" | "searchResults" | "wifiScanResults" | "lastLog" | "otherBtConnected" | "
|
|
438
|
+
export type PublicBluetoothStatus = Pick<BluetoothStatus, "searching" | "searchingController" | "systemMicUnavailable" | "micRanking" | "currentMic" | "searchResults" | "wifiScanResults" | "lastLog" | "otherBtConnected" | "galleryModeEnabled">;
|
|
421
439
|
export type BluetoothSdkEventMap = {
|
|
422
440
|
log: LogEvent;
|
|
423
441
|
device_discovered: Device;
|
|
@@ -428,7 +446,8 @@ export type BluetoothSdkEventMap = {
|
|
|
428
446
|
button_press: ButtonPressEvent;
|
|
429
447
|
touch_event: TouchEvent;
|
|
430
448
|
head_up: HeadUpEvent;
|
|
431
|
-
|
|
449
|
+
voice_activity_detection_status: VoiceActivityDetectionStatusEvent;
|
|
450
|
+
speaking_status: SpeakingStatusEvent;
|
|
432
451
|
battery_status: BatteryStatusEvent;
|
|
433
452
|
local_transcription: LocalTranscriptionEvent;
|
|
434
453
|
wifi_status_change: WifiStatusChangeEvent;
|
|
@@ -479,20 +498,21 @@ export interface BluetoothSdkPublicModule {
|
|
|
479
498
|
sendWifiCredentials(ssid: string, password: string): Promise<void>;
|
|
480
499
|
forgetWifiNetwork(ssid: string): Promise<void>;
|
|
481
500
|
setHotspotState(enabled: boolean): Promise<void>;
|
|
482
|
-
|
|
501
|
+
setGalleryModeEnabled(enabled: boolean): Promise<void>;
|
|
502
|
+
setVoiceActivityDetectionEnabled(enabled: boolean): Promise<void>;
|
|
483
503
|
setButtonPhotoSettings(size: ButtonPhotoSize): Promise<void>;
|
|
484
|
-
setButtonVideoRecordingSettings(width: number, height: number,
|
|
504
|
+
setButtonVideoRecordingSettings(width: number, height: number, fps: number): Promise<void>;
|
|
485
505
|
setButtonCameraLed(enabled: boolean): Promise<void>;
|
|
486
506
|
setButtonMaxRecordingTime(minutes: number): Promise<void>;
|
|
487
507
|
setCameraFov(fov: CameraFov): Promise<void>;
|
|
488
508
|
queryGalleryStatus(): Promise<void>;
|
|
489
|
-
requestPhoto(
|
|
509
|
+
requestPhoto(params: PhotoRequestParams): Promise<void>;
|
|
490
510
|
startVideoRecording(requestId: string, save: boolean, sound: boolean): Promise<void>;
|
|
491
511
|
stopVideoRecording(requestId: string): Promise<void>;
|
|
492
512
|
startStream(params: StreamStartRequest): Promise<void>;
|
|
493
513
|
stopStream(): Promise<void>;
|
|
494
514
|
keepStreamAlive(params: StreamKeepAliveRequest): Promise<void>;
|
|
495
|
-
setMicState(enabled: boolean, useGlassesMic?: boolean,
|
|
515
|
+
setMicState(enabled: boolean, useGlassesMic?: boolean, sendTranscript?: boolean, sendLc3Data?: boolean): Promise<void>;
|
|
496
516
|
setPreferredMic(preferredMic: MicPreference): Promise<void>;
|
|
497
517
|
setOwnAppAudioPlaying(playing: boolean): Promise<void>;
|
|
498
518
|
getGlassesMediaVolume(): Promise<GlassesMediaVolumeGetResult>;
|
|
@@ -533,6 +553,7 @@ export interface OtaProgress {
|
|
|
533
553
|
export interface GlassesStatus {
|
|
534
554
|
connection: GlassesConnectionStatus;
|
|
535
555
|
micEnabled: boolean;
|
|
556
|
+
voiceActivityDetectionEnabled: boolean;
|
|
536
557
|
bluetoothClassicConnected: boolean;
|
|
537
558
|
signalStrength: number;
|
|
538
559
|
/** Milliseconds since epoch when signalStrength was last refreshed by the phone BLE stack. */
|
|
@@ -588,6 +609,10 @@ export interface Device {
|
|
|
588
609
|
name: string;
|
|
589
610
|
/** Platform address/identifier when available: Android Bluetooth address, iOS CoreBluetooth identifier. */
|
|
590
611
|
address?: string;
|
|
612
|
+
/**
|
|
613
|
+
* Optional scan signal strength. It may be undefined at first discovery and
|
|
614
|
+
* appear in a later scan update when the platform reports RSSI metadata.
|
|
615
|
+
*/
|
|
591
616
|
rssi?: number;
|
|
592
617
|
}
|
|
593
618
|
export interface ConnectOptions {
|
|
@@ -635,7 +660,7 @@ export interface BluetoothStatus {
|
|
|
635
660
|
wifiScanResults: WifiSearchResult[];
|
|
636
661
|
lastLog: string[];
|
|
637
662
|
otherBtConnected: boolean;
|
|
638
|
-
|
|
663
|
+
galleryModeEnabled: boolean;
|
|
639
664
|
}
|
|
640
665
|
export type BluetoothSettingsUpdate = Partial<{
|
|
641
666
|
auth_email: string;
|
|
@@ -652,23 +677,23 @@ export type BluetoothSettingsUpdate = Partial<{
|
|
|
652
677
|
dashboard_height: number;
|
|
653
678
|
dashboard_depth: number;
|
|
654
679
|
menu_apps: DashboardMenuItem[] | CoreDashboardMenuItem[] | Array<Record<string, unknown>> | null;
|
|
655
|
-
|
|
680
|
+
gallery_mode: boolean;
|
|
681
|
+
voice_activity_detection_enabled: boolean;
|
|
656
682
|
button_photo_size: ButtonPhotoSize;
|
|
657
683
|
button_video_settings: {
|
|
658
684
|
width: number;
|
|
659
685
|
height: number;
|
|
660
|
-
|
|
686
|
+
fps: number;
|
|
661
687
|
};
|
|
662
688
|
button_video_width: number;
|
|
663
689
|
button_video_height: number;
|
|
664
690
|
button_video_fps: number;
|
|
665
691
|
button_camera_led: boolean;
|
|
666
692
|
button_max_recording_time: number;
|
|
667
|
-
camera_fov:
|
|
693
|
+
camera_fov: NativeCameraFovSetting;
|
|
668
694
|
should_send_pcm: boolean;
|
|
669
695
|
should_send_lc3: boolean;
|
|
670
696
|
should_send_transcript: boolean;
|
|
671
|
-
bypass_vad: boolean;
|
|
672
697
|
offline_mode: boolean;
|
|
673
698
|
offline_captions_running: boolean;
|
|
674
699
|
local_stt_fallback_active: boolean;
|
|
@@ -681,4 +706,5 @@ export type BluetoothSettingsUpdate = Partial<{
|
|
|
681
706
|
controller_device_name: string;
|
|
682
707
|
controller_address: string;
|
|
683
708
|
}>;
|
|
709
|
+
export {};
|
|
684
710
|
//# sourceMappingURL=BluetoothSdk.types.d.ts.map
|