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