@siteed/expo-audio-studio 2.12.3 → 2.13.1
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/CHANGELOG.md +11 -1
- package/android/build.gradle +11 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/net/siteed/audiostream/AudioDeviceManager.kt +266 -42
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +55 -1
- package/app.plugin.js +3 -1
- package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
- package/build/cjs/AudioDeviceManager.js +229 -40
- package/build/cjs/AudioDeviceManager.js.map +1 -1
- package/build/cjs/WebRecorder.web.js +1 -0
- package/build/cjs/WebRecorder.web.js.map +1 -1
- package/build/cjs/hooks/useAudioDevices.js +30 -5
- package/build/cjs/hooks/useAudioDevices.js.map +1 -1
- package/build/cjs/useAudioRecorder.js +53 -8
- package/build/cjs/useAudioRecorder.js.map +1 -1
- package/build/cjs/workers/InlineFeaturesExtractor.web.js +8 -2
- package/build/cjs/workers/InlineFeaturesExtractor.web.js.map +1 -1
- package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
- package/build/esm/AudioDeviceManager.js +229 -40
- package/build/esm/AudioDeviceManager.js.map +1 -1
- package/build/esm/WebRecorder.web.js +1 -0
- package/build/esm/WebRecorder.web.js.map +1 -1
- package/build/esm/hooks/useAudioDevices.js +31 -6
- package/build/esm/hooks/useAudioDevices.js.map +1 -1
- package/build/esm/useAudioRecorder.js +54 -9
- package/build/esm/useAudioRecorder.js.map +1 -1
- package/build/esm/workers/InlineFeaturesExtractor.web.js +8 -2
- package/build/esm/workers/InlineFeaturesExtractor.web.js.map +1 -1
- package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts +1 -0
- package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -1
- package/build/types/AudioDeviceManager.d.ts +82 -2
- package/build/types/AudioDeviceManager.d.ts.map +1 -1
- package/build/types/WebRecorder.web.d.ts.map +1 -1
- package/build/types/hooks/useAudioDevices.d.ts +1 -0
- package/build/types/hooks/useAudioDevices.d.ts.map +1 -1
- package/build/types/useAudioRecorder.d.ts.map +1 -1
- package/build/types/workers/InlineFeaturesExtractor.web.d.ts +1 -1
- package/build/types/workers/InlineFeaturesExtractor.web.d.ts.map +1 -1
- package/ios/AudioDeviceManager.swift +21 -9
- package/ios/ExpoAudioStreamModule.swift +33 -1
- package/package.json +7 -6
- package/plugin/build/index.cjs +194 -0
- package/plugin/build/index.d.cts +1 -0
- package/plugin/build/index.js +7 -6
- package/plugin/src/index.ts +8 -8
- package/src/AudioAnalysis/AudioAnalysis.types.ts +1 -0
- package/src/AudioDeviceManager.ts +290 -59
- package/src/WebRecorder.web.ts +1 -0
- package/src/hooks/useAudioDevices.ts +39 -6
- package/src/useAudioRecorder.tsx +103 -9
- package/src/workers/InlineFeaturesExtractor.web.tsx +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
## [2.13.1] - 2025-06-09
|
|
12
|
+
### Changed
|
|
13
|
+
- feat(investigation): resolve Issue #251 - comprehensive sub-100ms audio events analysis (#270) ([4813f1e](https://github.com/deeeed/expo-audio-stream/commit/4813f1ef05f3856b58ec8fde95b7b8909feb513d))
|
|
14
|
+
- fix(deps): update expo-modules-core peer dependency for Expo SDK 53 compatibility ([40b946f](https://github.com/deeeed/expo-audio-stream/commit/40b946f83eecd3fdcedfe7a2cbac62f1207a4ff0))
|
|
15
|
+
- docs: updated docs site ([8a01a97](https://github.com/deeeed/expo-audio-stream/commit/8a01a97ebee927a2dfa0a7cb40b11329410509d2))
|
|
16
|
+
## [2.13.0] - 2025-06-09
|
|
17
|
+
### Changed
|
|
18
|
+
- feat(expo-audio-studio): enhance device detection and management system ([97ceef0](https://github.com/deeeed/expo-audio-stream/commit/97ceef003ddb8eb5246cda8a5a00ddc75bf665a0))
|
|
11
19
|
## [2.12.3] - 2025-06-07
|
|
12
20
|
### Changed
|
|
13
21
|
- refactor(expo-audio-studio): adjust audio focus request timing in AudioRecorderManager ([317367c](https://github.com/deeeed/expo-audio-stream/commit/317367cb29fa09016aa73884f2f51e9cfdee1086))
|
|
@@ -304,7 +312,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
304
312
|
- Feature: Audio features extraction during recording.
|
|
305
313
|
- Feature: Consistent WAV PCM recording format across all platforms.
|
|
306
314
|
|
|
307
|
-
[unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.
|
|
315
|
+
[unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.13.1...HEAD
|
|
316
|
+
[2.13.1]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.13.0...@siteed/expo-audio-studio@2.13.1
|
|
317
|
+
[2.13.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.12.3...@siteed/expo-audio-studio@2.13.0
|
|
308
318
|
[2.12.3]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.12.2...@siteed/expo-audio-studio@2.12.3
|
|
309
319
|
[2.12.2]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.12.1...@siteed/expo-audio-studio@2.12.2
|
|
310
320
|
[2.12.1]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.12.0...@siteed/expo-audio-studio@2.12.1
|
package/android/build.gradle
CHANGED
|
@@ -32,6 +32,7 @@ buildscript {
|
|
|
32
32
|
|
|
33
33
|
repositories {
|
|
34
34
|
mavenCentral()
|
|
35
|
+
google()
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
dependencies {
|
|
@@ -91,12 +92,22 @@ android {
|
|
|
91
92
|
|
|
92
93
|
repositories {
|
|
93
94
|
mavenCentral()
|
|
95
|
+
google()
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
dependencies {
|
|
97
99
|
implementation project(':expo-modules-core')
|
|
98
100
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
|
|
99
101
|
|
|
102
|
+
// Add AndroidX dependencies
|
|
103
|
+
implementation 'androidx.core:core-ktx:1.10.1'
|
|
104
|
+
implementation 'androidx.annotation:annotation:1.6.0'
|
|
105
|
+
implementation 'androidx.appcompat:appcompat:1.6.1'
|
|
106
|
+
|
|
107
|
+
// Add Kotlinx Coroutines for main code
|
|
108
|
+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
|
|
109
|
+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
|
|
110
|
+
|
|
100
111
|
// Add testing dependencies
|
|
101
112
|
testImplementation 'junit:junit:4.13.2'
|
|
102
113
|
testImplementation 'org.jetbrains.kotlin:kotlin-test:1.8.10'
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
|
|
7
7
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
8
8
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
|
9
|
+
|
|
10
|
+
<!-- Bluetooth permissions for device detection -->
|
|
11
|
+
<uses-permission android:name="android.permission.BLUETOOTH" />
|
|
12
|
+
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
|
13
|
+
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
|
14
|
+
|
|
15
|
+
<!-- USB permission for USB device detection -->
|
|
16
|
+
<uses-permission android:name="android.permission.USB_PERMISSION" />
|
|
9
17
|
|
|
10
18
|
<application>
|
|
11
19
|
<receiver
|
|
@@ -21,6 +21,7 @@ import net.siteed.audiostream.LogUtils
|
|
|
21
21
|
import kotlinx.coroutines.CoroutineScope
|
|
22
22
|
import kotlinx.coroutines.Dispatchers
|
|
23
23
|
import kotlinx.coroutines.launch
|
|
24
|
+
import kotlinx.coroutines.delay
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Constants not available in all Android versions
|
|
@@ -62,6 +63,12 @@ class AudioDeviceManager(private val context: Context) {
|
|
|
62
63
|
// Delegate for handling device disconnection
|
|
63
64
|
var delegate: AudioDeviceManagerDelegate? = null
|
|
64
65
|
|
|
66
|
+
// Simple callback for device connections
|
|
67
|
+
var onDeviceConnected: ((String) -> Unit)? = null
|
|
68
|
+
|
|
69
|
+
// Simple callback for device disconnections
|
|
70
|
+
var onDeviceDisconnected: ((String) -> Unit)? = null
|
|
71
|
+
|
|
65
72
|
// Audio manager for accessing device information
|
|
66
73
|
private val audioManager: AudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
67
74
|
|
|
@@ -1113,6 +1120,9 @@ class AudioDeviceManager(private val context: Context) {
|
|
|
1113
1120
|
addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)
|
|
1114
1121
|
addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED)
|
|
1115
1122
|
}
|
|
1123
|
+
|
|
1124
|
+
// Bluetooth SCO state changes - to detect when microphone becomes available
|
|
1125
|
+
addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)
|
|
1116
1126
|
}
|
|
1117
1127
|
|
|
1118
1128
|
deviceReceiver = object : BroadcastReceiver() {
|
|
@@ -1153,6 +1163,28 @@ class AudioDeviceManager(private val context: Context) {
|
|
|
1153
1163
|
}
|
|
1154
1164
|
}
|
|
1155
1165
|
}
|
|
1166
|
+
} else if (state == 1) { // Plugged in
|
|
1167
|
+
LogUtils.d(CLASS_NAME, "Wired headset connected: $name")
|
|
1168
|
+
|
|
1169
|
+
// For M+ find the actual new device
|
|
1170
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1171
|
+
// Find the newly connected wired device
|
|
1172
|
+
val audioDevices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)
|
|
1173
|
+
val wiredDevice = audioDevices.firstOrNull {
|
|
1174
|
+
(it.type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
|
|
1175
|
+
it.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES) &&
|
|
1176
|
+
getDeviceName(it).contains(name, ignoreCase = true)
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
if (wiredDevice != null) {
|
|
1180
|
+
val connectedDeviceId = wiredDevice.id.toString()
|
|
1181
|
+
LogUtils.d(CLASS_NAME, "Found connected wired device: $name (ID: $connectedDeviceId)")
|
|
1182
|
+
handleDeviceConnection(connectedDeviceId)
|
|
1183
|
+
}
|
|
1184
|
+
} else {
|
|
1185
|
+
// Legacy handling for older Android
|
|
1186
|
+
handleDeviceConnection("1")
|
|
1187
|
+
}
|
|
1156
1188
|
}
|
|
1157
1189
|
}
|
|
1158
1190
|
|
|
@@ -1184,6 +1216,59 @@ class AudioDeviceManager(private val context: Context) {
|
|
|
1184
1216
|
}
|
|
1185
1217
|
}
|
|
1186
1218
|
|
|
1219
|
+
BluetoothDevice.ACTION_ACL_CONNECTED -> {
|
|
1220
|
+
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
1221
|
+
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
|
|
1222
|
+
} else {
|
|
1223
|
+
@Suppress("DEPRECATION")
|
|
1224
|
+
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if (device != null) {
|
|
1228
|
+
LogUtils.d(CLASS_NAME, "Bluetooth device connected: ${device.name}")
|
|
1229
|
+
|
|
1230
|
+
// For M+ find the actual new device
|
|
1231
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1232
|
+
val actualDevice = findBluetoothDevice(device)
|
|
1233
|
+
if (actualDevice != null) {
|
|
1234
|
+
val connectedDeviceId = actualDevice.id.toString()
|
|
1235
|
+
LogUtils.d(CLASS_NAME, "Found connected Bluetooth device: ${device.name} (ID: $connectedDeviceId)")
|
|
1236
|
+
handleDeviceConnection(connectedDeviceId)
|
|
1237
|
+
} else {
|
|
1238
|
+
LogUtils.d(CLASS_NAME, "Bluetooth device ${device.name} connected but not found in audio device list - attempting to activate SCO")
|
|
1239
|
+
|
|
1240
|
+
// Try to activate Bluetooth SCO to make microphone available
|
|
1241
|
+
if (!audioManager.isBluetoothScoOn) {
|
|
1242
|
+
LogUtils.d(CLASS_NAME, "Starting Bluetooth SCO to activate microphone for ${device.name}")
|
|
1243
|
+
audioManager.startBluetoothSco()
|
|
1244
|
+
|
|
1245
|
+
// Give SCO time to activate, then check again
|
|
1246
|
+
coroutineScope.launch {
|
|
1247
|
+
delay(2000) // Wait 2 seconds
|
|
1248
|
+
|
|
1249
|
+
val scoDevice = findBluetoothDevice(device)
|
|
1250
|
+
if (scoDevice != null) {
|
|
1251
|
+
val activatedDeviceId = scoDevice.id.toString()
|
|
1252
|
+
LogUtils.d(CLASS_NAME, "Bluetooth SCO activated for device: ${device.name} (ID: $activatedDeviceId)")
|
|
1253
|
+
handleDeviceConnection(activatedDeviceId)
|
|
1254
|
+
} else {
|
|
1255
|
+
LogUtils.d(CLASS_NAME, "Bluetooth SCO didn't activate microphone for ${device.name}")
|
|
1256
|
+
// Send generic connection event anyway
|
|
1257
|
+
handleDeviceConnection("bluetooth:${device.address}")
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
} else {
|
|
1261
|
+
// SCO already on, send generic event
|
|
1262
|
+
handleDeviceConnection("bluetooth:${device.address}")
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
} else {
|
|
1266
|
+
// Legacy handling for older Android
|
|
1267
|
+
handleDeviceConnection("2")
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1187
1272
|
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
|
|
1188
1273
|
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
1189
1274
|
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
|
|
@@ -1213,42 +1298,117 @@ class AudioDeviceManager(private val context: Context) {
|
|
|
1213
1298
|
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1)
|
|
1214
1299
|
logAudioState()
|
|
1215
1300
|
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
if (device != null) {
|
|
1226
|
-
deviceId = "2" // Legacy ID for bluetooth
|
|
1227
|
-
deviceName = device.name
|
|
1228
|
-
deviceType = DEVICE_TYPE_BLUETOOTH
|
|
1301
|
+
when (state) {
|
|
1302
|
+
BluetoothAdapter.STATE_CONNECTED -> {
|
|
1303
|
+
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
1304
|
+
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
|
|
1305
|
+
} else {
|
|
1306
|
+
@Suppress("DEPRECATION")
|
|
1307
|
+
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
|
|
1308
|
+
}
|
|
1229
1309
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1310
|
+
if (device != null) {
|
|
1311
|
+
LogUtils.d(CLASS_NAME, "Bluetooth profile connected: ${device.name}")
|
|
1312
|
+
|
|
1313
|
+
// For M+ find the actual new device
|
|
1314
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1315
|
+
val actualDevice = findBluetoothDevice(device)
|
|
1316
|
+
if (actualDevice != null) {
|
|
1317
|
+
val connectedDeviceId = actualDevice.id.toString()
|
|
1318
|
+
LogUtils.d(CLASS_NAME, "Found connected Bluetooth profile device: ${device.name} (ID: $connectedDeviceId)")
|
|
1319
|
+
handleDeviceConnection(connectedDeviceId)
|
|
1320
|
+
} else {
|
|
1321
|
+
LogUtils.d(CLASS_NAME, "Bluetooth profile ${device.name} connected but not found in audio device list - attempting to activate SCO")
|
|
1322
|
+
|
|
1323
|
+
// Try to activate Bluetooth SCO to make microphone available
|
|
1324
|
+
if (!audioManager.isBluetoothScoOn) {
|
|
1325
|
+
LogUtils.d(CLASS_NAME, "Starting Bluetooth SCO to activate microphone for ${device.name}")
|
|
1326
|
+
audioManager.startBluetoothSco()
|
|
1327
|
+
|
|
1328
|
+
// Give SCO time to activate, then check again
|
|
1329
|
+
coroutineScope.launch {
|
|
1330
|
+
delay(2000) // Wait 2 seconds
|
|
1331
|
+
|
|
1332
|
+
val scoDevice = findBluetoothDevice(device)
|
|
1333
|
+
if (scoDevice != null) {
|
|
1334
|
+
val activatedDeviceId = scoDevice.id.toString()
|
|
1335
|
+
LogUtils.d(CLASS_NAME, "Bluetooth SCO activated for device: ${device.name} (ID: $activatedDeviceId)")
|
|
1336
|
+
handleDeviceConnection(activatedDeviceId)
|
|
1337
|
+
} else {
|
|
1338
|
+
LogUtils.d(CLASS_NAME, "Bluetooth SCO didn't activate microphone for ${device.name}")
|
|
1339
|
+
// Send generic connection event anyway
|
|
1340
|
+
handleDeviceConnection("bluetooth:${device.address}")
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
} else {
|
|
1344
|
+
// SCO already on, send generic event
|
|
1345
|
+
handleDeviceConnection("bluetooth:${device.address}")
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
} else {
|
|
1349
|
+
// Legacy handling for older Android
|
|
1350
|
+
handleDeviceConnection("2")
|
|
1235
1351
|
}
|
|
1236
1352
|
}
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
BluetoothAdapter.STATE_DISCONNECTED -> {
|
|
1356
|
+
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
1357
|
+
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
|
|
1358
|
+
} else {
|
|
1359
|
+
@Suppress("DEPRECATION")
|
|
1360
|
+
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
|
|
1361
|
+
}
|
|
1237
1362
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1363
|
+
if (device != null) {
|
|
1364
|
+
deviceId = "2" // Legacy ID for bluetooth
|
|
1365
|
+
deviceName = device.name
|
|
1366
|
+
deviceType = DEVICE_TYPE_BLUETOOTH
|
|
1367
|
+
|
|
1368
|
+
// For M+ get the actual ID
|
|
1369
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1370
|
+
val actualDevice = findBluetoothDevice(device)
|
|
1371
|
+
if (actualDevice != null) {
|
|
1372
|
+
deviceId = actualDevice.id.toString()
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
LogUtils.d(CLASS_NAME, "Bluetooth profile disconnected: ${device.name}, using ID: $deviceId")
|
|
1377
|
+
}
|
|
1378
|
+
// No device info, check if our last device was bluetooth
|
|
1379
|
+
else if (lastSelectedDeviceId != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1380
|
+
val lastDeviceInfo = findDeviceById(lastSelectedDeviceId!!)
|
|
1381
|
+
|
|
1382
|
+
if (lastDeviceInfo?.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
|
|
1383
|
+
deviceId = lastSelectedDeviceId
|
|
1384
|
+
LogUtils.d(CLASS_NAME, "Bluetooth profile disconnected, using last selected device ID: $deviceId")
|
|
1385
|
+
}
|
|
1247
1386
|
}
|
|
1248
1387
|
}
|
|
1249
1388
|
}
|
|
1250
1389
|
}
|
|
1251
1390
|
|
|
1391
|
+
UsbManager.ACTION_USB_DEVICE_ATTACHED, UsbManager.ACTION_USB_ACCESSORY_ATTACHED -> {
|
|
1392
|
+
LogUtils.d(CLASS_NAME, "USB device attached")
|
|
1393
|
+
|
|
1394
|
+
// For M+ find newly connected USB audio devices
|
|
1395
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1396
|
+
val audioDevices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)
|
|
1397
|
+
val usbDevice = audioDevices.firstOrNull {
|
|
1398
|
+
it.type == AudioDeviceInfo.TYPE_USB_DEVICE ||
|
|
1399
|
+
it.type == AudioDeviceInfo.TYPE_USB_HEADSET ||
|
|
1400
|
+
it.type == AudioDeviceInfo.TYPE_USB_ACCESSORY
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
if (usbDevice != null) {
|
|
1404
|
+
val connectedDeviceId = usbDevice.id.toString()
|
|
1405
|
+
val deviceName = getDeviceName(usbDevice)
|
|
1406
|
+
LogUtils.d(CLASS_NAME, "Found connected USB audio device: $deviceName (ID: $connectedDeviceId)")
|
|
1407
|
+
handleDeviceConnection(connectedDeviceId)
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1252
1412
|
UsbManager.ACTION_USB_DEVICE_DETACHED, UsbManager.ACTION_USB_ACCESSORY_DETACHED -> {
|
|
1253
1413
|
LogUtils.d(CLASS_NAME, "USB device detached")
|
|
1254
1414
|
|
|
@@ -1266,28 +1426,74 @@ class AudioDeviceManager(private val context: Context) {
|
|
|
1266
1426
|
}
|
|
1267
1427
|
}
|
|
1268
1428
|
}
|
|
1429
|
+
|
|
1430
|
+
AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED -> {
|
|
1431
|
+
val scoState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1)
|
|
1432
|
+
LogUtils.d(CLASS_NAME, "Bluetooth SCO state changed: $scoState")
|
|
1433
|
+
|
|
1434
|
+
when (scoState) {
|
|
1435
|
+
AudioManager.SCO_AUDIO_STATE_CONNECTED -> {
|
|
1436
|
+
LogUtils.d(CLASS_NAME, "Bluetooth SCO connected - microphone available")
|
|
1437
|
+
|
|
1438
|
+
// Check if any new Bluetooth SCO devices appeared
|
|
1439
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1440
|
+
val audioDevices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)
|
|
1441
|
+
val bluetoothScoDevices = audioDevices.filter {
|
|
1442
|
+
it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
if (bluetoothScoDevices.isNotEmpty()) {
|
|
1446
|
+
// Found Bluetooth SCO device(s), notify about the first one
|
|
1447
|
+
val scoDevice = bluetoothScoDevices.first()
|
|
1448
|
+
val scoDeviceId = scoDevice.id.toString()
|
|
1449
|
+
val scoDeviceName = getDeviceName(scoDevice)
|
|
1450
|
+
LogUtils.d(CLASS_NAME, "Found Bluetooth SCO device: $scoDeviceName (ID: $scoDeviceId)")
|
|
1451
|
+
handleDeviceConnection(scoDeviceId)
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
AudioManager.SCO_AUDIO_STATE_DISCONNECTED -> {
|
|
1457
|
+
LogUtils.d(CLASS_NAME, "Bluetooth SCO disconnected - microphone no longer available")
|
|
1458
|
+
// Note: Device disconnection will be handled by other broadcasts
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
AudioManager.SCO_AUDIO_STATE_CONNECTING -> {
|
|
1462
|
+
LogUtils.d(CLASS_NAME, "Bluetooth SCO connecting...")
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
else -> {
|
|
1466
|
+
LogUtils.d(CLASS_NAME, "Bluetooth SCO state: $scoState")
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1269
1470
|
}
|
|
1270
1471
|
|
|
1271
|
-
//
|
|
1272
|
-
if (deviceId != null
|
|
1273
|
-
LogUtils.d(CLASS_NAME, "
|
|
1472
|
+
// Handle any device disconnection - send events to React Native for device list updates
|
|
1473
|
+
if (deviceId != null) {
|
|
1474
|
+
LogUtils.d(CLASS_NAME, "Device disconnected: $deviceId (selected: ${deviceId == lastSelectedDeviceId})")
|
|
1475
|
+
if (deviceName != null) {
|
|
1476
|
+
LogUtils.d(CLASS_NAME, "Device name: $deviceName, type: $deviceType")
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1274
1479
|
// Log the disconnection for debugging
|
|
1275
1480
|
logDeviceDisconnection(deviceId, action ?: "unknown")
|
|
1276
1481
|
|
|
1277
|
-
//
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1482
|
+
// Send device disconnection event to React Native (for UI updates)
|
|
1483
|
+
handleDeviceDisconnectionEvent(deviceId)
|
|
1484
|
+
|
|
1485
|
+
// If this was the currently selected device, also notify delegate for recording interruption
|
|
1486
|
+
if (deviceId == lastSelectedDeviceId) {
|
|
1487
|
+
LogUtils.d(CLASS_NAME, "Currently selected device disconnected - notifying delegate: $deviceId")
|
|
1488
|
+
// Launch a coroutine to call the suspend function
|
|
1489
|
+
coroutineScope.launch {
|
|
1490
|
+
try {
|
|
1491
|
+
handleDeviceDisconnection(deviceId)
|
|
1492
|
+
} catch (e: Exception) {
|
|
1493
|
+
LogUtils.e(CLASS_NAME, "Error handling device disconnection: ${e.message}", e)
|
|
1494
|
+
}
|
|
1283
1495
|
}
|
|
1284
1496
|
}
|
|
1285
|
-
} else if (deviceId != null) {
|
|
1286
|
-
// Even if not our current device, log for debugging
|
|
1287
|
-
LogUtils.d(CLASS_NAME, "Device disconnected but not currently selected: $deviceId")
|
|
1288
|
-
if (deviceName != null) {
|
|
1289
|
-
LogUtils.d(CLASS_NAME, "Device name: $deviceName, type: $deviceType")
|
|
1290
|
-
}
|
|
1291
1497
|
}
|
|
1292
1498
|
|
|
1293
1499
|
// Force refresh the device list
|
|
@@ -1498,4 +1704,22 @@ class AudioDeviceManager(private val context: Context) {
|
|
|
1498
1704
|
LogUtils.d(CLASS_NAME, "Device disconnected: $deviceId. Pausing recording.")
|
|
1499
1705
|
delegate?.onDeviceDisconnected(deviceId)
|
|
1500
1706
|
}
|
|
1707
|
+
|
|
1708
|
+
/**
|
|
1709
|
+
* Handles audio device connection
|
|
1710
|
+
*/
|
|
1711
|
+
private fun handleDeviceConnection(deviceId: String) {
|
|
1712
|
+
LogUtils.d(CLASS_NAME, "Device connected: $deviceId")
|
|
1713
|
+
onDeviceConnected?.invoke(deviceId)
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
/**
|
|
1717
|
+
* Handles audio device disconnection (for React Native events)
|
|
1718
|
+
*/
|
|
1719
|
+
private fun handleDeviceDisconnectionEvent(deviceId: String) {
|
|
1720
|
+
LogUtils.d(CLASS_NAME, "Device disconnected: $deviceId")
|
|
1721
|
+
onDeviceDisconnected?.invoke(deviceId)
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
|
|
1501
1725
|
}
|
|
@@ -7,6 +7,7 @@ import android.os.Bundle
|
|
|
7
7
|
import android.util.Log
|
|
8
8
|
import android.content.pm.PackageManager
|
|
9
9
|
import androidx.annotation.RequiresApi
|
|
10
|
+
import androidx.core.content.ContextCompat
|
|
10
11
|
import androidx.core.os.bundleOf
|
|
11
12
|
import expo.modules.kotlin.Promise
|
|
12
13
|
import expo.modules.kotlin.modules.Module
|
|
@@ -30,6 +31,7 @@ class ExpoAudioStreamModule : Module(), EventSender {
|
|
|
30
31
|
private var enablePhoneStateHandling: Boolean = false // Default to false until we check manifest
|
|
31
32
|
private var enableNotificationHandling: Boolean = false // Default to false until we check manifest
|
|
32
33
|
private var enableBackgroundAudio: Boolean = false // Default to false until we check manifest
|
|
34
|
+
private var enableDeviceDetection: Boolean = false // Default to false until we check manifest
|
|
33
35
|
private val coroutineScope = CoroutineScope(Dispatchers.Main)
|
|
34
36
|
|
|
35
37
|
private val audioFileHandler by lazy {
|
|
@@ -65,14 +67,19 @@ class ExpoAudioStreamModule : Module(), EventSender {
|
|
|
65
67
|
// Check if background audio is enabled by looking for FOREGROUND_SERVICE_MICROPHONE permission
|
|
66
68
|
enableBackgroundAudio = packageInfo.requestedPermissions?.contains(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE) ?: false
|
|
67
69
|
|
|
70
|
+
// Check if device detection is enabled by looking for BLUETOOTH_CONNECT permission
|
|
71
|
+
enableDeviceDetection = packageInfo.requestedPermissions?.contains(Manifest.permission.BLUETOOTH_CONNECT) ?: false
|
|
72
|
+
|
|
68
73
|
LogUtils.d(CLASS_NAME, "Phone state handling ${if (enablePhoneStateHandling) "enabled" else "disabled"} based on manifest permissions")
|
|
69
74
|
LogUtils.d(CLASS_NAME, "Notification handling ${if (enableNotificationHandling) "enabled" else "disabled"} based on manifest permissions")
|
|
70
75
|
LogUtils.d(CLASS_NAME, "Background audio handling ${if (enableBackgroundAudio) "enabled" else "disabled"} based on manifest permissions")
|
|
76
|
+
LogUtils.d(CLASS_NAME, "Device detection ${if (enableDeviceDetection) "enabled" else "disabled"} based on manifest permissions")
|
|
71
77
|
} catch (e: Exception) {
|
|
72
78
|
LogUtils.e(CLASS_NAME, "Failed to check manifest permissions: ${e.message}", e)
|
|
73
79
|
enablePhoneStateHandling = false
|
|
74
80
|
enableNotificationHandling = false
|
|
75
81
|
enableBackgroundAudio = false
|
|
82
|
+
enableDeviceDetection = false
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
Events(
|
|
@@ -91,6 +98,11 @@ class ExpoAudioStreamModule : Module(), EventSender {
|
|
|
91
98
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && enableBackgroundAudio
|
|
92
99
|
}
|
|
93
100
|
|
|
101
|
+
// Helper function to check if device detection is enabled
|
|
102
|
+
fun isDeviceDetectionEnabled(): Boolean {
|
|
103
|
+
return enableDeviceDetection
|
|
104
|
+
}
|
|
105
|
+
|
|
94
106
|
// Add device-related functions to the module
|
|
95
107
|
|
|
96
108
|
// Gets available audio input devices with an optional refresh parameter
|
|
@@ -169,6 +181,8 @@ class ExpoAudioStreamModule : Module(), EventSender {
|
|
|
169
181
|
return@Function mapOf("success" to success)
|
|
170
182
|
}
|
|
171
183
|
|
|
184
|
+
|
|
185
|
+
|
|
172
186
|
AsyncFunction("prepareRecording") { options: Map<String, Any?>, promise: Promise ->
|
|
173
187
|
try {
|
|
174
188
|
// If notifications are requested but permission not in manifest, modify options
|
|
@@ -263,6 +277,15 @@ class ExpoAudioStreamModule : Module(), EventSender {
|
|
|
263
277
|
permissions.add(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE)
|
|
264
278
|
}
|
|
265
279
|
|
|
280
|
+
// Add device detection permissions if device detection is enabled
|
|
281
|
+
if (isDeviceDetectionEnabled()) {
|
|
282
|
+
// BLUETOOTH_CONNECT is needed on Android 12+ to access device names/addresses
|
|
283
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
284
|
+
permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
|
|
285
|
+
LogUtils.d(CLASS_NAME, "Adding BLUETOOTH_CONNECT permission request for device detection")
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
266
289
|
LogUtils.d(CLASS_NAME, "Requesting permissions: $permissions")
|
|
267
290
|
Permissions.askForPermissionsWithPermissionsManager(
|
|
268
291
|
appContext.permissions,
|
|
@@ -290,6 +313,14 @@ class ExpoAudioStreamModule : Module(), EventSender {
|
|
|
290
313
|
permissions.add(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE)
|
|
291
314
|
}
|
|
292
315
|
|
|
316
|
+
// Add device detection permissions if enabled
|
|
317
|
+
if (isDeviceDetectionEnabled()) {
|
|
318
|
+
// BLUETOOTH_CONNECT is needed on Android 12+ to access device names/addresses
|
|
319
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
320
|
+
permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
293
324
|
Permissions.getPermissionsWithPermissionsManager(
|
|
294
325
|
appContext.permissions,
|
|
295
326
|
promise,
|
|
@@ -909,7 +940,10 @@ class ExpoAudioStreamModule : Module(), EventSender {
|
|
|
909
940
|
val audioDataEncoder = AudioDataEncoder()
|
|
910
941
|
|
|
911
942
|
// Initialize AudioDeviceManager
|
|
943
|
+
LogUtils.d(CLASS_NAME, "🔧 Initializing AudioDeviceManager...")
|
|
944
|
+
LogUtils.d(CLASS_NAME, "🔧 Device detection enabled: $enableDeviceDetection")
|
|
912
945
|
audioDeviceManager = AudioDeviceManager(context)
|
|
946
|
+
LogUtils.d(CLASS_NAME, "🔧 AudioDeviceManager initialized")
|
|
913
947
|
|
|
914
948
|
// Initialize AudioRecorderManager with AudioDeviceManager integration
|
|
915
949
|
audioRecorderManager = AudioRecorderManager.initialize(
|
|
@@ -936,7 +970,7 @@ class ExpoAudioStreamModule : Module(), EventSender {
|
|
|
936
970
|
|
|
937
971
|
// Notify JS about the disconnection
|
|
938
972
|
sendEvent(Constants.DEVICE_CHANGED_EVENT, bundleOf(
|
|
939
|
-
"
|
|
973
|
+
"type" to "deviceDisconnected",
|
|
940
974
|
"deviceId" to deviceId
|
|
941
975
|
))
|
|
942
976
|
} catch (e: Exception) {
|
|
@@ -946,6 +980,26 @@ class ExpoAudioStreamModule : Module(), EventSender {
|
|
|
946
980
|
}
|
|
947
981
|
}
|
|
948
982
|
|
|
983
|
+
// Set up connection callback
|
|
984
|
+
audioDeviceManager.onDeviceConnected = { deviceId ->
|
|
985
|
+
LogUtils.d(CLASS_NAME, "📱 Device connected: $deviceId")
|
|
986
|
+
// Notify JS about the connection
|
|
987
|
+
sendEvent(Constants.DEVICE_CHANGED_EVENT, bundleOf(
|
|
988
|
+
"type" to "deviceConnected",
|
|
989
|
+
"deviceId" to deviceId
|
|
990
|
+
))
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// Set up disconnection callback
|
|
994
|
+
audioDeviceManager.onDeviceDisconnected = { deviceId ->
|
|
995
|
+
LogUtils.d(CLASS_NAME, "📱 Device disconnected: $deviceId")
|
|
996
|
+
// Notify JS about the disconnection
|
|
997
|
+
sendEvent(Constants.DEVICE_CHANGED_EVENT, bundleOf(
|
|
998
|
+
"type" to "deviceDisconnected",
|
|
999
|
+
"deviceId" to deviceId
|
|
1000
|
+
))
|
|
1001
|
+
}
|
|
1002
|
+
|
|
949
1003
|
audioProcessor = AudioProcessor(filesDir)
|
|
950
1004
|
}
|
|
951
1005
|
|
package/app.plugin.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioAnalysis.types.js","sourceRoot":"","sources":["../../../src/AudioAnalysis/AudioAnalysis.types.ts"],"names":[],"mappings":";AAAA,sEAAsE","sourcesContent":["// packages/expo-audio-stream/src/AudioAnalysis/AudioAnalysis.types.ts\n\nimport { BitDepth, ConsoleLike } from '../ExpoAudioStream.types'\n\n/**\n * Represents the configuration for decoding audio data.\n */\nexport interface DecodingConfig {\n /** Target sample rate for decoded audio (Android and Web) */\n targetSampleRate?: number\n /** Target number of channels (Android and Web) */\n targetChannels?: number\n /** Target bit depth (Android and Web) */\n targetBitDepth?: BitDepth\n /** Whether to normalize audio levels (Android and Web) */\n normalizeAudio?: boolean\n}\n\n/**\n * Represents speech-related features extracted from audio.\n */\nexport interface SpeechFeatures {\n isActive: boolean // Whether speech is detected in this segment\n speakerId?: number // Optional speaker identification\n // Could add more speech-related features here like:\n // confidence: number\n // language?: string\n // sentiment?: number\n // etc.\n}\n\n/**\n * Represents various audio features extracted from an audio signal.\n */\nexport interface AudioFeatures {\n energy?: number // The infinite integral of the squared signal, representing the overall energy of the audio.\n mfcc?: number[] // Mel-frequency cepstral coefficients, describing the short-term power spectrum of a sound.\n rms?: number // Root mean square value, indicating the amplitude of the audio signal.\n minAmplitude?: number // Minimum amplitude value in the audio signal.\n maxAmplitude?: number // Maximum amplitude value in the audio signal.\n zcr?: number // Zero-crossing rate, indicating the rate at which the signal changes sign.\n spectralCentroid?: number // The center of mass of the spectrum, indicating the brightness of the sound.\n spectralFlatness?: number // Measure of the flatness of the spectrum, indicating how noise-like the signal is.\n spectralRolloff?: number // The frequency below which a specified percentage (usually 85%) of the total spectral energy lies.\n spectralBandwidth?: number // The width of the spectrum, indicating the range of frequencies present.\n chromagram?: number[] // Chromagram, representing the 12 different pitch classes of the audio.\n tempo?: number // Estimated tempo of the audio signal, measured in beats per minute (BPM).\n hnr?: number // Harmonics-to-noise ratio, indicating the proportion of harmonics to noise in the audio signal.\n melSpectrogram?: number[] // Mel-scaled spectrogram representation of the audio.\n spectralContrast?: number[] // Spectral contrast features representing the difference between peaks and valleys.\n tonnetz?: number[] // Tonal network features representing harmonic relationships.\n pitch?: number // Pitch of the audio signal, measured in Hertz (Hz).\n crc32?: number // crc32 checksum of the audio signal, used to verify the integrity of the audio.\n}\n\n/**\n * Options to specify which audio features to extract.\n * Note: Advanced features (spectral features, chromagram, pitch, etc.) are experimental,\n * especially during live recording, due to high processing requirements.\n */\nexport interface AudioFeaturesOptions {\n // Basic features - well optimized\n energy?: boolean\n rms?: boolean\n zcr?: boolean\n\n // Advanced features - experimental, may impact performance in live recording\n mfcc?: boolean\n spectralCentroid?: boolean\n spectralFlatness?: boolean\n spectralRolloff?: boolean\n spectralBandwidth?: boolean\n chromagram?: boolean\n tempo?: boolean\n hnr?: boolean\n melSpectrogram?: boolean\n spectralContrast?: boolean\n tonnetz?: boolean\n pitch?: boolean\n\n // Utility\n crc32?: boolean\n}\n\n/**\n * Represents a single data point in the audio analysis.\n */\nexport interface DataPoint {\n id: number\n amplitude: number // Peak amplitude for the segment\n rms: number // Root mean square value\n dB: number // dBFS (decibels relative to full scale) computed from RMS value\n silent: boolean // Always computed\n features?: AudioFeatures\n speech?: SpeechFeatures\n startTime?: number\n endTime?: number\n // start / end position in bytes\n startPosition?: number\n endPosition?: number\n // number of audio samples for this point (samples size depends on bit depth)\n samples?: number\n}\n\n/**\n * Represents the complete data from the audio analysis.\n */\nexport interface AudioAnalysis {\n segmentDurationMs: number // Duration of each segment in milliseconds\n durationMs: number // Duration of the audio in milliseconds\n bitDepth: number // Bit depth of the audio\n samples: number // Size of the audio in bytes\n numberOfChannels: number // Number of audio channels\n sampleRate: number // Sample rate of the audio\n dataPoints: DataPoint[] // Array of data points from the analysis.\n amplitudeRange: {\n min: number\n max: number\n }\n rmsRange: {\n min: number\n max: number\n }\n // TODO: speaker changes into a broader speech analysis section\n speechAnalysis?: {\n speakerChanges: {\n timestamp: number\n speakerId: number\n }[]\n // Could add more speech analysis data here like:\n // dominantSpeaker?: number\n // totalSpeechDuration?: number\n // speakerStats?: { [speakerId: number]: { duration: number, segments: number } }\n }\n}\n\n/**\n * Options for specifying a time range within an audio file.\n */\nexport interface AudioRangeOptions {\n /** Start time in milliseconds */\n startTimeMs?: number\n /** End time in milliseconds */\n endTimeMs?: number\n}\n\n/**\n * Options for generating a quick preview of audio waveform.\n * This is optimized for UI rendering with a specified number of points.\n */\nexport interface PreviewOptions extends AudioRangeOptions {\n /** URI of the audio file to analyze */\n fileUri: string\n /**\n * Total number of points to generate for the preview.\n * @default 100\n */\n numberOfPoints?: number\n /**\n * Optional logger for debugging.\n */\n logger?: ConsoleLike\n /**\n * Optional configuration for decoding the audio file.\n * Defaults to:\n * - targetSampleRate: undefined (keep original)\n * - targetChannels: undefined (keep original)\n * - targetBitDepth: 16\n * - normalizeAudio: false\n */\n decodingOptions?: DecodingConfig\n}\n\n/**\n * Options for mel-spectrogram extraction\n *\n * @experimental This feature is experimental and currently only available on Android.\n * The API may change in future versions.\n */\nexport interface ExtractMelSpectrogramOptions {\n fileUri?: string // Path to audio file\n arrayBuffer?: ArrayBuffer // Raw audio buffer\n windowSizeMs: number // Window size in ms (e.g., 25)\n hopLengthMs: number // Hop length in ms (e.g., 10)\n nMels: number // Number of mel filters (e.g., 60)\n fMin?: number // Min frequency (default: 0)\n fMax?: number // Max frequency (default: sampleRate / 2)\n windowType?: 'hann' | 'hamming' // Window function (default: 'hann')\n normalize?: boolean // Mean normalization (default: false)\n logScale?: boolean // Log scaling of mel energies (default: true)\n decodingOptions?: DecodingConfig // Audio decoding settings\n startTimeMs?: number // Optional start time\n endTimeMs?: number // Optional end time\n logger?: ConsoleLike\n}\n\n/**\n * Return type for mel spectrogram extraction\n *\n * @experimental This feature is experimental and currently only available on Android.\n * The API may change in future versions.\n */\nexport interface MelSpectrogram {\n spectrogram: number[][] // 2D array [time][mel]\n sampleRate: number // Audio sample rate\n nMels: number // Number of mel filters\n timeSteps: number // Number of time frames\n durationMs: number // Audio duration in ms\n}\n"]}
|
|
1
|
+
{"version":3,"file":"AudioAnalysis.types.js","sourceRoot":"","sources":["../../../src/AudioAnalysis/AudioAnalysis.types.ts"],"names":[],"mappings":";AAAA,sEAAsE","sourcesContent":["// packages/expo-audio-stream/src/AudioAnalysis/AudioAnalysis.types.ts\n\nimport { BitDepth, ConsoleLike } from '../ExpoAudioStream.types'\n\n/**\n * Represents the configuration for decoding audio data.\n */\nexport interface DecodingConfig {\n /** Target sample rate for decoded audio (Android and Web) */\n targetSampleRate?: number\n /** Target number of channels (Android and Web) */\n targetChannels?: number\n /** Target bit depth (Android and Web) */\n targetBitDepth?: BitDepth\n /** Whether to normalize audio levels (Android and Web) */\n normalizeAudio?: boolean\n}\n\n/**\n * Represents speech-related features extracted from audio.\n */\nexport interface SpeechFeatures {\n isActive: boolean // Whether speech is detected in this segment\n speakerId?: number // Optional speaker identification\n // Could add more speech-related features here like:\n // confidence: number\n // language?: string\n // sentiment?: number\n // etc.\n}\n\n/**\n * Represents various audio features extracted from an audio signal.\n */\nexport interface AudioFeatures {\n energy?: number // The infinite integral of the squared signal, representing the overall energy of the audio.\n mfcc?: number[] // Mel-frequency cepstral coefficients, describing the short-term power spectrum of a sound.\n rms?: number // Root mean square value, indicating the amplitude of the audio signal.\n minAmplitude?: number // Minimum amplitude value in the audio signal.\n maxAmplitude?: number // Maximum amplitude value in the audio signal.\n zcr?: number // Zero-crossing rate, indicating the rate at which the signal changes sign.\n spectralCentroid?: number // The center of mass of the spectrum, indicating the brightness of the sound.\n spectralFlatness?: number // Measure of the flatness of the spectrum, indicating how noise-like the signal is.\n spectralRolloff?: number // The frequency below which a specified percentage (usually 85%) of the total spectral energy lies.\n spectralBandwidth?: number // The width of the spectrum, indicating the range of frequencies present.\n chromagram?: number[] // Chromagram, representing the 12 different pitch classes of the audio.\n tempo?: number // Estimated tempo of the audio signal, measured in beats per minute (BPM).\n hnr?: number // Harmonics-to-noise ratio, indicating the proportion of harmonics to noise in the audio signal.\n melSpectrogram?: number[] // Mel-scaled spectrogram representation of the audio.\n spectralContrast?: number[] // Spectral contrast features representing the difference between peaks and valleys.\n tonnetz?: number[] // Tonal network features representing harmonic relationships.\n pitch?: number // Pitch of the audio signal, measured in Hertz (Hz).\n crc32?: number // crc32 checksum of the audio signal, used to verify the integrity of the audio.\n}\n\n/**\n * Options to specify which audio features to extract.\n * Note: Advanced features (spectral features, chromagram, pitch, etc.) are experimental,\n * especially during live recording, due to high processing requirements.\n */\nexport interface AudioFeaturesOptions {\n // Basic features - well optimized\n energy?: boolean\n rms?: boolean\n zcr?: boolean\n\n // Advanced features - experimental, may impact performance in live recording\n mfcc?: boolean\n spectralCentroid?: boolean\n spectralFlatness?: boolean\n spectralRolloff?: boolean\n spectralBandwidth?: boolean\n chromagram?: boolean\n tempo?: boolean\n hnr?: boolean\n melSpectrogram?: boolean\n spectralContrast?: boolean\n tonnetz?: boolean\n pitch?: boolean\n\n // Utility\n crc32?: boolean\n}\n\n/**\n * Represents a single data point in the audio analysis.\n */\nexport interface DataPoint {\n id: number\n amplitude: number // Peak amplitude for the segment\n rms: number // Root mean square value\n dB: number // dBFS (decibels relative to full scale) computed from RMS value\n silent: boolean // Always computed\n features?: AudioFeatures\n speech?: SpeechFeatures\n startTime?: number\n endTime?: number\n // start / end position in bytes\n startPosition?: number\n endPosition?: number\n // number of audio samples for this point (samples size depends on bit depth)\n samples?: number\n}\n\n/**\n * Represents the complete data from the audio analysis.\n */\nexport interface AudioAnalysis {\n segmentDurationMs: number // Duration of each segment in milliseconds\n durationMs: number // Duration of the audio in milliseconds\n bitDepth: number // Bit depth of the audio\n samples: number // Size of the audio in bytes\n numberOfChannels: number // Number of audio channels\n sampleRate: number // Sample rate of the audio\n dataPoints: DataPoint[] // Array of data points from the analysis.\n amplitudeRange: {\n min: number\n max: number\n }\n rmsRange: {\n min: number\n max: number\n }\n extractionTimeMs: number // Time taken to extract/process the analysis in milliseconds\n // TODO: speaker changes into a broader speech analysis section\n speechAnalysis?: {\n speakerChanges: {\n timestamp: number\n speakerId: number\n }[]\n // Could add more speech analysis data here like:\n // dominantSpeaker?: number\n // totalSpeechDuration?: number\n // speakerStats?: { [speakerId: number]: { duration: number, segments: number } }\n }\n}\n\n/**\n * Options for specifying a time range within an audio file.\n */\nexport interface AudioRangeOptions {\n /** Start time in milliseconds */\n startTimeMs?: number\n /** End time in milliseconds */\n endTimeMs?: number\n}\n\n/**\n * Options for generating a quick preview of audio waveform.\n * This is optimized for UI rendering with a specified number of points.\n */\nexport interface PreviewOptions extends AudioRangeOptions {\n /** URI of the audio file to analyze */\n fileUri: string\n /**\n * Total number of points to generate for the preview.\n * @default 100\n */\n numberOfPoints?: number\n /**\n * Optional logger for debugging.\n */\n logger?: ConsoleLike\n /**\n * Optional configuration for decoding the audio file.\n * Defaults to:\n * - targetSampleRate: undefined (keep original)\n * - targetChannels: undefined (keep original)\n * - targetBitDepth: 16\n * - normalizeAudio: false\n */\n decodingOptions?: DecodingConfig\n}\n\n/**\n * Options for mel-spectrogram extraction\n *\n * @experimental This feature is experimental and currently only available on Android.\n * The API may change in future versions.\n */\nexport interface ExtractMelSpectrogramOptions {\n fileUri?: string // Path to audio file\n arrayBuffer?: ArrayBuffer // Raw audio buffer\n windowSizeMs: number // Window size in ms (e.g., 25)\n hopLengthMs: number // Hop length in ms (e.g., 10)\n nMels: number // Number of mel filters (e.g., 60)\n fMin?: number // Min frequency (default: 0)\n fMax?: number // Max frequency (default: sampleRate / 2)\n windowType?: 'hann' | 'hamming' // Window function (default: 'hann')\n normalize?: boolean // Mean normalization (default: false)\n logScale?: boolean // Log scaling of mel energies (default: true)\n decodingOptions?: DecodingConfig // Audio decoding settings\n startTimeMs?: number // Optional start time\n endTimeMs?: number // Optional end time\n logger?: ConsoleLike\n}\n\n/**\n * Return type for mel spectrogram extraction\n *\n * @experimental This feature is experimental and currently only available on Android.\n * The API may change in future versions.\n */\nexport interface MelSpectrogram {\n spectrogram: number[][] // 2D array [time][mel]\n sampleRate: number // Audio sample rate\n nMels: number // Number of mel filters\n timeSteps: number // Number of time frames\n durationMs: number // Audio duration in ms\n}\n"]}
|