@mentra/bluetooth-sdk 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +29 -13
  2. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +15 -7
  3. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +1 -0
  4. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +34 -46
  5. package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +125 -0
  6. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +187 -0
  7. package/android/src/main/java/com/mentra/bluetoothsdk/connection/ConnectionModels.kt +78 -0
  8. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +60 -0
  9. package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +132 -0
  10. package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +39 -0
  11. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +605 -0
  12. package/android/src/main/java/com/mentra/bluetoothsdk/status/RuntimeState.kt +199 -0
  13. package/android/src/main/java/com/mentra/bluetoothsdk/status/WifiHotspotStatus.kt +173 -0
  14. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +348 -0
  15. package/android/src/main/java/com/mentra/bluetoothsdk/types/DeviceModels.kt +75 -0
  16. package/build/BluetoothSdk.types.d.ts +4 -0
  17. package/build/BluetoothSdk.types.d.ts.map +1 -1
  18. package/build/BluetoothSdk.types.js.map +1 -1
  19. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  20. package/build/_private/BluetoothSdkModule.js +3 -0
  21. package/build/_private/BluetoothSdkModule.js.map +1 -1
  22. package/ios/BluetoothSdkModule.swift +4 -4
  23. package/ios/Source/Audio/AudioModels.swift +159 -0
  24. package/ios/Source/Camera/CameraModels.swift +246 -0
  25. package/ios/Source/Connection/ScanSession.swift +27 -0
  26. package/ios/Source/DeviceStore.swift +1 -0
  27. package/ios/Source/Errors/BluetoothError.swift +19 -0
  28. package/ios/Source/Events/BluetoothEvents.swift +115 -0
  29. package/ios/Source/Internal/BluetoothAvailability.swift +58 -0
  30. package/ios/Source/Internal/ValueParsing.swift +90 -0
  31. package/ios/Source/MentraBluetoothSDK.swift +25 -2140
  32. package/ios/Source/Requests/DisplayRequests.swift +58 -0
  33. package/ios/Source/Status/DeviceStatus.swift +463 -0
  34. package/ios/Source/Status/RuntimeState.swift +350 -0
  35. package/ios/Source/Status/WifiHotspotStatus.swift +326 -0
  36. package/ios/Source/Streaming/StreamModels.swift +436 -0
  37. package/ios/Source/Types/DeviceModels.swift +134 -0
  38. package/ios/Source/sgcs/G2.swift +97 -36
  39. package/package.json +1 -1
  40. package/src/BluetoothSdk.types.ts +4 -0
  41. package/src/_private/BluetoothSdkModule.ts +3 -0
  42. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +0 -1810
