@siteed/expo-audio-studio 2.18.5 → 3.0.0

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 (329) hide show
  1. package/README.md +13 -297
  2. package/index.d.ts +1 -0
  3. package/index.js +1 -0
  4. package/package.json +6 -135
  5. package/CHANGELOG.md +0 -493
  6. package/LICENSE +0 -21
  7. package/android/build.gradle +0 -129
  8. package/android/src/androidTest/assets/chorus.wav +0 -0
  9. package/android/src/androidTest/assets/jfk.wav +0 -0
  10. package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
  11. package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
  12. package/android/src/androidTest/java/net/siteed/audiostream/AudioProcessorInstrumentedTest.kt +0 -197
  13. package/android/src/androidTest/java/net/siteed/audiostream/AudioRecorderInstrumentedTest.kt +0 -541
  14. package/android/src/androidTest/java/net/siteed/audiostream/AudioRecorderPerformanceInstrumentedTest.kt +0 -234
  15. package/android/src/androidTest/java/net/siteed/audiostream/integration/AudioFocusStrategyIntegrationTest.kt +0 -332
  16. package/android/src/androidTest/java/net/siteed/audiostream/integration/BufferDurationIntegrationTest.kt +0 -324
  17. package/android/src/androidTest/java/net/siteed/audiostream/integration/CompressedOnlyOutputTest.kt +0 -253
  18. package/android/src/androidTest/java/net/siteed/audiostream/integration/DeviceDisconnectionFallbackTest.kt +0 -218
  19. package/android/src/androidTest/java/net/siteed/audiostream/integration/EventEmissionIntervalTest.kt +0 -120
  20. package/android/src/androidTest/java/net/siteed/audiostream/integration/M4aFormatTest.kt +0 -345
  21. package/android/src/androidTest/java/net/siteed/audiostream/integration/OutputControlIntegrationTest.kt +0 -340
  22. package/android/src/androidTest/java/net/siteed/audiostream/integration/PcmStreamingDurationTest.kt +0 -252
  23. package/android/src/androidTest/java/net/siteed/audiostream/integration/README.md +0 -95
  24. package/android/src/androidTest/java/net/siteed/audiostream/integration/run_integration_tests.sh +0 -43
  25. package/android/src/main/AndroidManifest.xml +0 -30
  26. package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +0 -188
  27. package/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +0 -9
  28. package/android/src/main/java/net/siteed/audiostream/AudioDeviceManager.kt +0 -1741
  29. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +0 -136
  30. package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +0 -354
  31. package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +0 -439
  32. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +0 -2237
  33. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +0 -2141
  34. package/android/src/main/java/net/siteed/audiostream/AudioRecordingService.kt +0 -167
  35. package/android/src/main/java/net/siteed/audiostream/AudioTrimmer.kt +0 -1099
  36. package/android/src/main/java/net/siteed/audiostream/Constants.kt +0 -37
  37. package/android/src/main/java/net/siteed/audiostream/EventSender.kt +0 -7
  38. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +0 -1113
  39. package/android/src/main/java/net/siteed/audiostream/FFT.kt +0 -99
  40. package/android/src/main/java/net/siteed/audiostream/Features.kt +0 -98
  41. package/android/src/main/java/net/siteed/audiostream/LogUtils.kt +0 -93
  42. package/android/src/main/java/net/siteed/audiostream/NotificationConfig.kt +0 -72
  43. package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +0 -68
  44. package/android/src/main/java/net/siteed/audiostream/RecordingActionReceiver.kt +0 -59
  45. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +0 -257
  46. package/android/src/main/java/net/siteed/audiostream/WaveformConfig.kt +0 -19
  47. package/android/src/main/java/net/siteed/audiostream/WaveformRenderer.kt +0 -159
  48. package/android/src/main/res/drawable/ic_default_action_icon.xml +0 -16
  49. package/android/src/main/res/drawable/ic_microphone.xml +0 -13
  50. package/android/src/main/res/drawable/ic_pause.xml +0 -10
  51. package/android/src/main/res/drawable/ic_play.xml +0 -10
  52. package/android/src/main/res/drawable/ic_stop.xml +0 -10
  53. package/android/src/main/res/layout/notification_recording.xml +0 -37
  54. package/android/src/test/java/net/siteed/audiostream/AudioFileHandlerTest.kt +0 -279
  55. package/android/src/test/java/net/siteed/audiostream/AudioFocusStrategyTest.kt +0 -249
  56. package/android/src/test/java/net/siteed/audiostream/AudioFormatTest.kt +0 -151
  57. package/android/src/test/java/net/siteed/audiostream/AudioFormatUtilsTest.kt +0 -273
  58. package/android/src/test/java/net/siteed/audiostream/DeviceDisconnectionFallbackUnitTest.kt +0 -140
  59. package/android/src/test/resources/chorus.wav +0 -0
  60. package/android/src/test/resources/generate_test_audio.py +0 -94
  61. package/android/src/test/resources/jfk.wav +0 -0
  62. package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
  63. package/android/src/test/resources/recorder_hello_world.wav +0 -0
  64. package/app.plugin.js +0 -3
  65. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js +0 -4
  66. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +0 -1
  67. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js +0 -210
  68. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
  69. package/build/cjs/AudioAnalysis/extractAudioData.js +0 -21
  70. package/build/cjs/AudioAnalysis/extractAudioData.js.map +0 -1
  71. package/build/cjs/AudioAnalysis/extractMelSpectrogram.js +0 -92
  72. package/build/cjs/AudioAnalysis/extractMelSpectrogram.js.map +0 -1
  73. package/build/cjs/AudioAnalysis/extractPreview.js +0 -28
  74. package/build/cjs/AudioAnalysis/extractPreview.js.map +0 -1
  75. package/build/cjs/AudioAnalysis/extractWaveform.js +0 -18
  76. package/build/cjs/AudioAnalysis/extractWaveform.js.map +0 -1
  77. package/build/cjs/AudioDeviceManager.js +0 -689
  78. package/build/cjs/AudioDeviceManager.js.map +0 -1
  79. package/build/cjs/AudioRecorder.provider.js +0 -78
  80. package/build/cjs/AudioRecorder.provider.js.map +0 -1
  81. package/build/cjs/ExpoAudioStream.native.js +0 -8
  82. package/build/cjs/ExpoAudioStream.native.js.map +0 -1
  83. package/build/cjs/ExpoAudioStream.types.js +0 -11
  84. package/build/cjs/ExpoAudioStream.types.js.map +0 -1
  85. package/build/cjs/ExpoAudioStream.web.js +0 -708
  86. package/build/cjs/ExpoAudioStream.web.js.map +0 -1
  87. package/build/cjs/ExpoAudioStreamModule.js +0 -718
  88. package/build/cjs/ExpoAudioStreamModule.js.map +0 -1
  89. package/build/cjs/WebRecorder.web.js +0 -777
  90. package/build/cjs/WebRecorder.web.js.map +0 -1
  91. package/build/cjs/constants/platformLimitations.js +0 -99
  92. package/build/cjs/constants/platformLimitations.js.map +0 -1
  93. package/build/cjs/constants.js +0 -17
  94. package/build/cjs/constants.js.map +0 -1
  95. package/build/cjs/events.js +0 -29
  96. package/build/cjs/events.js.map +0 -1
  97. package/build/cjs/hooks/useAudioDevices.js +0 -179
  98. package/build/cjs/hooks/useAudioDevices.js.map +0 -1
  99. package/build/cjs/index.js +0 -58
  100. package/build/cjs/index.js.map +0 -1
  101. package/build/cjs/trimAudio.js +0 -76
  102. package/build/cjs/trimAudio.js.map +0 -1
  103. package/build/cjs/useAudioRecorder.js +0 -518
  104. package/build/cjs/useAudioRecorder.js.map +0 -1
  105. package/build/cjs/utils/BlobFix.js +0 -502
  106. package/build/cjs/utils/BlobFix.js.map +0 -1
  107. package/build/cjs/utils/audioProcessing.js +0 -136
  108. package/build/cjs/utils/audioProcessing.js.map +0 -1
  109. package/build/cjs/utils/cleanNativeOptions.js +0 -22
  110. package/build/cjs/utils/cleanNativeOptions.js.map +0 -1
  111. package/build/cjs/utils/concatenateBuffers.js +0 -25
  112. package/build/cjs/utils/concatenateBuffers.js.map +0 -1
  113. package/build/cjs/utils/convertPCMToFloat32.js +0 -124
  114. package/build/cjs/utils/convertPCMToFloat32.js.map +0 -1
  115. package/build/cjs/utils/crc32.js +0 -52
  116. package/build/cjs/utils/crc32.js.map +0 -1
  117. package/build/cjs/utils/encodingToBitDepth.js +0 -17
  118. package/build/cjs/utils/encodingToBitDepth.js.map +0 -1
  119. package/build/cjs/utils/getWavFileInfo.js +0 -96
  120. package/build/cjs/utils/getWavFileInfo.js.map +0 -1
  121. package/build/cjs/utils/writeWavHeader.js +0 -88
  122. package/build/cjs/utils/writeWavHeader.js.map +0 -1
  123. package/build/cjs/workers/InlineFeaturesExtractor.web.js +0 -859
  124. package/build/cjs/workers/InlineFeaturesExtractor.web.js.map +0 -1
  125. package/build/cjs/workers/inlineAudioWebWorker.web.js +0 -184
  126. package/build/cjs/workers/inlineAudioWebWorker.web.js.map +0 -1
  127. package/build/esm/AudioAnalysis/AudioAnalysis.types.js +0 -3
  128. package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +0 -1
  129. package/build/esm/AudioAnalysis/extractAudioAnalysis.js +0 -202
  130. package/build/esm/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
  131. package/build/esm/AudioAnalysis/extractAudioData.js +0 -14
  132. package/build/esm/AudioAnalysis/extractAudioData.js.map +0 -1
  133. package/build/esm/AudioAnalysis/extractMelSpectrogram.js +0 -89
  134. package/build/esm/AudioAnalysis/extractMelSpectrogram.js.map +0 -1
  135. package/build/esm/AudioAnalysis/extractPreview.js +0 -25
  136. package/build/esm/AudioAnalysis/extractPreview.js.map +0 -1
  137. package/build/esm/AudioAnalysis/extractWaveform.js +0 -11
  138. package/build/esm/AudioAnalysis/extractWaveform.js.map +0 -1
  139. package/build/esm/AudioDeviceManager.js +0 -682
  140. package/build/esm/AudioDeviceManager.js.map +0 -1
  141. package/build/esm/AudioRecorder.provider.js +0 -40
  142. package/build/esm/AudioRecorder.provider.js.map +0 -1
  143. package/build/esm/ExpoAudioStream.native.js +0 -6
  144. package/build/esm/ExpoAudioStream.native.js.map +0 -1
  145. package/build/esm/ExpoAudioStream.types.js +0 -8
  146. package/build/esm/ExpoAudioStream.types.js.map +0 -1
  147. package/build/esm/ExpoAudioStream.web.js +0 -704
  148. package/build/esm/ExpoAudioStream.web.js.map +0 -1
  149. package/build/esm/ExpoAudioStreamModule.js +0 -713
  150. package/build/esm/ExpoAudioStreamModule.js.map +0 -1
  151. package/build/esm/WebRecorder.web.js +0 -773
  152. package/build/esm/WebRecorder.web.js.map +0 -1
  153. package/build/esm/constants/platformLimitations.js +0 -90
  154. package/build/esm/constants/platformLimitations.js.map +0 -1
  155. package/build/esm/constants.js +0 -14
  156. package/build/esm/constants.js.map +0 -1
  157. package/build/esm/events.js +0 -21
  158. package/build/esm/events.js.map +0 -1
  159. package/build/esm/hooks/useAudioDevices.js +0 -176
  160. package/build/esm/hooks/useAudioDevices.js.map +0 -1
  161. package/build/esm/index.js +0 -20
  162. package/build/esm/index.js.map +0 -1
  163. package/build/esm/trimAudio.js +0 -69
  164. package/build/esm/trimAudio.js.map +0 -1
  165. package/build/esm/useAudioRecorder.js +0 -512
  166. package/build/esm/useAudioRecorder.js.map +0 -1
  167. package/build/esm/utils/BlobFix.js +0 -498
  168. package/build/esm/utils/BlobFix.js.map +0 -1
  169. package/build/esm/utils/audioProcessing.js +0 -133
  170. package/build/esm/utils/audioProcessing.js.map +0 -1
  171. package/build/esm/utils/cleanNativeOptions.js +0 -19
  172. package/build/esm/utils/cleanNativeOptions.js.map +0 -1
  173. package/build/esm/utils/concatenateBuffers.js +0 -21
  174. package/build/esm/utils/concatenateBuffers.js.map +0 -1
  175. package/build/esm/utils/convertPCMToFloat32.js +0 -120
  176. package/build/esm/utils/convertPCMToFloat32.js.map +0 -1
  177. package/build/esm/utils/crc32.js +0 -50
  178. package/build/esm/utils/crc32.js.map +0 -1
  179. package/build/esm/utils/encodingToBitDepth.js +0 -13
  180. package/build/esm/utils/encodingToBitDepth.js.map +0 -1
  181. package/build/esm/utils/getWavFileInfo.js +0 -92
  182. package/build/esm/utils/getWavFileInfo.js.map +0 -1
  183. package/build/esm/utils/writeWavHeader.js +0 -84
  184. package/build/esm/utils/writeWavHeader.js.map +0 -1
  185. package/build/esm/workers/InlineFeaturesExtractor.web.js +0 -856
  186. package/build/esm/workers/InlineFeaturesExtractor.web.js.map +0 -1
  187. package/build/esm/workers/inlineAudioWebWorker.web.js +0 -181
  188. package/build/esm/workers/inlineAudioWebWorker.web.js.map +0 -1
  189. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts +0 -196
  190. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +0 -1
  191. package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts +0 -74
  192. package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts.map +0 -1
  193. package/build/types/AudioAnalysis/extractAudioData.d.ts +0 -3
  194. package/build/types/AudioAnalysis/extractAudioData.d.ts.map +0 -1
  195. package/build/types/AudioAnalysis/extractMelSpectrogram.d.ts +0 -14
  196. package/build/types/AudioAnalysis/extractMelSpectrogram.d.ts.map +0 -1
  197. package/build/types/AudioAnalysis/extractPreview.d.ts +0 -11
  198. package/build/types/AudioAnalysis/extractPreview.d.ts.map +0 -1
  199. package/build/types/AudioAnalysis/extractWaveform.d.ts +0 -8
  200. package/build/types/AudioAnalysis/extractWaveform.d.ts.map +0 -1
  201. package/build/types/AudioDeviceManager.d.ts +0 -187
  202. package/build/types/AudioDeviceManager.d.ts.map +0 -1
  203. package/build/types/AudioRecorder.provider.d.ts +0 -11
  204. package/build/types/AudioRecorder.provider.d.ts.map +0 -1
  205. package/build/types/ExpoAudioStream.native.d.ts +0 -3
  206. package/build/types/ExpoAudioStream.native.d.ts.map +0 -1
  207. package/build/types/ExpoAudioStream.types.d.ts +0 -738
  208. package/build/types/ExpoAudioStream.types.d.ts.map +0 -1
  209. package/build/types/ExpoAudioStream.web.d.ts +0 -96
  210. package/build/types/ExpoAudioStream.web.d.ts.map +0 -1
  211. package/build/types/ExpoAudioStreamModule.d.ts +0 -3
  212. package/build/types/ExpoAudioStreamModule.d.ts.map +0 -1
  213. package/build/types/WebRecorder.web.d.ts +0 -198
  214. package/build/types/WebRecorder.web.d.ts.map +0 -1
  215. package/build/types/constants/platformLimitations.d.ts +0 -40
  216. package/build/types/constants/platformLimitations.d.ts.map +0 -1
  217. package/build/types/constants.d.ts +0 -11
  218. package/build/types/constants.d.ts.map +0 -1
  219. package/build/types/events.d.ts +0 -26
  220. package/build/types/events.d.ts.map +0 -1
  221. package/build/types/hooks/useAudioDevices.d.ts +0 -15
  222. package/build/types/hooks/useAudioDevices.d.ts.map +0 -1
  223. package/build/types/index.d.ts +0 -18
  224. package/build/types/index.d.ts.map +0 -1
  225. package/build/types/trimAudio.d.ts +0 -25
  226. package/build/types/trimAudio.d.ts.map +0 -1
  227. package/build/types/useAudioRecorder.d.ts +0 -22
  228. package/build/types/useAudioRecorder.d.ts.map +0 -1
  229. package/build/types/utils/BlobFix.d.ts +0 -9
  230. package/build/types/utils/BlobFix.d.ts.map +0 -1
  231. package/build/types/utils/audioProcessing.d.ts +0 -24
  232. package/build/types/utils/audioProcessing.d.ts.map +0 -1
  233. package/build/types/utils/cleanNativeOptions.d.ts +0 -15
  234. package/build/types/utils/cleanNativeOptions.d.ts.map +0 -1
  235. package/build/types/utils/concatenateBuffers.d.ts +0 -8
  236. package/build/types/utils/concatenateBuffers.d.ts.map +0 -1
  237. package/build/types/utils/convertPCMToFloat32.d.ts +0 -13
  238. package/build/types/utils/convertPCMToFloat32.d.ts.map +0 -1
  239. package/build/types/utils/crc32.d.ts +0 -7
  240. package/build/types/utils/crc32.d.ts.map +0 -1
  241. package/build/types/utils/encodingToBitDepth.d.ts +0 -5
  242. package/build/types/utils/encodingToBitDepth.d.ts.map +0 -1
  243. package/build/types/utils/getWavFileInfo.d.ts +0 -26
  244. package/build/types/utils/getWavFileInfo.d.ts.map +0 -1
  245. package/build/types/utils/writeWavHeader.d.ts +0 -34
  246. package/build/types/utils/writeWavHeader.d.ts.map +0 -1
  247. package/build/types/workers/InlineFeaturesExtractor.web.d.ts +0 -2
  248. package/build/types/workers/InlineFeaturesExtractor.web.d.ts.map +0 -1
  249. package/build/types/workers/inlineAudioWebWorker.web.d.ts +0 -2
  250. package/build/types/workers/inlineAudioWebWorker.web.d.ts.map +0 -1
  251. package/expo-module.config.json +0 -10
  252. package/ios/AudioAnalysisData.swift +0 -74
  253. package/ios/AudioDeviceManager.swift +0 -666
  254. package/ios/AudioNotificationManager.swift +0 -154
  255. package/ios/AudioProcessingHelpers.swift +0 -743
  256. package/ios/AudioProcessor.swift +0 -1151
  257. package/ios/AudioStreamError.swift +0 -7
  258. package/ios/AudioStreamManager.swift +0 -2352
  259. package/ios/AudioStreamManagerDelegate.swift +0 -16
  260. package/ios/DataPoint.swift +0 -54
  261. package/ios/DecodingConfig.swift +0 -59
  262. package/ios/ExpoAudioStream.podspec +0 -33
  263. package/ios/ExpoAudioStreamModule.swift +0 -1013
  264. package/ios/ExpoAudioStudioTests/AudioFileHandlerTests.swift +0 -338
  265. package/ios/ExpoAudioStudioTests/AudioFormatUtilsTests.swift +0 -331
  266. package/ios/ExpoAudioStudioTests/AudioTestHelpers.swift +0 -130
  267. package/ios/ExpoAudioStudioTests/CompressedOnlyOutputTests.swift +0 -294
  268. package/ios/ExpoAudioStudioTests/EventEmissionIntervalTests.swift +0 -105
  269. package/ios/ExpoAudioStudioTests/Info.plist +0 -22
  270. package/ios/ExpoAudioStudioTests/README.md +0 -39
  271. package/ios/ExpoAudioStudioTests/SimpleAudioTest.swift +0 -98
  272. package/ios/ExpoAudioStudioTests/TestAudioGenerator.swift +0 -75
  273. package/ios/FFT.swift +0 -62
  274. package/ios/Features.swift +0 -95
  275. package/ios/ISSUE_IOS.md +0 -68
  276. package/ios/Logger.swift +0 -39
  277. package/ios/NotificationExtension.swift +0 -15
  278. package/ios/RecordingResult.swift +0 -22
  279. package/ios/RecordingSettings.swift +0 -308
  280. package/ios/WaveformExtractor.swift +0 -105
  281. package/ios/tests/README.md +0 -41
  282. package/ios/tests/integration/buffer_and_fallback_test.swift +0 -178
  283. package/ios/tests/integration/buffer_duration_test.swift +0 -185
  284. package/ios/tests/integration/compressed_only_output_test.swift +0 -271
  285. package/ios/tests/integration/output_control_test.swift +0 -322
  286. package/ios/tests/integration/run_integration_tests.sh +0 -37
  287. package/ios/tests/opus_support_test_macos.swift +0 -154
  288. package/ios/tests/standalone/audio_processing_test.swift +0 -144
  289. package/ios/tests/standalone/audio_recording_test.swift +0 -277
  290. package/ios/tests/standalone/audio_streaming_test.swift +0 -249
  291. package/ios/tests/standalone/standalone_test.swift +0 -144
  292. package/plugin/build/index.cjs +0 -194
  293. package/plugin/build/index.d.cts +0 -22
  294. package/plugin/build/index.js +0 -194
  295. package/plugin/src/index.ts +0 -285
  296. package/plugin/tsconfig.json +0 -10
  297. package/plugin/tsconfig.tsbuildinfo +0 -1
  298. package/src/AudioAnalysis/AudioAnalysis.types.ts +0 -224
  299. package/src/AudioAnalysis/extractAudioAnalysis.ts +0 -344
  300. package/src/AudioAnalysis/extractAudioData.ts +0 -17
  301. package/src/AudioAnalysis/extractMelSpectrogram.ts +0 -154
  302. package/src/AudioAnalysis/extractPreview.ts +0 -34
  303. package/src/AudioAnalysis/extractWaveform.ts +0 -22
  304. package/src/AudioDeviceManager.ts +0 -803
  305. package/src/AudioRecorder.provider.tsx +0 -57
  306. package/src/ExpoAudioStream.native.ts +0 -6
  307. package/src/ExpoAudioStream.types.ts +0 -874
  308. package/src/ExpoAudioStream.web.ts +0 -905
  309. package/src/ExpoAudioStreamModule.ts +0 -990
  310. package/src/WebRecorder.web.ts +0 -1005
  311. package/src/constants/platformLimitations.ts +0 -118
  312. package/src/constants.ts +0 -18
  313. package/src/events.ts +0 -60
  314. package/src/hooks/useAudioDevices.ts +0 -213
  315. package/src/index.ts +0 -54
  316. package/src/trimAudio.ts +0 -94
  317. package/src/types/crc-32.d.ts +0 -9
  318. package/src/useAudioRecorder.tsx +0 -766
  319. package/src/utils/BlobFix.ts +0 -561
  320. package/src/utils/audioProcessing.ts +0 -205
  321. package/src/utils/cleanNativeOptions.ts +0 -18
  322. package/src/utils/concatenateBuffers.ts +0 -24
  323. package/src/utils/convertPCMToFloat32.ts +0 -170
  324. package/src/utils/crc32.ts +0 -59
  325. package/src/utils/encodingToBitDepth.ts +0 -18
  326. package/src/utils/getWavFileInfo.ts +0 -132
  327. package/src/utils/writeWavHeader.ts +0 -115
  328. package/src/workers/InlineFeaturesExtractor.web.tsx +0 -855
  329. package/src/workers/inlineAudioWebWorker.web.tsx +0 -180
