@mentra/bluetooth-sdk 0.1.12 → 0.1.14
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 +30 -11
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +47 -12
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +75 -29
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +1 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +208 -45
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdkDebug.kt +14 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/ObservableStore.kt +20 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +153 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +80 -14
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +4 -16
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +4 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +90 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +3831 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +20 -28
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/{Mach1.java → Mach1.kt} +514 -561
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +8674 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +12 -3
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +5 -17
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +9 -20
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +0 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +16 -33
- package/build/BluetoothSdk.types.d.ts +48 -17
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +9 -9
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +0 -6
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/photoRequestPayload.d.ts +3 -1
- package/build/_private/photoRequestPayload.d.ts.map +1 -1
- package/build/_private/photoRequestPayload.js +46 -2
- package/build/_private/photoRequestPayload.js.map +1 -1
- package/build/debug.d.ts +3 -0
- package/build/debug.d.ts.map +1 -0
- package/build/debug.js +8 -0
- package/build/debug.js.map +1 -0
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +8 -8
- package/build/index.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +44 -68
- package/ios/Source/BluetoothSdkDefaults.swift +23 -3
- package/ios/Source/DeviceManager.swift +38 -33
- package/ios/Source/DeviceStore.swift +5 -5
- package/ios/Source/MentraBluetoothSDK.swift +217 -50
- package/ios/Source/MentraBluetoothSDKDebug.swift +12 -0
- package/ios/Source/ObservableStore.swift +11 -0
- package/ios/Source/OtaManifest.swift +170 -0
- package/ios/Source/camera/CameraModels.swift +219 -13
- package/ios/Source/controllers/ControllerManager.swift +3 -7
- package/ios/Source/controllers/R1.swift +3 -7
- package/ios/Source/sgcs/Frame.swift +0 -2
- package/ios/Source/sgcs/G1.swift +7 -8
- package/ios/Source/sgcs/G2.swift +21 -20
- package/ios/Source/sgcs/Mach1.swift +7 -8
- package/ios/Source/sgcs/MentraLive.swift +132 -63
- package/ios/Source/sgcs/MentraNex.swift +7 -8
- package/ios/Source/sgcs/SGCManager.swift +7 -10
- package/ios/Source/sgcs/Simulated.swift +8 -8
- package/ios/Source/status/DeviceStatus.swift +1 -9
- package/ios/Source/streaming/StreamModels.swift +7 -32
- package/package.json +6 -1
- package/src/BluetoothSdk.types.ts +49 -17
- package/src/_private/BluetoothSdkModule.ts +9 -17
- package/src/_private/photoRequestPayload.ts +48 -3
- package/src/debug.ts +9 -0
- package/src/index.ts +19 -10
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +0 -3974
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +0 -7434
|
@@ -1,28 +1,46 @@
|
|
|
1
1
|
package com.mentra.bluetoothsdk
|
|
2
2
|
|
|
3
|
+
import org.json.JSONObject
|
|
4
|
+
|
|
3
5
|
enum class PhotoSize(val value: String) {
|
|
4
|
-
|
|
6
|
+
LOW("low"),
|
|
5
7
|
MEDIUM("medium"),
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
HIGH("high"),
|
|
9
|
+
MAX("max");
|
|
8
10
|
|
|
9
11
|
companion object {
|
|
10
12
|
@JvmStatic
|
|
11
|
-
fun
|
|
12
|
-
|
|
13
|
+
fun normalizeLegacy(value: String?): String =
|
|
14
|
+
when (value) {
|
|
15
|
+
"small" -> LOW.value
|
|
16
|
+
"large" -> HIGH.value
|
|
17
|
+
"full" -> MAX.value
|
|
18
|
+
else -> value ?: MEDIUM.value
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@JvmStatic
|
|
22
|
+
fun fromValue(value: String?): PhotoSize {
|
|
23
|
+
val normalized = normalizeLegacy(value)
|
|
24
|
+
return values().firstOrNull { it.value == normalized } ?: MEDIUM
|
|
25
|
+
}
|
|
13
26
|
}
|
|
14
27
|
}
|
|
15
28
|
|
|
16
29
|
enum class ButtonPhotoSize(val value: String) {
|
|
17
|
-
|
|
30
|
+
LOW("low"),
|
|
18
31
|
MEDIUM("medium"),
|
|
19
|
-
|
|
32
|
+
HIGH("high"),
|
|
20
33
|
MAX("max");
|
|
21
34
|
|
|
22
35
|
companion object {
|
|
23
36
|
@JvmStatic
|
|
24
|
-
fun
|
|
25
|
-
|
|
37
|
+
fun normalizeLegacy(value: String?): String = PhotoSize.normalizeLegacy(value)
|
|
38
|
+
|
|
39
|
+
@JvmStatic
|
|
40
|
+
fun fromValue(value: String?): ButtonPhotoSize {
|
|
41
|
+
val normalized = normalizeLegacy(value)
|
|
42
|
+
return values().firstOrNull { it.value == normalized } ?: MEDIUM
|
|
43
|
+
}
|
|
26
44
|
}
|
|
27
45
|
}
|
|
28
46
|
|
|
@@ -38,11 +56,22 @@ enum class PhotoCompression(val value: String) {
|
|
|
38
56
|
}
|
|
39
57
|
}
|
|
40
58
|
|
|
41
|
-
data class
|
|
42
|
-
val size:
|
|
59
|
+
data class PhotoCaptureDefaults(
|
|
60
|
+
val size: PhotoSize? = null,
|
|
61
|
+
val mfnr: Boolean? = null,
|
|
62
|
+
val zsl: Boolean? = null,
|
|
63
|
+
val noiseReduction: Boolean? = null,
|
|
64
|
+
val edgeEnhancement: Boolean? = null,
|
|
65
|
+
val ispDigitalGain: Int? = null,
|
|
66
|
+
val ispAnalogGain: String? = null,
|
|
67
|
+
val aeExposureDivisor: Int? = null,
|
|
68
|
+
val isoCap: Int? = null,
|
|
69
|
+
val compress: String? = null,
|
|
70
|
+
val sound: Boolean? = null,
|
|
71
|
+
val resetCaptureTuning: Boolean = false,
|
|
43
72
|
)
|
|
44
73
|
|
|
45
|
-
data class
|
|
74
|
+
data class VideoRecordingDefaults(
|
|
46
75
|
val width: Int,
|
|
47
76
|
val height: Int,
|
|
48
77
|
val fps: Int,
|
|
@@ -138,13 +167,21 @@ data class PhotoRequest @JvmOverloads constructor(
|
|
|
138
167
|
val webhookUrl: String,
|
|
139
168
|
val authToken: String? = null,
|
|
140
169
|
val compress: PhotoCompression = PhotoCompression.MEDIUM,
|
|
141
|
-
val flash: Boolean = true,
|
|
142
170
|
val save: Boolean = false,
|
|
143
171
|
val sound: Boolean = true,
|
|
144
172
|
/** Sensor exposure time for this capture only (ns), or null for auto exposure */
|
|
145
173
|
val exposureTimeNs: Double? = null,
|
|
146
174
|
/** Sensor ISO for this capture only. Only used when exposureTimeNs enables manual exposure. */
|
|
147
175
|
val iso: Int? = null,
|
|
176
|
+
val aeExposureDivisor: Int? = null,
|
|
177
|
+
val isoCap: Int? = null,
|
|
178
|
+
val noiseReduction: Boolean? = null,
|
|
179
|
+
val edgeEnhancement: Boolean? = null,
|
|
180
|
+
val mfnr: Boolean? = null,
|
|
181
|
+
val zsl: Boolean? = null,
|
|
182
|
+
val ispDigitalGain: Int? = null,
|
|
183
|
+
val ispAnalogGain: String? = null,
|
|
184
|
+
val resetCaptureTuning: Boolean? = null,
|
|
148
185
|
) {
|
|
149
186
|
companion object {
|
|
150
187
|
/** Mirrors iOS `BluetoothSdkModule` defaults for keys omitted from the JS bridge. */
|
|
@@ -167,6 +204,12 @@ data class PhotoRequest @JvmOverloads constructor(
|
|
|
167
204
|
}
|
|
168
205
|
else -> null
|
|
169
206
|
}
|
|
207
|
+
val aeDivisor =
|
|
208
|
+
numberValue(values, "aeExposureDivisor")?.takeIf { it > 1 }
|
|
209
|
+
val isoCap = numberValue(values, "isoCap")?.takeIf { it > 0 }
|
|
210
|
+
val ispDigitalGain = numberValue(values, "ispDigitalGain")
|
|
211
|
+
val ispAnalogGain = stringValue(values, "ispAnalogGain")
|
|
212
|
+
|
|
170
213
|
return PhotoRequest(
|
|
171
214
|
requestId = stringValue(values, "requestId", "request_id").orEmpty(),
|
|
172
215
|
appId = stringValue(values, "appId", "app_id").orEmpty(),
|
|
@@ -174,13 +217,36 @@ data class PhotoRequest @JvmOverloads constructor(
|
|
|
174
217
|
webhookUrl = stringValue(values, "webhookUrl", "webhook_url").orEmpty(),
|
|
175
218
|
authToken = stringValue(values, "authToken", "auth_token")?.takeIf { it.isNotBlank() },
|
|
176
219
|
compress = PhotoCompression.fromValue(stringValue(values, "compress") ?: "none"),
|
|
177
|
-
flash = boolValue(values, "flash") ?: true,
|
|
178
220
|
save = boolValue(values, "save", "saveToGallery") ?: false,
|
|
179
221
|
sound = boolValue(values, "sound") ?: true,
|
|
180
222
|
exposureTimeNs = exposureTimeNs,
|
|
181
223
|
iso = iso,
|
|
224
|
+
aeExposureDivisor = aeDivisor,
|
|
225
|
+
isoCap = isoCap,
|
|
226
|
+
noiseReduction = boolValue(values, "noiseReduction"),
|
|
227
|
+
edgeEnhancement = boolValue(values, "edgeEnhancement"),
|
|
228
|
+
mfnr = boolValue(values, "mfnr"),
|
|
229
|
+
zsl = boolValue(values, "zsl"),
|
|
230
|
+
ispDigitalGain = ispDigitalGain,
|
|
231
|
+
ispAnalogGain = ispAnalogGain,
|
|
182
232
|
)
|
|
183
233
|
}
|
|
234
|
+
|
|
235
|
+
/** Adds optional scan-mode tuning fields to a `take_photo` JSON payload. */
|
|
236
|
+
@JvmStatic
|
|
237
|
+
fun appendScanFields(
|
|
238
|
+
json: JSONObject,
|
|
239
|
+
request: PhotoRequest,
|
|
240
|
+
) {
|
|
241
|
+
request.aeExposureDivisor?.let { json.put("aeExposureDivisor", it) }
|
|
242
|
+
request.isoCap?.let { json.put("isoCap", it) }
|
|
243
|
+
request.noiseReduction?.let { json.put("noiseReduction", it) }
|
|
244
|
+
request.edgeEnhancement?.let { json.put("edgeEnhancement", it) }
|
|
245
|
+
request.mfnr?.let { json.put("mfnr", it) }
|
|
246
|
+
request.zsl?.let { json.put("zsl", it) }
|
|
247
|
+
request.ispDigitalGain?.let { json.put("ispDigitalGain", it) }
|
|
248
|
+
request.ispAnalogGain?.let { json.put("ispAnalogGain", it) }
|
|
249
|
+
}
|
|
184
250
|
}
|
|
185
251
|
}
|
|
186
252
|
|
|
@@ -2,6 +2,7 @@ package com.mentra.bluetoothsdk.controllers
|
|
|
2
2
|
|
|
3
3
|
import com.mentra.bluetoothsdk.BluetoothSdkDefaults
|
|
4
4
|
import com.mentra.bluetoothsdk.DeviceStore
|
|
5
|
+
import com.mentra.bluetoothsdk.PhotoRequest
|
|
5
6
|
|
|
6
7
|
abstract class ControllerManager {
|
|
7
8
|
@JvmField var type: String = ""
|
|
@@ -15,30 +16,17 @@ abstract class ControllerManager {
|
|
|
15
16
|
abstract fun sendJson(jsonOriginal: Map<String, Any>, wakeUp: Boolean, requireAck: Boolean)
|
|
16
17
|
|
|
17
18
|
// Camera & Media
|
|
18
|
-
abstract fun requestPhoto(
|
|
19
|
-
requestId: String,
|
|
20
|
-
appId: String,
|
|
21
|
-
size: String?,
|
|
22
|
-
webhookUrl: String?,
|
|
23
|
-
authToken: String?,
|
|
24
|
-
compress: String?,
|
|
25
|
-
flash: Boolean,
|
|
26
|
-
save: Boolean,
|
|
27
|
-
sound: Boolean,
|
|
28
|
-
exposureTimeNs: Long?,
|
|
29
|
-
iso: Int?,
|
|
30
|
-
)
|
|
19
|
+
abstract fun requestPhoto(request: PhotoRequest)
|
|
31
20
|
abstract fun startStream(message: Map<String, Any>)
|
|
32
21
|
abstract fun stopStream()
|
|
33
22
|
abstract fun sendStreamKeepAlive(message: Map<String, Any>)
|
|
34
|
-
abstract fun startVideoRecording(requestId: String, save: Boolean,
|
|
23
|
+
abstract fun startVideoRecording(requestId: String, save: Boolean, sound: Boolean)
|
|
35
24
|
abstract fun stopVideoRecording(requestId: String)
|
|
36
25
|
|
|
37
26
|
// Button Settings
|
|
38
27
|
abstract fun sendButtonPhotoSettings()
|
|
39
28
|
abstract fun sendButtonVideoRecordingSettings()
|
|
40
29
|
abstract fun sendButtonMaxRecordingTime()
|
|
41
|
-
abstract fun sendButtonCameraLedSetting()
|
|
42
30
|
|
|
43
31
|
// Display Control
|
|
44
32
|
abstract fun setBrightness(level: Int, autoMode: Boolean)
|
|
@@ -87,7 +75,7 @@ abstract class ControllerManager {
|
|
|
87
75
|
abstract fun sendWifiCredentials(ssid: String, password: String)
|
|
88
76
|
abstract fun forgetWifiNetwork(ssid: String)
|
|
89
77
|
abstract fun sendHotspotState(enabled: Boolean)
|
|
90
|
-
abstract fun sendOtaStart()
|
|
78
|
+
abstract fun sendOtaStart(otaVersionUrl: String? = null)
|
|
91
79
|
|
|
92
80
|
// User Context (for crash reporting)
|
|
93
81
|
abstract fun sendUserEmailToGlasses(email: String)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.mentra.bluetoothsdk.controllers
|
|
2
2
|
|
|
3
|
+
import com.mentra.bluetoothsdk.PhotoRequest
|
|
3
4
|
import android.Manifest
|
|
4
5
|
import android.bluetooth.BluetoothAdapter
|
|
5
6
|
import android.bluetooth.BluetoothDevice
|
|
@@ -861,12 +862,8 @@ class R1 : ControllerManager() {
|
|
|
861
862
|
override fun setMicEnabled(enabled: Boolean) {}
|
|
862
863
|
override fun sortMicRanking(list: MutableList<String>): MutableList<String> = list
|
|
863
864
|
override fun sendJson(jsonOriginal: Map<String, Any>, wakeUp: Boolean, requireAck: Boolean) {}
|
|
864
|
-
override fun requestPhoto(
|
|
865
|
-
|
|
866
|
-
authToken: String?, compress: String?, flash: Boolean, save: Boolean, sound: Boolean,
|
|
867
|
-
exposureTimeNs: Long?, iso: Int?,
|
|
868
|
-
) {}
|
|
869
|
-
override fun startVideoRecording(requestId: String, save: Boolean, flash: Boolean, sound: Boolean) {}
|
|
865
|
+
override fun requestPhoto(request: PhotoRequest) {}
|
|
866
|
+
override fun startVideoRecording(requestId: String, save: Boolean, sound: Boolean) {}
|
|
870
867
|
override fun stopVideoRecording(requestId: String) {}
|
|
871
868
|
override fun startStream(message: Map<String, Any>) {}
|
|
872
869
|
override fun stopStream() {}
|
|
@@ -874,7 +871,6 @@ class R1 : ControllerManager() {
|
|
|
874
871
|
override fun sendButtonPhotoSettings() {}
|
|
875
872
|
override fun sendButtonVideoRecordingSettings() {}
|
|
876
873
|
override fun sendButtonMaxRecordingTime() {}
|
|
877
|
-
override fun sendButtonCameraLedSetting() {}
|
|
878
874
|
override fun setBrightness(level: Int, autoMode: Boolean) {}
|
|
879
875
|
override fun clearDisplay() {}
|
|
880
876
|
override fun sendTextWall(text: String) {}
|
|
@@ -901,7 +897,7 @@ class R1 : ControllerManager() {
|
|
|
901
897
|
override fun sendWifiCredentials(ssid: String, password: String) {}
|
|
902
898
|
override fun forgetWifiNetwork(ssid: String) {}
|
|
903
899
|
override fun sendHotspotState(enabled: Boolean) {}
|
|
904
|
-
override fun sendOtaStart() {}
|
|
900
|
+
override fun sendOtaStart(otaVersionUrl: String?) {}
|
|
905
901
|
override fun sendUserEmailToGlasses(email: String) {}
|
|
906
902
|
override fun queryGalleryStatus() {}
|
|
907
903
|
override fun sendGalleryMode() {}
|
|
@@ -49,6 +49,7 @@ class PhoneMic private constructor(private val context: Context) {
|
|
|
49
49
|
private const val FOCUS_REGAIN_DELAY_MS = 500L
|
|
50
50
|
private const val SAMSUNG_MIC_TEST_DELAY_MS = 500L
|
|
51
51
|
private const val MIC_SWITCH_DELAY_MS = 300L // Time for DeviceManager to switch mics
|
|
52
|
+
private const val AUDIO_SOURCE_HOTWORD = 1999
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
// Audio recording components
|
|
@@ -185,7 +186,42 @@ class PhoneMic private constructor(private val context: Context) {
|
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
fun isRecordingWithMode(mode: String): Boolean {
|
|
188
|
-
|
|
189
|
+
if (!isRecording.get()) {
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
val record = audioRecord
|
|
194
|
+
val activelyRecording =
|
|
195
|
+
try {
|
|
196
|
+
record?.recordingState == AudioRecord.RECORDSTATE_RECORDING
|
|
197
|
+
} catch (e: Exception) {
|
|
198
|
+
Bridge.log("MIC: Failed to read AudioRecord state: ${e.message}")
|
|
199
|
+
false
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!activelyRecording) {
|
|
203
|
+
Bridge.log("MIC: Stale recording state detected, cleaning up")
|
|
204
|
+
cleanUpRecording()
|
|
205
|
+
abandonAudioFocus()
|
|
206
|
+
currentMicMode = ""
|
|
207
|
+
return false
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return currentMicMode == mode
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
fun hasBlockingMicInterruption(): Boolean {
|
|
214
|
+
val callActive =
|
|
215
|
+
isPhoneCallActive ||
|
|
216
|
+
try {
|
|
217
|
+
(telephonyManager?.callState ?: TelephonyManager.CALL_STATE_IDLE) !=
|
|
218
|
+
TelephonyManager.CALL_STATE_IDLE
|
|
219
|
+
} catch (e: Exception) {
|
|
220
|
+
Bridge.log("MIC: Failed to read telephony call state: ${e.message}")
|
|
221
|
+
true
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return callActive || isExternalAudioActive
|
|
189
225
|
}
|
|
190
226
|
|
|
191
227
|
/**
|
|
@@ -544,7 +580,23 @@ class PhoneMic private constructor(private val context: Context) {
|
|
|
544
580
|
audioRecord?.let { ourAudioSessionIds.add(it.audioSessionId) }
|
|
545
581
|
|
|
546
582
|
// Start recording
|
|
547
|
-
audioRecord
|
|
583
|
+
val record = audioRecord ?: return false
|
|
584
|
+
try {
|
|
585
|
+
record.startRecording()
|
|
586
|
+
} catch (e: Exception) {
|
|
587
|
+
Bridge.log("MIC: AudioRecord.startRecording() failed: ${e.message}")
|
|
588
|
+
audioRecord?.release()
|
|
589
|
+
audioRecord = null
|
|
590
|
+
return false
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (record.recordingState != AudioRecord.RECORDSTATE_RECORDING) {
|
|
594
|
+
Bridge.log("MIC: AudioRecord did not enter recording state")
|
|
595
|
+
record.release()
|
|
596
|
+
audioRecord = null
|
|
597
|
+
return false
|
|
598
|
+
}
|
|
599
|
+
|
|
548
600
|
isRecording.set(true)
|
|
549
601
|
|
|
550
602
|
// Start recording thread
|
|
@@ -577,15 +629,22 @@ class PhoneMic private constructor(private val context: Context) {
|
|
|
577
629
|
|
|
578
630
|
val audioBuffer = ShortArray(bufferSize / 2)
|
|
579
631
|
|
|
632
|
+
var unexpectedStopReason: String? = null
|
|
633
|
+
|
|
580
634
|
while (isRecording.get()) {
|
|
581
635
|
// Capture local reference to avoid use-after-free if cleanUpRecording()
|
|
582
636
|
// releases the AudioRecord while we're in read()
|
|
583
|
-
val record = audioRecord
|
|
637
|
+
val record = audioRecord
|
|
638
|
+
if (record == null) {
|
|
639
|
+
unexpectedStopReason = "AudioRecord missing"
|
|
640
|
+
break
|
|
641
|
+
}
|
|
584
642
|
|
|
585
643
|
val readResult = try {
|
|
586
644
|
record.read(audioBuffer, 0, audioBuffer.size)
|
|
587
645
|
} catch (e: Exception) {
|
|
588
646
|
Bridge.log("MIC: AudioRecord.read() exception: ${e.message}")
|
|
647
|
+
unexpectedStopReason = "AudioRecord.read() exception: ${e.message}"
|
|
589
648
|
break
|
|
590
649
|
}
|
|
591
650
|
|
|
@@ -603,9 +662,16 @@ class PhoneMic private constructor(private val context: Context) {
|
|
|
603
662
|
} else if (readResult < 0) {
|
|
604
663
|
// AudioRecord error (ERROR, ERROR_INVALID_OPERATION, ERROR_BAD_VALUE, ERROR_DEAD_OBJECT)
|
|
605
664
|
Bridge.log("MIC: AudioRecord.read() returned error: $readResult")
|
|
665
|
+
unexpectedStopReason = "AudioRecord.read() error: $readResult"
|
|
606
666
|
break
|
|
607
667
|
}
|
|
608
668
|
}
|
|
669
|
+
|
|
670
|
+
unexpectedStopReason?.let { reason ->
|
|
671
|
+
if (isRecording.get()) {
|
|
672
|
+
mainHandler.post { handleUnexpectedRecordingStop(reason) }
|
|
673
|
+
}
|
|
674
|
+
}
|
|
609
675
|
}
|
|
610
676
|
.apply {
|
|
611
677
|
name = "AudioRecordingThread"
|
|
@@ -613,6 +679,25 @@ class PhoneMic private constructor(private val context: Context) {
|
|
|
613
679
|
}
|
|
614
680
|
}
|
|
615
681
|
|
|
682
|
+
private fun handleUnexpectedRecordingStop(reason: String) {
|
|
683
|
+
if (!isRecording.compareAndSet(true, false)) {
|
|
684
|
+
return
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
Bridge.log("MIC: Recording stopped unexpectedly: $reason")
|
|
688
|
+
cleanUpRecording()
|
|
689
|
+
abandonAudioFocus()
|
|
690
|
+
|
|
691
|
+
if (audioManager.isBluetoothScoOn) {
|
|
692
|
+
audioManager.stopBluetoothSco()
|
|
693
|
+
audioManager.isBluetoothScoOn = false
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
audioManager.mode = AudioManager.MODE_NORMAL
|
|
697
|
+
currentMicMode = ""
|
|
698
|
+
notifyDeviceManager("recording_stopped", getAvailableInputDevices().values.toList())
|
|
699
|
+
}
|
|
700
|
+
|
|
616
701
|
private fun cleanUpRecording() {
|
|
617
702
|
isRecording.set(false)
|
|
618
703
|
|
|
@@ -778,7 +863,8 @@ class PhoneMic private constructor(private val context: Context) {
|
|
|
778
863
|
// Filter out our own recordings
|
|
779
864
|
val otherAppRecordings =
|
|
780
865
|
configs.filter { config ->
|
|
781
|
-
!ourAudioSessionIds.contains(config.clientAudioSessionId)
|
|
866
|
+
!ourAudioSessionIds.contains(config.clientAudioSessionId) &&
|
|
867
|
+
config.clientAudioSource != AUDIO_SOURCE_HOTWORD
|
|
782
868
|
}
|
|
783
869
|
|
|
784
870
|
val wasExternalActive = isExternalAudioActive
|