@@ -0,0 +1,436 @@
1
+ import Foundation
2
+
3
+ public struct StreamVideoConfig {
4
+ public let width: Int?
5
+ public let height: Int?
6
+ public let bitrate: Int?
7
+ public let frameRate: Int?
8
+
9
+ public init(
10
+ width: Int? = nil,
11
+ height: Int? = nil,
12
+ bitrate: Int? = nil,
13
+ frameRate: Int? = nil
14
+ ) {
15
+ self.width = width
16
+ self.height = height
17
+ self.bitrate = bitrate
18
+ self.frameRate = frameRate
19
+ }
20
+
21
+ var dictionary: [String: Any] {
22
+ var values: [String: Any] = [:]
23
+ if let width { values["width"] = width }
24
+ if let height { values["height"] = height }
25
+ if let bitrate { values["bitrate"] = bitrate }
26
+ if let frameRate { values["frameRate"] = frameRate }
27
+ return values
28
+ }
29
+
30
+ init?(values: [String: Any]?) {
31
+ guard let values else { return nil }
32
+ self.init(
33
+ width: intValue(values["width"]),
34
+ height: intValue(values["height"]),
35
+ bitrate: intValue(values["bitrate"]),
36
+ frameRate: intValue(values["frameRate"])
37
+ )
38
+ }
39
+ }
40
+
41
+ public struct StreamAudioConfig {
42
+ public let bitrate: Int?
43
+ public let sampleRate: Int?
44
+ public let echoCancellation: Bool?
45
+ public let noiseSuppression: Bool?
46
+
47
+ public init(
48
+ bitrate: Int? = nil,
49
+ sampleRate: Int? = nil,
50
+ echoCancellation: Bool? = nil,
51
+ noiseSuppression: Bool? = nil
52
+ ) {
53
+ self.bitrate = bitrate
54
+ self.sampleRate = sampleRate
55
+ self.echoCancellation = echoCancellation
56
+ self.noiseSuppression = noiseSuppression
57
+ }
58
+
59
+ var dictionary: [String: Any] {
60
+ var values: [String: Any] = [:]
61
+ if let bitrate { values["bitrate"] = bitrate }
62
+ if let sampleRate { values["sampleRate"] = sampleRate }
63
+ if let echoCancellation { values["echoCancellation"] = echoCancellation }
64
+ if let noiseSuppression { values["noiseSuppression"] = noiseSuppression }
65
+ return values
66
+ }
67
+
68
+ init?(values: [String: Any]?) {
69
+ guard let values else { return nil }
70
+ self.init(
71
+ bitrate: intValue(values["bitrate"]),
72
+ sampleRate: intValue(values["sampleRate"]),
73
+ echoCancellation: values["echoCancellation"] as? Bool,
74
+ noiseSuppression: values["noiseSuppression"] as? Bool
75
+ )
76
+ }
77
+ }
78
+
79
+ public struct StreamRequest {
80
+ public let streamUrl: String
81
+ public let streamId: String
82
+ public let keepAlive: Bool
83
+ public let keepAliveIntervalSeconds: Int
84
+ public let sound: Bool
85
+ public let video: StreamVideoConfig?
86
+ public let audio: StreamAudioConfig?
87
+ public let extraValues: [String: Any]
88
+
89
+ public init(
90
+ streamUrl: String,
91
+ streamId: String = "",
92
+ keepAlive: Bool = true,
93
+ keepAliveIntervalSeconds: Int = 15,
94
+ sound: Bool = true,
95
+ video: StreamVideoConfig? = nil,
96
+ audio: StreamAudioConfig? = nil,
97
+ extraValues: [String: Any] = [:]
98
+ ) {
99
+ self.streamUrl = streamUrl
100
+ self.streamId = streamId
101
+ self.keepAlive = keepAlive
102
+ self.keepAliveIntervalSeconds = keepAliveIntervalSeconds
103
+ self.sound = sound
104
+ self.video = video
105
+ self.audio = audio
106
+ self.extraValues = extraValues
107
+ }
108
+
109
+ init(values: [String: Any]) {
110
+ self.init(
111
+ streamUrl: values["streamUrl"] as? String
112
+ ?? values["rtmpUrl"] as? String
113
+ ?? values["srtUrl"] as? String
114
+ ?? values["whipUrl"] as? String
115
+ ?? "",
116
+ streamId: values["streamId"] as? String ?? "",
117
+ keepAlive: values["keepAlive"] as? Bool ?? true,
118
+ keepAliveIntervalSeconds: intValue(values["keepAliveIntervalSeconds"]) ?? 15,
119
+ sound: values["sound"] as? Bool ?? true,
120
+ video: StreamVideoConfig(values: values["video"] as? [String: Any]),
121
+ audio: StreamAudioConfig(values: values["audio"] as? [String: Any]),
122
+ extraValues: values
123
+ )
124
+ }
125
+
126
+ public var values: [String: Any] {
127
+ var values = extraValues
128
+ values["type"] = "start_stream"
129
+ values["streamUrl"] = streamUrl
130
+ values["streamId"] = streamId
131
+ values["keepAlive"] = keepAlive
132
+ values["keepAliveIntervalSeconds"] = keepAliveIntervalSeconds
133
+ // The camera light is a privacy indicator and cannot be disabled by SDK callers.
134
+ values["flash"] = true
135
+ values["sound"] = sound
136
+ if let videoValues = video?.dictionary, !videoValues.isEmpty {
137
+ values["video"] = videoValues
138
+ }
139
+ if let audioValues = audio?.dictionary, !audioValues.isEmpty {
140
+ values["audio"] = audioValues
141
+ }
142
+ return values
143
+ }
144
+ }
145
+
146
+ public struct StreamKeepAliveRequest {
147
+ public let streamId: String
148
+ public let ackId: String
149
+ public let extraValues: [String: Any]
150
+
151
+ public init(streamId: String, ackId: String, extraValues: [String: Any] = [:]) {
152
+ self.streamId = streamId
153
+ self.ackId = ackId
154
+ self.extraValues = extraValues
155
+ }
156
+
157
+ init(values: [String: Any]) {
158
+ self.init(
159
+ streamId: values["streamId"] as? String ?? "",
160
+ ackId: values["ackId"] as? String ?? "",
161
+ extraValues: values
162
+ )
163
+ }
164
+
165
+ public var values: [String: Any] {
166
+ var values = extraValues
167
+ values["type"] = "keep_stream_alive"
168
+ values["streamId"] = streamId
169
+ values["ackId"] = ackId
170
+ return values
171
+ }
172
+ }
173
+
174
+ public enum StreamState: String, Equatable {
175
+ case initializing
176
+ case streaming
177
+ case stopping
178
+ case stopped
179
+ case reconnecting
180
+ case reconnected
181
+ case reconnectFailed = "reconnect_failed"
182
+ case error
183
+
184
+ fileprivate static func from(_ value: String?) -> StreamState? {
185
+ switch value?.lowercased() {
186
+ case "initializing", "starting", "connecting":
187
+ return .initializing
188
+ case "streaming", "streaming_started", "active":
189
+ return .streaming
190
+ case "stopping":
191
+ return .stopping
192
+ case "stopped", "not_streaming", "disconnected", "timeout":
193
+ return .stopped
194
+ case "reconnecting":
195
+ return .reconnecting
196
+ case "reconnected":
197
+ return .reconnected
198
+ case "reconnect_failed":
199
+ return .reconnectFailed
200
+ case "error", "error_not_streaming":
201
+ return .error
202
+ default:
203
+ return nil
204
+ }
205
+ }
206
+ }
207
+
208
+ public enum StreamStatusKind: String, Equatable {
209
+ case lifecycle
210
+ case reconnect
211
+ case error
212
+ case snapshot
213
+ }
214
+
215
+ public enum StreamStatus: CustomStringConvertible, Equatable {
216
+ case lifecycle(state: StreamState, streamId: String?, timestamp: Int?)
217
+ case reconnecting(streamId: String?, attempt: Int, maxAttempts: Int, reason: String, timestamp: Int?)
218
+ case reconnected(streamId: String?, attempt: Int, timestamp: Int?)
219
+ case reconnectFailed(streamId: String?, maxAttempts: Int, timestamp: Int?)
220
+ case error(streamId: String?, errorDetails: String, timestamp: Int?)
221
+ case snapshot(state: StreamState, streaming: Bool, reconnecting: Bool, streamId: String?, attempt: Int?, timestamp: Int?)
222
+
223
+ public init(values: [String: Any]) {
224
+ let rawState = stringValue(values, "status")
225
+ let streamId = stringValue(values, "streamId")
226
+ let timestamp = intValue(values["timestamp"])
227
+ let attempt = optionalIntValue(values, "attempt")
228
+ let maxAttempts = optionalIntValue(values, "maxAttempts") ?? 0
229
+
230
+ if hasAnyKey(values, "streaming") || hasAnyKey(values, "reconnecting") {
231
+ let streaming = boolValue(values, "streaming") == true
232
+ let reconnecting = boolValue(values, "reconnecting") == true
233
+ let snapshotState: StreamState = reconnecting ? .reconnecting : (streaming ? .streaming : .stopped)
234
+ self = .snapshot(
235
+ state: snapshotState,
236
+ streaming: streaming,
237
+ reconnecting: reconnecting,
238
+ streamId: streamId,
239
+ attempt: attempt,
240
+ timestamp: timestamp
241
+ )
242
+ return
243
+ }
244
+
245
+ guard let state = StreamState.from(rawState) else {
246
+ self = .error(
247
+ streamId: streamId,
248
+ errorDetails: rawState.map { "Unknown stream status: \($0)" } ?? "Missing stream status",
249
+ timestamp: timestamp
250
+ )
251
+ return
252
+ }
253
+
254
+ switch state {
255
+ case .reconnecting:
256
+ self = .reconnecting(
257
+ streamId: streamId,
258
+ attempt: attempt ?? 0,
259
+ maxAttempts: maxAttempts,
260
+ reason: stringValue(values, "reason") ?? "",
261
+ timestamp: timestamp
262
+ )
263
+ case .reconnected:
264
+ self = .reconnected(streamId: streamId, attempt: attempt ?? 0, timestamp: timestamp)
265
+ case .reconnectFailed:
266
+ self = .reconnectFailed(streamId: streamId, maxAttempts: maxAttempts, timestamp: timestamp)
267
+ case .error:
268
+ self = .error(
269
+ streamId: streamId,
270
+ errorDetails: stringValue(values, "errorDetails")
271
+ ?? (rawState == "error_not_streaming" ? "not_streaming" : "Unknown stream error"),
272
+ timestamp: timestamp
273
+ )
274
+ default:
275
+ self = .lifecycle(state: state, streamId: streamId, timestamp: timestamp)
276
+ }
277
+ }
278
+
279
+ public var kind: StreamStatusKind {
280
+ switch self {
281
+ case .lifecycle:
282
+ .lifecycle
283
+ case .reconnecting, .reconnected, .reconnectFailed:
284
+ .reconnect
285
+ case .error:
286
+ .error
287
+ case .snapshot:
288
+ .snapshot
289
+ }
290
+ }
291
+
292
+ public var state: StreamState {
293
+ switch self {
294
+ case let .lifecycle(state, _, _):
295
+ state
296
+ case .reconnecting:
297
+ .reconnecting
298
+ case .reconnected:
299
+ .reconnected
300
+ case .reconnectFailed:
301
+ .reconnectFailed
302
+ case .error:
303
+ .error
304
+ case let .snapshot(state, _, _, _, _, _):
305
+ state
306
+ }
307
+ }
308
+
309
+ public var streamId: String? {
310
+ switch self {
311
+ case let .lifecycle(_, streamId, _),
312
+ let .reconnecting(streamId, _, _, _, _),
313
+ let .reconnected(streamId, _, _),
314
+ let .reconnectFailed(streamId, _, _),
315
+ let .error(streamId, _, _),
316
+ let .snapshot(_, _, _, streamId, _, _):
317
+ streamId
318
+ }
319
+ }
320
+
321
+ public var timestamp: Int? {
322
+ switch self {
323
+ case let .lifecycle(_, _, timestamp),
324
+ let .reconnecting(_, _, _, _, timestamp),
325
+ let .reconnected(_, _, timestamp),
326
+ let .reconnectFailed(_, _, timestamp),
327
+ let .error(_, _, timestamp),
328
+ let .snapshot(_, _, _, _, _, timestamp):
329
+ timestamp
330
+ }
331
+ }
332
+
333
+ public var values: [String: Any] {
334
+ var values: [String: Any] = [
335
+ "kind": kind.rawValue,
336
+ "status": state.rawValue,
337
+ ]
338
+ if let streamId, !streamId.isEmpty {
339
+ values["streamId"] = streamId
340
+ }
341
+ if let timestamp {
342
+ values["timestamp"] = timestamp
343
+ }
344
+
345
+ switch self {
346
+ case .lifecycle:
347
+ break
348
+ case let .reconnecting(_, attempt, maxAttempts, reason, _):
349
+ values["attempt"] = attempt
350
+ values["maxAttempts"] = maxAttempts
351
+ values["reason"] = reason
352
+ case let .reconnected(_, attempt, _):
353
+ values["attempt"] = attempt
354
+ case let .reconnectFailed(_, maxAttempts, _):
355
+ values["maxAttempts"] = maxAttempts
356
+ case let .error(_, errorDetails, _):
357
+ values["errorDetails"] = errorDetails
358
+ case let .snapshot(_, streaming, reconnecting, _, attempt, _):
359
+ values["streaming"] = streaming
360
+ values["reconnecting"] = reconnecting
361
+ if let attempt {
362
+ values["attempt"] = attempt
363
+ }
364
+ }
365
+
366
+ return values
367
+ }
368
+
369
+ public var description: String {
370
+ "StreamStatus(kind: \(kind.rawValue), status: \(state.rawValue), streamId: \(streamId ?? "none"))"
371
+ }
372
+ }
373
+
374
+ public struct StreamStatusEvent: CustomStringConvertible {
375
+ public let status: StreamStatus
376
+
377
+ public init(status: StreamStatus) {
378
+ self.status = status
379
+ }
380
+
381
+ public init(values: [String: Any]) {
382
+ self.status = StreamStatus(values: values)
383
+ }
384
+
385
+ public var state: StreamState {
386
+ status.state
387
+ }
388
+
389
+ public var streamId: String? {
390
+ status.streamId
391
+ }
392
+
393
+ public var values: [String: Any] {
394
+ var values = status.values
395
+ values["type"] = "stream_status"
396
+ return values
397
+ }
398
+
399
+ public var description: String {
400
+ "StreamStatusEvent(kind: \(status.kind.rawValue), status: \(state.rawValue), streamId: \(streamId ?? "none"))"
401
+ }
402
+ }
403
+
404
+ public struct KeepAliveAckEvent: CustomStringConvertible, Equatable {
405
+ public let streamId: String
406
+ public let ackId: String
407
+ public let timestamp: Int?
408
+
409
+ public init(streamId: String, ackId: String, timestamp: Int? = nil) {
410
+ self.streamId = streamId
411
+ self.ackId = ackId
412
+ self.timestamp = timestamp
413
+ }
414
+
415
+ public init(values: [String: Any]) {
416
+ self.streamId = stringValue(values, "streamId") ?? ""
417
+ self.ackId = stringValue(values, "ackId") ?? ""
418
+ self.timestamp = intValue(values["timestamp"])
419
+ }
420
+
421
+ public var values: [String: Any] {
422
+ var values: [String: Any] = [
423
+ "type": "keep_alive_ack",
424
+ "streamId": streamId,
425
+ "ackId": ackId,
426
+ ]
427
+ if let timestamp {
428
+ values["timestamp"] = timestamp
429
+ }
430
+ return values
431
+ }
432
+
433
+ public var description: String {
434
+ "KeepAliveAckEvent(streamId: \(streamId), ackId: \(ackId))"
435
+ }
436
+ }
@@ -0,0 +1,134 @@
1
+ import Foundation
2
+
3
+ public struct MentraBluetoothSDKConfiguration {
4
+ public static let `default` = MentraBluetoothSDKConfiguration()
5
+
6
+ public init() {}
7
+ }
8
+
9
+ public enum DeviceModel: String {
10
+ case g1
11
+ case g2
12
+ case mentraLive
13
+ case mentraNex
14
+ case mach1
15
+ case z100
16
+ case frame
17
+ case simulated
18
+ case r1
19
+
20
+ public var deviceType: String {
21
+ switch self {
22
+ case .g1:
23
+ DeviceTypes.G1
24
+ case .g2:
25
+ DeviceTypes.G2
26
+ case .mentraLive:
27
+ DeviceTypes.LIVE
28
+ case .mentraNex:
29
+ DeviceTypes.NEX
30
+ case .mach1:
31
+ DeviceTypes.MACH1
32
+ case .z100:
33
+ DeviceTypes.Z100
34
+ case .frame:
35
+ DeviceTypes.FRAME
36
+ case .simulated:
37
+ DeviceTypes.SIMULATED
38
+ case .r1:
39
+ ControllerTypes.R1
40
+ }
41
+ }
42
+
43
+ public static func fromDeviceType(_ deviceType: String?) -> DeviceModel {
44
+ switch deviceType {
45
+ case DeviceTypes.G1:
46
+ .g1
47
+ case DeviceTypes.G2:
48
+ .g2
49
+ case DeviceTypes.LIVE:
50
+ .mentraLive
51
+ case DeviceTypes.NEX:
52
+ .mentraNex
53
+ case DeviceTypes.MACH1:
54
+ .mach1
55
+ case DeviceTypes.Z100:
56
+ .z100
57
+ case DeviceTypes.FRAME:
58
+ .frame
59
+ case DeviceTypes.SIMULATED:
60
+ .simulated
61
+ case ControllerTypes.R1:
62
+ .r1
63
+ default:
64
+ .mentraLive
65
+ }
66
+ }
67
+ }
68
+
69
+ public struct Device: Identifiable, Equatable, CustomStringConvertible {
70
+ public let model: DeviceModel
71
+ public let name: String
72
+ /// CoreBluetooth identifier when available.
73
+ public let identifier: String?
74
+ public let rssi: Int?
75
+ /// Stable app-facing scan-result key. Do not parse; use typed fields instead.
76
+ public let id: String
77
+
78
+ public init(
79
+ model: DeviceModel,
80
+ name: String,
81
+ identifier: String? = nil,
82
+ rssi: Int? = nil,
83
+ id: String? = nil
84
+ ) {
85
+ self.model = model
86
+ self.name = name
87
+ self.identifier = identifier
88
+ self.rssi = rssi
89
+ self.id = id ?? identifier.flatMap { $0.isEmpty ? nil : $0 } ?? "\(model.deviceType):\(name)"
90
+ }
91
+
92
+ public var description: String {
93
+ "Device(model: \(model), name: \(name))"
94
+ }
95
+
96
+ var dictionary: [String: Any] {
97
+ var values: [String: Any] = [
98
+ "id": id,
99
+ "model": model.deviceType,
100
+ "name": name,
101
+ ]
102
+ if let identifier, !identifier.isEmpty {
103
+ values["address"] = identifier
104
+ }
105
+ if let rssi {
106
+ values["rssi"] = rssi
107
+ }
108
+ return values
109
+ }
110
+
111
+ init?(values: [String: Any]) {
112
+ guard let model = stringValue(values, "model") else { return nil }
113
+ guard let name = stringValue(values, "name") else { return nil }
114
+ let identifier = stringValue(values, "address").flatMap { $0.isEmpty ? nil : $0 }
115
+ let rssi = intValue(values["rssi"])
116
+ self.init(
117
+ model: DeviceModel.fromDeviceType(model),
118
+ name: name,
119
+ identifier: identifier,
120
+ rssi: rssi,
121
+ id: stringValue(values, "id")
122
+ )
123
+ }
124
+ }
125
+
126
+ public struct ConnectOptions {
127
+ public let saveAsDefault: Bool
128
+ public let cancelExistingConnectionAttempt: Bool
129
+
130
+ public init(saveAsDefault: Bool = true, cancelExistingConnectionAttempt: Bool = true) {
131
+ self.saveAsDefault = saveAsDefault
132
+ self.cancelExistingConnectionAttempt = cancelExistingConnectionAttempt
133
+ }
134
+ }