@@ -1,803 +0,0 @@
1
- import { EventEmitter } from 'expo-modules-core'
2
- import { Platform } from 'react-native'
3
-
4
- import {
5
- AudioDevice,
6
- AudioDeviceCapabilities,
7
- DeviceDisconnectionBehavior,
8
- ConsoleLike,
9
- } from './ExpoAudioStream.types'
10
- import ExpoAudioStreamModule from './ExpoAudioStreamModule'
11
-
12
- // Default device fallback for web and unsupported platforms
13
- const DEFAULT_DEVICE: AudioDevice = {
14
- id: 'default',
15
- name: 'Default Microphone',
16
- type: 'builtin_mic',
17
- isDefault: true,
18
- isAvailable: true,
19
- capabilities: {
20
- sampleRates: [16000, 44100, 48000],
21
- channelCounts: [1, 2],
22
- bitDepths: [16, 24, 32],
23
- hasEchoCancellation: true,
24
- hasNoiseSuppression: true,
25
- hasAutomaticGainControl: true,
26
- },
27
- }
28
-
29
- // Helper function to map raw object to AudioDevice interface
30
- // This handles potential inconsistencies from the native module
31
- function mapRawDeviceToAudioDevice(rawDevice: any): AudioDevice {
32
- const capabilities = rawDevice.capabilities || {}
33
- return {
34
- id: rawDevice.id || 'unknown',
35
- name: rawDevice.name || 'Unknown Device',
36
- type: rawDevice.type || 'unknown',
37
- isDefault: rawDevice.isDefault || false,
38
- isAvailable:
39
- rawDevice.isAvailable !== undefined ? rawDevice.isAvailable : true, // Default to true if undefined
40
- capabilities: {
41
- sampleRates: capabilities.sampleRates || [16000, 44100, 48000], // Provide defaults
42
- channelCounts: capabilities.channelCounts || [1, 2],
43
- bitDepths: capabilities.bitDepths || [16, 24, 32],
44
- hasEchoCancellation: capabilities.hasEchoCancellation,
45
- hasNoiseSuppression: capabilities.hasNoiseSuppression,
46
- hasAutomaticGainControl: capabilities.hasAutomaticGainControl,
47
- },
48
- }
49
- }
50
-
51
- /**
52
- * Class that provides a cross-platform API for managing audio input devices
53
- *
54
- * EVENT API SPECIFICATION:
55
- * ========================
56
- *
57
- * Device Events (deviceChangedEvent):
58
- * ```
59
- * {
60
- * type: "deviceConnected" | "deviceDisconnected",
61
- * deviceId: string
62
- * }
63
- * ```
64
- *
65
- * Recording Interruption Events (recordingInterruptedEvent):
66
- * ```
67
- * {
68
- * reason: "userPaused" | "userResumed" | "audioFocusLoss" | "audioFocusGain" |
69
- * "deviceFallback" | "deviceSwitchFailed" | "phoneCall" | "phoneCallEnded",
70
- * isPaused: boolean,
71
- * timestamp: number
72
- * }
73
- * ```
74
- *
75
- * NOTE: Device events use "type" field, interruption events use "reason" field.
76
- * This is intentional to distinguish between different event categories.
77
- */
78
- export class AudioDeviceManager {
79
- private eventEmitter: InstanceType<typeof EventEmitter>
80
- private currentDeviceId: string | null = null
81
- private availableDevices: AudioDevice[] = []
82
- private deviceChangeListeners: Set<(devices: AudioDevice[]) => void> =
83
- new Set()
84
- private webDeviceChangeHandler?: () => void
85
- private lastRefreshTime: number = 0
86
- private refreshInProgress: boolean = false
87
- private refreshDebounceMs: number = 500 // Minimum 500ms between refreshes
88
- private logger?: ConsoleLike
89
-
90
- // Track temporarily disconnected devices
91
- private temporarilyDisconnectedDevices: Set<string> = new Set()
92
- private disconnectionTimeouts: Map<string, ReturnType<typeof setTimeout>> =
93
- new Map()
94
- private readonly DISCONNECTION_TIMEOUT_MS = 5000 // 5 seconds
95
-
96
- constructor(options?: { logger?: ConsoleLike }) {
97
- this.eventEmitter = new EventEmitter(ExpoAudioStreamModule)
98
- this.logger = options?.logger
99
-
100
- // Set up device event listeners for all platforms immediately
101
- this.setupDeviceEventListeners()
102
- }
103
-
104
- /**
105
- * Set up device event listeners for the current platform
106
- */
107
- private setupDeviceEventListeners(): void {
108
- if (Platform.OS === 'web') {
109
- this.setupWebDeviceChangeListener()
110
- } else {
111
- this.setupNativeDeviceEventListener()
112
- }
113
- }
114
-
115
- /**
116
- * Set up native device event listener for iOS/Android
117
- */
118
- private setupNativeDeviceEventListener(): void {
119
- // Store the last event type to avoid duplicates
120
- let lastEventType: string | null = null
121
- let lastEventTime = 0
122
-
123
- this.eventEmitter.addListener('deviceChangedEvent', (event: any) => {
124
- // Skip processing duplicate events that occur too close together
125
- const now = Date.now()
126
- const isSimilarEvent =
127
- lastEventType === event.type &&
128
- now - lastEventTime < this.refreshDebounceMs
129
-
130
- if (isSimilarEvent) {
131
- this.logger?.debug(
132
- `Skipping similar device event (${event.type}) received too soon`
133
- )
134
- return
135
- }
136
-
137
- // Update the last event tracking
138
- lastEventType = event.type
139
- lastEventTime = now
140
-
141
- // Only refresh on meaningful events
142
- if (
143
- event.type === 'deviceConnected' ||
144
- event.type === 'deviceDisconnected' ||
145
- event.type === 'routeChanged'
146
- ) {
147
- this.logger?.debug(`Processing device event: ${event.type}`)
148
- // Force refresh for device events to ensure fresh data
149
- this.forceRefreshDevices()
150
- }
151
- })
152
- this.logger?.debug('Native device event listener set up')
153
- }
154
-
155
- /**
156
- * Initialize the device manager with a logger
157
- * @param logger A logger instance that implements the ConsoleLike interface
158
- * @returns The manager instance for chaining
159
- */
160
- initWithLogger(logger: ConsoleLike): AudioDeviceManager {
161
- this.setLogger(logger)
162
- return this
163
- }
164
-
165
- /**
166
- * Set the logger instance
167
- * @param logger A logger instance that implements the ConsoleLike interface
168
- */
169
- setLogger(logger: ConsoleLike) {
170
- this.logger = logger
171
- }
172
-
173
- /**
174
- * Initialize or reinitialize device detection
175
- * Useful for restarting device detection if initial setup failed
176
- */
177
- initializeDeviceDetection(): void {
178
- this.logger?.debug('Initializing device detection...')
179
-
180
- // Clean up existing listeners first
181
- if (Platform.OS === 'web' && this.webDeviceChangeHandler) {
182
- if (typeof navigator !== 'undefined' && navigator.mediaDevices) {
183
- navigator.mediaDevices.removeEventListener(
184
- 'devicechange',
185
- this.webDeviceChangeHandler
186
- )
187
- }
188
- this.webDeviceChangeHandler = undefined
189
- }
190
-
191
- // Re-setup device event listeners
192
- this.setupDeviceEventListeners()
193
- }
194
-
195
- /**
196
- * Get the current logger instance
197
- * @returns The logger instance or undefined if not set
198
- */
199
- getLogger(): ConsoleLike | undefined {
200
- return this.logger
201
- }
202
-
203
- /**
204
- * Get all available audio input devices
205
- * @param options Optional settings to force refresh the device list. Can include a refresh flag.
206
- * @returns Promise resolving to an array of audio devices conforming to AudioDevice interface
207
- */
208
- async getAvailableDevices(options?: {
209
- refresh?: boolean
210
- }): Promise<AudioDevice[]> {
211
- try {
212
- if (Platform.OS === 'web') {
213
- this.availableDevices = await this.getWebAudioDevices()
214
- } else if (ExpoAudioStreamModule.getAvailableInputDevices) {
215
- // Expecting an array of raw device objects from native
216
- const rawDevices: any[] =
217
- await ExpoAudioStreamModule.getAvailableInputDevices(
218
- options
219
- )
220
- // Map raw objects to the AudioDevice interface
221
- this.availableDevices = rawDevices.map(
222
- mapRawDeviceToAudioDevice
223
- )
224
- } else {
225
- // Fallback for unsupported platforms
226
- this.availableDevices = [DEFAULT_DEVICE]
227
- }
228
- return this.availableDevices
229
- } catch (error) {
230
- this.logger?.error('Failed to get available devices:', error)
231
- this.availableDevices = [DEFAULT_DEVICE] // Ensure state is updated on error
232
- return this.availableDevices
233
- }
234
- }
235
-
236
- /**
237
- * Get the currently selected audio input device
238
- * @returns Promise resolving to the current device (conforming to AudioDevice) or null
239
- */
240
- async getCurrentDevice(): Promise<AudioDevice | null> {
241
- try {
242
- if (Platform.OS === 'web') {
243
- if (!this.currentDeviceId) {
244
- // On web, return the typed default device if nothing is selected
245
- return DEFAULT_DEVICE
246
- }
247
- // Refresh web devices to ensure the current one is up-to-date
248
- const webDevices = await this.getWebAudioDevices()
249
- return (
250
- webDevices.find((d) => d.id === this.currentDeviceId) ||
251
- DEFAULT_DEVICE // Fallback to default if current ID not found
252
- )
253
- } else if (ExpoAudioStreamModule.getCurrentInputDevice) {
254
- // Expecting a single raw device object or null from native
255
- const rawDevice: any | null =
256
- await ExpoAudioStreamModule.getCurrentInputDevice()
257
- // Map to AudioDevice interface if not null
258
- return rawDevice ? mapRawDeviceToAudioDevice(rawDevice) : null
259
- } else {
260
- // Fallback for unsupported platforms
261
- return DEFAULT_DEVICE
262
- }
263
- } catch (error) {
264
- this.logger?.error('Failed to get current device:', error)
265
- return DEFAULT_DEVICE // Return default on error
266
- }
267
- }
268
-
269
- /**
270
- * Select a specific audio input device for recording
271
- * @param deviceId The ID of the device to select
272
- * @returns Promise resolving to a boolean indicating success
273
- */
274
- async selectDevice(deviceId: string): Promise<boolean> {
275
- try {
276
- let success = false
277
- if (Platform.OS === 'web') {
278
- // Check if the device exists before setting it
279
- const devices = await this.getWebAudioDevices()
280
- if (devices.some((d) => d.id === deviceId)) {
281
- this.currentDeviceId = deviceId
282
- success = true
283
- } else {
284
- this.logger?.warn(
285
- `Web: Device with ID ${deviceId} not found.`
286
- )
287
- success = false
288
- }
289
- } else if (ExpoAudioStreamModule.selectInputDevice) {
290
- success =
291
- await ExpoAudioStreamModule.selectInputDevice(deviceId)
292
- if (success) {
293
- this.currentDeviceId = deviceId
294
- }
295
- }
296
- // Refresh devices after selection attempt to update state
297
- await this.refreshDevices()
298
- return success
299
- } catch (error) {
300
- this.logger?.error('Failed to select device:', error)
301
- await this.refreshDevices() // Refresh even on error
302
- return false
303
- }
304
- }
305
-
306
- /**
307
- * Reset to the default audio input device
308
- * @returns Promise resolving to a boolean indicating success
309
- */
310
- async resetToDefaultDevice(): Promise<boolean> {
311
- try {
312
- let success = false
313
- if (Platform.OS === 'web') {
314
- this.currentDeviceId = 'default'
315
- success = true
316
- } else if (ExpoAudioStreamModule.resetToDefaultDevice) {
317
- success = await ExpoAudioStreamModule.resetToDefaultDevice()
318
- if (success) {
319
- this.currentDeviceId = null
320
- }
321
- }
322
- // Refresh devices after reset attempt
323
- await this.refreshDevices()
324
- return success
325
- } catch (error) {
326
- this.logger?.error('Failed to reset to default device:', error)
327
- await this.refreshDevices() // Refresh even on error
328
- return false
329
- }
330
- }
331
-
332
- /**
333
- * Register a listener for device changes
334
- * @param listener Function to call when devices change (receives AudioDevice[])
335
- * @returns Function to remove the listener
336
- */
337
- addDeviceChangeListener(
338
- listener: (devices: AudioDevice[]) => void
339
- ): () => void {
340
- this.deviceChangeListeners.add(listener)
341
-
342
- // Immediately call listener with current devices if available
343
- if (this.availableDevices.length > 0) {
344
- listener([...this.availableDevices])
345
- }
346
-
347
- // Return a function to remove the listener
348
- return () => {
349
- this.deviceChangeListeners.delete(listener)
350
- }
351
- }
352
-
353
- /**
354
- * Mark a device as temporarily disconnected (for UI filtering)
355
- * @param deviceId The ID of the device that was disconnected
356
- * @param notify Whether to notify listeners immediately (default: true)
357
- */
358
- markDeviceAsDisconnected(deviceId: string, notify: boolean = true): void {
359
- this.logger?.debug(
360
- `Marking device ${deviceId} as temporarily disconnected`
361
- )
362
-
363
- // Clear any existing timeout for this device
364
- const existingTimeout = this.disconnectionTimeouts.get(deviceId)
365
- if (existingTimeout) {
366
- clearTimeout(existingTimeout)
367
- }
368
-
369
- // Add to disconnected set
370
- this.temporarilyDisconnectedDevices.add(deviceId)
371
-
372
- // Set timeout to remove from disconnected set
373
- const timeout = setTimeout(() => {
374
- this.logger?.debug(
375
- `Reconnection timeout expired for device ${deviceId}`
376
- )
377
- this.temporarilyDisconnectedDevices.delete(deviceId)
378
- this.disconnectionTimeouts.delete(deviceId)
379
- // Refresh devices to show the device again if it's still available
380
- this.forceRefreshDevices()
381
- }, this.DISCONNECTION_TIMEOUT_MS)
382
-
383
- this.disconnectionTimeouts.set(deviceId, timeout)
384
-
385
- // Only notify listeners if requested
386
- if (notify) {
387
- this.notifyListeners()
388
- }
389
- }
390
-
391
- /**
392
- * Mark a device as reconnected (remove from disconnected set)
393
- * @param deviceId The ID of the device that was reconnected
394
- */
395
- markDeviceAsReconnected(deviceId: string): void {
396
- this.logger?.debug(`Marking device ${deviceId} as reconnected`)
397
-
398
- // Clear timeout and remove from disconnected set
399
- const timeout = this.disconnectionTimeouts.get(deviceId)
400
- if (timeout) {
401
- clearTimeout(timeout)
402
- this.disconnectionTimeouts.delete(deviceId)
403
- }
404
-
405
- this.temporarilyDisconnectedDevices.delete(deviceId)
406
-
407
- // Notify listeners with updated device list
408
- this.notifyListeners()
409
- }
410
-
411
- /**
412
- * Get filtered device list (excluding temporarily disconnected devices)
413
- * @returns Array of available devices excluding temporarily disconnected ones
414
- */
415
- private getFilteredDevices(): AudioDevice[] {
416
- if (this.temporarilyDisconnectedDevices.size === 0) {
417
- return [...this.availableDevices]
418
- }
419
-
420
- const filtered = this.availableDevices.filter(
421
- (device) => !this.temporarilyDisconnectedDevices.has(device.id)
422
- )
423
-
424
- this.logger?.debug(
425
- `Filtered ${this.availableDevices.length - filtered.length} temporarily disconnected devices. ` +
426
- `Showing ${filtered.length} devices.`
427
- )
428
-
429
- return filtered
430
- }
431
-
432
- /**
433
- * Get the raw device list (including temporarily disconnected devices)
434
- * @returns Array of all available devices from native layer
435
- */
436
- getRawDevices(): AudioDevice[] {
437
- return [...this.availableDevices]
438
- }
439
-
440
- /**
441
- * Get the IDs of temporarily disconnected devices
442
- * @returns Set of device IDs that are temporarily hidden from UI
443
- */
444
- getTemporarilyDisconnectedDeviceIds(): ReadonlySet<string> {
445
- return new Set(this.temporarilyDisconnectedDevices)
446
- }
447
-
448
- /**
449
- * Clean up timeouts and listeners (useful for testing or cleanup)
450
- */
451
- cleanup(): void {
452
- // Clear all disconnection timeouts
453
- this.disconnectionTimeouts.forEach((timeout) => clearTimeout(timeout))
454
- this.disconnectionTimeouts.clear()
455
- this.temporarilyDisconnectedDevices.clear()
456
-
457
- // Clear device change listeners
458
- this.deviceChangeListeners.clear()
459
-
460
- // Clean up web device listener
461
- if (Platform.OS === 'web' && this.webDeviceChangeHandler) {
462
- if (typeof navigator !== 'undefined' && navigator.mediaDevices) {
463
- navigator.mediaDevices.removeEventListener(
464
- 'devicechange',
465
- this.webDeviceChangeHandler
466
- )
467
- }
468
- this.webDeviceChangeHandler = undefined
469
- }
470
-
471
- this.logger?.debug('AudioDeviceManager cleanup completed')
472
- }
473
-
474
- /**
475
- * Force refresh devices without debouncing (for device events)
476
- * @returns Promise resolving to the updated device list (AudioDevice[])
477
- */
478
- async forceRefreshDevices(): Promise<AudioDevice[]> {
479
- this.logger?.debug('Force refreshing devices (bypassing debounce)...')
480
- this.refreshInProgress = true
481
- try {
482
- // Force fetch the latest devices from native layer
483
- const devices = await this.getAvailableDevices({ refresh: true })
484
- // Update internal state
485
- this.availableDevices = devices
486
- // Notify listeners with fresh data
487
- this.notifyListeners()
488
- this.lastRefreshTime = Date.now()
489
- return devices
490
- } catch (error) {
491
- this.logger?.error('Error during forceRefreshDevices:', error)
492
- return this.availableDevices
493
- } finally {
494
- this.refreshInProgress = false
495
- this.logger?.debug('Force refresh finished.')
496
- }
497
- }
498
-
499
- /**
500
- * Refresh the list of available devices with debouncing and notify listeners.
501
- * @returns Promise resolving to the updated device list (AudioDevice[])
502
- */
503
- async refreshDevices(): Promise<AudioDevice[]> {
504
- const now = Date.now()
505
-
506
- if (this.refreshInProgress) {
507
- this.logger?.debug('Refresh already in progress, skipping')
508
- return this.availableDevices
509
- }
510
-
511
- // Always allow refresh if forced by native event or longer than 2s debounce
512
- const timeSinceLastRefresh = now - this.lastRefreshTime
513
- const shouldDebounce =
514
- timeSinceLastRefresh < this.refreshDebounceMs &&
515
- timeSinceLastRefresh < 2000
516
-
517
- if (shouldDebounce) {
518
- this.logger?.debug(
519
- `Refresh debounced, skipping (last refresh was ${timeSinceLastRefresh}ms ago)`
520
- )
521
- return this.availableDevices
522
- }
523
-
524
- this.logger?.debug('Refreshing devices...')
525
- this.refreshInProgress = true
526
- try {
527
- // Fetch the latest devices; getAvailableDevices handles mapping now
528
- const devices = await this.getAvailableDevices({ refresh: true })
529
- // availableDevices state is updated within getAvailableDevices
530
- this.notifyListeners() // Notify listeners with the updated list
531
- this.lastRefreshTime = Date.now()
532
- return devices // Return the fetched & mapped list
533
- } catch (error) {
534
- this.logger?.error('Error during refreshDevices:', error)
535
- return this.availableDevices // Return potentially stale list on error
536
- } finally {
537
- this.refreshInProgress = false
538
- this.logger?.debug('Refresh finished.')
539
- }
540
- }
541
-
542
- /**
543
- * Get audio input devices using the Web Audio API
544
- * @returns Promise resolving to an array of AudioDevice objects
545
- */
546
- private async getWebAudioDevices(): Promise<AudioDevice[]> {
547
- if (
548
- typeof navigator === 'undefined' ||
549
- !navigator.mediaDevices ||
550
- !navigator.mediaDevices.enumerateDevices
551
- ) {
552
- return [DEFAULT_DEVICE]
553
- }
554
-
555
- try {
556
- const permissionStatus = await this.checkMicrophonePermission()
557
-
558
- if (permissionStatus === 'denied') {
559
- return [
560
- {
561
- ...DEFAULT_DEVICE,
562
- name: 'Microphone Access Denied',
563
- isAvailable: false,
564
- },
565
- ]
566
- }
567
-
568
- if (permissionStatus !== 'granted') {
569
- try {
570
- // Requesting stream often reveals device labels
571
- await navigator.mediaDevices.getUserMedia({ audio: true })
572
- } catch (error) {
573
- this.logger?.warn(
574
- 'Microphone permission request failed:',
575
- error
576
- )
577
- return [
578
- {
579
- ...DEFAULT_DEVICE,
580
- name: 'Microphone Access Required',
581
- isAvailable: false,
582
- },
583
- ]
584
- }
585
- }
586
-
587
- const devices = await navigator.mediaDevices.enumerateDevices()
588
- const audioInputDevices = devices
589
- .filter((device) => device.kind === 'audioinput')
590
- .map((device) => this.mapWebDeviceToAudioDevice(device))
591
-
592
- const hasUnlabeledDevices = audioInputDevices.some(
593
- (device) =>
594
- !device.name || device.name.startsWith('Microphone ')
595
- )
596
-
597
- let finalDevices = audioInputDevices
598
- if (hasUnlabeledDevices && this.isSafariOrIOS()) {
599
- finalDevices = this.enhanceDevicesForSafari(audioInputDevices)
600
- }
601
-
602
- if (finalDevices.length === 0) {
603
- finalDevices = [DEFAULT_DEVICE]
604
- }
605
-
606
- this.availableDevices = finalDevices // Update internal state
607
- return finalDevices
608
- } catch (error) {
609
- this.logger?.error('Failed to enumerate web audio devices:', error)
610
- this.availableDevices = [DEFAULT_DEVICE] // Update state on error
611
- return this.availableDevices
612
- }
613
- }
614
-
615
- /**
616
- * Check the current microphone permission status
617
- * @returns Permission state ('prompt', 'granted', or 'denied')
618
- */
619
- private async checkMicrophonePermission(): Promise<PermissionState> {
620
- if (!navigator.permissions || !navigator.permissions.query) {
621
- return 'prompt'
622
- }
623
- try {
624
- const permissionStatus = await navigator.permissions.query({
625
- name: 'microphone' as PermissionName,
626
- })
627
- permissionStatus.onchange = () => {
628
- // Refresh devices when permission changes
629
- this.refreshDevices()
630
- }
631
- return permissionStatus.state
632
- } catch (error) {
633
- this.logger?.warn('Permission query not supported:', error)
634
- return 'prompt'
635
- }
636
- }
637
-
638
- /**
639
- * Setup listener for device changes in web environment
640
- */
641
- private setupWebDeviceChangeListener(): void {
642
- if (
643
- typeof navigator === 'undefined' ||
644
- !navigator.mediaDevices ||
645
- this.webDeviceChangeHandler // Avoid adding multiple listeners
646
- ) {
647
- this.logger?.debug(
648
- 'Web device change listener not available or already set up'
649
- )
650
- return
651
- }
652
-
653
- try {
654
- this.webDeviceChangeHandler = () => {
655
- this.logger?.debug(
656
- 'Web device change detected, refreshing device list'
657
- )
658
- // Force refresh to get immediate updates
659
- this.forceRefreshDevices()
660
- }
661
-
662
- navigator.mediaDevices.addEventListener(
663
- 'devicechange',
664
- this.webDeviceChangeHandler
665
- )
666
- this.logger?.debug('Web device change listener successfully set up')
667
- } catch (error) {
668
- this.logger?.warn(
669
- 'Failed to set up web device change listener:',
670
- error
671
- )
672
- this.webDeviceChangeHandler = undefined
673
- }
674
- }
675
-
676
- /**
677
- * Check if the current browser is Safari or iOS WebKit
678
- */
679
- private isSafariOrIOS(): boolean {
680
- if (typeof navigator === 'undefined') return false
681
- const ua = navigator.userAgent
682
- return (
683
- /^((?!chrome|android).)*safari/i.test(ua) ||
684
- /iPad|iPhone|iPod/.test(ua) ||
685
- (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
686
- )
687
- }
688
-
689
- /**
690
- * Create enhanced device information for Safari and privacy-restricted browsers
691
- * @param devices Array of AudioDevice objects, potentially unlabeled
692
- * @returns Array of enhanced AudioDevice objects
693
- */
694
- private enhanceDevicesForSafari(devices: AudioDevice[]): AudioDevice[] {
695
- const defaultDevice = devices.find((d) => d.isDefault)
696
-
697
- if (devices.length <= 1) {
698
- // Return a typed default device
699
- return [
700
- {
701
- id: defaultDevice?.id || 'default',
702
- name: 'Microphone (Browser Managed)',
703
- type: 'builtin_mic',
704
- isDefault: true,
705
- isAvailable: true,
706
- capabilities:
707
- defaultDevice?.capabilities ||
708
- DEFAULT_DEVICE.capabilities,
709
- },
710
- ]
711
- }
712
-
713
- // Provide more descriptive names for unlabeled devices
714
- return devices.map((device, index) => {
715
- if (!device.name || device.name.startsWith('Microphone ')) {
716
- const deviceTypes = [
717
- 'Built-in Microphone',
718
- 'External Microphone',
719
- 'Headset Microphone',
720
- ]
721
- const typeName = deviceTypes[index % deviceTypes.length]
722
- return {
723
- ...device,
724
- name: device.isDefault ? `${typeName} (Default)` : typeName,
725
- }
726
- }
727
- return device
728
- })
729
- }
730
-
731
- /**
732
- * Map a Web MediaDeviceInfo to our AudioDevice format
733
- * @param device The MediaDeviceInfo object from the browser
734
- * @returns An object conforming to the AudioDevice interface
735
- */
736
- private mapWebDeviceToAudioDevice(device: MediaDeviceInfo): AudioDevice {
737
- const isDefault = device.deviceId === 'default'
738
- const deviceType = this.inferDeviceType(device.label || '')
739
-
740
- // Provide reasonable default capabilities for web devices
741
- const defaultWebCapabilities: AudioDeviceCapabilities = {
742
- sampleRates: [16000, 44100, 48000],
743
- channelCounts: [1, 2],
744
- bitDepths: [16, 32], // Web Audio uses float32, common PCM might be 16/32
745
- hasEchoCancellation: true, // Often handled by browser
746
- hasNoiseSuppression: true, // Often handled by browser
747
- hasAutomaticGainControl: true, // Often handled by browser
748
- }
749
-
750
- return {
751
- id: device.deviceId,
752
- name:
753
- device.label || `Microphone ${device.deviceId.substring(0, 8)}`,
754
- type: deviceType,
755
- isDefault,
756
- isAvailable: true, // Assume available if enumerated
757
- capabilities: defaultWebCapabilities,
758
- }
759
- }
760
-
761
- /**
762
- * Try to infer the device type from its name
763
- * @param deviceName The label of the device
764
- * @returns A string representing the inferred device type
765
- */
766
- private inferDeviceType(deviceName: string): string {
767
- const name = deviceName.toLowerCase()
768
- if (name.includes('bluetooth') || name.includes('airpods'))
769
- return 'bluetooth'
770
- if (name.includes('usb')) return 'usb'
771
- if (name.includes('headphone') || name.includes('headset')) {
772
- return name.includes('wired') ? 'wired_headset' : 'wired_headphones'
773
- }
774
- if (name.includes('speaker')) return 'speaker'
775
- return 'builtin_mic' // Default assumption
776
- }
777
-
778
- /**
779
- * Notify all registered listeners about device changes.
780
- */
781
- notifyListeners(): void {
782
- // Pass a copy of the filtered devices array to listeners
783
- const devicesCopy = this.getFilteredDevices()
784
-
785
- this.logger?.debug(
786
- `Notifying ${this.deviceChangeListeners.size} listeners with ${devicesCopy.length} devices ` +
787
- `(${this.temporarilyDisconnectedDevices.size} temporarily hidden)`
788
- )
789
-
790
- this.deviceChangeListeners.forEach((listener) => {
791
- try {
792
- listener(devicesCopy)
793
- } catch (error) {
794
- this.logger?.error('Error in device change listener:', error)
795
- }
796
- })
797
- }
798
- }
799
-
800
- // Create and export the singleton instance
801
- export const audioDeviceManager = new AudioDeviceManager()
802
-
803
- export { DeviceDisconnectionBehavior }