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