@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,708 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AudioStudioWeb = void 0;
4
+ // src/AudioStudio.web.ts
5
+ const expo_modules_core_1 = require("expo-modules-core");
6
+ const WebRecorder_web_1 = require("./WebRecorder.web");
7
+ const constants_1 = require("./constants");
8
+ const encodingToBitDepth_1 = require("./utils/encodingToBitDepth");
9
+ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
10
+ customRecorder;
11
+ audioChunks;
12
+ isRecording;
13
+ isPaused;
14
+ recordingStartTime;
15
+ pausedTime;
16
+ currentDurationMs;
17
+ currentSize;
18
+ currentInterval;
19
+ currentIntervalAnalysis;
20
+ lastEmittedSize;
21
+ lastEmittedTime;
22
+ lastEmittedCompressionSize;
23
+ lastEmittedAnalysisTime;
24
+ streamUuid;
25
+ extension = 'wav'; // Default extension is 'wav'
26
+ recordingConfig;
27
+ bitDepth; // Bit depth of the audio
28
+ audioWorkletUrl;
29
+ featuresExtratorUrl;
30
+ logger;
31
+ latestPosition = 0;
32
+ totalCompressedSize = 0;
33
+ maxBufferSize;
34
+ eventCallback;
35
+ constructor({ audioWorkletUrl, featuresExtratorUrl, logger, maxBufferSize = constants_1.DEFAULT_MAX_BUFFER_SIZE, }) {
36
+ const mockNativeModule = {
37
+ addListener: () => { },
38
+ removeListeners: () => { },
39
+ };
40
+ super(mockNativeModule); // Pass the mock native module to the parent class
41
+ this.logger = logger;
42
+ this.customRecorder = null;
43
+ this.audioChunks = [];
44
+ this.isRecording = false;
45
+ this.isPaused = false;
46
+ this.recordingStartTime = 0;
47
+ this.pausedTime = 0;
48
+ this.currentDurationMs = 0;
49
+ this.currentSize = 0;
50
+ this.bitDepth = constants_1.DEFAULT_BIT_DEPTH;
51
+ this.currentInterval = constants_1.DEFAULT_INTERVAL_MS;
52
+ this.currentIntervalAnalysis = constants_1.DEFAULT_ANALYSIS_INTERVAL_MS;
53
+ this.lastEmittedSize = 0;
54
+ this.lastEmittedTime = 0;
55
+ this.latestPosition = 0;
56
+ this.lastEmittedCompressionSize = 0;
57
+ this.lastEmittedAnalysisTime = 0;
58
+ this.streamUuid = null; // Initialize UUID on first recording start
59
+ this.audioWorkletUrl = audioWorkletUrl;
60
+ this.featuresExtratorUrl = featuresExtratorUrl;
61
+ this.maxBufferSize = maxBufferSize;
62
+ }
63
+ // Utility to handle user media stream
64
+ async getMediaStream() {
65
+ try {
66
+ this.logger?.debug('Requesting user media (microphone)...');
67
+ // First check if the browser supports the necessary audio APIs
68
+ if (!navigator?.mediaDevices?.getUserMedia) {
69
+ this.logger?.error('Browser does not support mediaDevices.getUserMedia');
70
+ throw new Error('Browser does not support audio recording');
71
+ }
72
+ // Get media with detailed audio constraints for better diagnostics
73
+ const constraints = {
74
+ audio: {
75
+ echoCancellation: true,
76
+ noiseSuppression: true,
77
+ autoGainControl: true,
78
+ // Add deviceId constraint if specified
79
+ ...(this.recordingConfig?.deviceId
80
+ ? {
81
+ deviceId: {
82
+ exact: this.recordingConfig.deviceId,
83
+ },
84
+ }
85
+ : {}),
86
+ },
87
+ };
88
+ this.logger?.debug('Media constraints:', constraints);
89
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
90
+ // Get detailed info about the audio track for debugging
91
+ const audioTracks = stream.getAudioTracks();
92
+ if (audioTracks.length > 0) {
93
+ const track = audioTracks[0];
94
+ const settings = track.getSettings();
95
+ this.logger?.debug('Audio track obtained:', {
96
+ label: track.label,
97
+ id: track.id,
98
+ enabled: track.enabled,
99
+ muted: track.muted,
100
+ readyState: track.readyState,
101
+ settings,
102
+ });
103
+ }
104
+ else {
105
+ this.logger?.warn('Stream has no audio tracks!');
106
+ }
107
+ return stream;
108
+ }
109
+ catch (error) {
110
+ this.logger?.error('Failed to get media stream:', error);
111
+ throw error;
112
+ }
113
+ }
114
+ // Prepare recording with options
115
+ async prepareRecording(recordingConfig = {}) {
116
+ if (this.isRecording) {
117
+ this.logger?.warn('Cannot prepare: Recording is already in progress');
118
+ return false;
119
+ }
120
+ try {
121
+ // Check permissions and initialize basic settings
122
+ await this.getMediaStream().then((stream) => {
123
+ // Just verify we can access the microphone by getting a stream, then release it
124
+ stream.getTracks().forEach((track) => track.stop());
125
+ });
126
+ this.bitDepth = (0, encodingToBitDepth_1.encodingToBitDepth)({
127
+ encoding: recordingConfig.encoding ?? 'pcm_32bit',
128
+ });
129
+ // Store recording configuration for later use
130
+ this.recordingConfig = recordingConfig;
131
+ // Use custom filename if provided, otherwise fallback to timestamp
132
+ if (recordingConfig.filename) {
133
+ // Remove any existing extension from the filename
134
+ this.streamUuid = recordingConfig.filename.replace(/\.[^/.]+$/, '');
135
+ }
136
+ else {
137
+ this.streamUuid = Date.now().toString();
138
+ }
139
+ this.logger?.debug('Recording preparation completed successfully');
140
+ return true;
141
+ }
142
+ catch (error) {
143
+ this.logger?.error('Error preparing recording:', error);
144
+ return false;
145
+ }
146
+ }
147
+ // Start recording with options
148
+ async startRecording(recordingConfig = {}) {
149
+ if (this.isRecording) {
150
+ throw new Error('Recording is already in progress');
151
+ }
152
+ // If we haven't prepared or have different settings, prepare now
153
+ if (!this.recordingConfig ||
154
+ this.recordingConfig.sampleRate !== recordingConfig.sampleRate ||
155
+ this.recordingConfig.channels !== recordingConfig.channels ||
156
+ this.recordingConfig.encoding !== recordingConfig.encoding) {
157
+ await this.prepareRecording(recordingConfig);
158
+ }
159
+ else {
160
+ this.logger?.debug('Using previously prepared recording configuration');
161
+ }
162
+ // Save recording config for reference
163
+ this.recordingConfig = recordingConfig;
164
+ const audioContext = new (window.AudioContext ||
165
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
166
+ // @ts-ignore - Allow webkitAudioContext for Safari
167
+ window.webkitAudioContext)();
168
+ const stream = await this.getMediaStream();
169
+ const source = audioContext.createMediaStreamSource(stream);
170
+ this.customRecorder = new WebRecorder_web_1.WebRecorder({
171
+ logger: this.logger,
172
+ audioContext,
173
+ source,
174
+ recordingConfig,
175
+ emitAudioEventCallback: this.customRecorderEventCallback.bind(this),
176
+ emitAudioAnalysisCallback: this.customRecorderAnalysisCallback.bind(this),
177
+ onInterruption: this.handleRecordingInterruption.bind(this),
178
+ });
179
+ await this.customRecorder.init();
180
+ this.customRecorder.start();
181
+ this.isRecording = true;
182
+ this.recordingStartTime = Date.now();
183
+ this.pausedTime = 0;
184
+ this.isPaused = false;
185
+ this.lastEmittedSize = 0;
186
+ this.lastEmittedTime = 0;
187
+ this.lastEmittedCompressionSize = 0;
188
+ this.currentInterval = recordingConfig.interval ?? 1000;
189
+ this.currentIntervalAnalysis = recordingConfig.intervalAnalysis ?? 500;
190
+ this.lastEmittedAnalysisTime = Date.now();
191
+ // Use custom filename if provided, otherwise fallback to timestamp
192
+ if (recordingConfig.filename) {
193
+ // Remove any existing extension from the filename
194
+ this.streamUuid = recordingConfig.filename.replace(/\.[^/.]+$/, '');
195
+ }
196
+ else {
197
+ this.streamUuid = Date.now().toString();
198
+ }
199
+ const fileUri = `${this.streamUuid}.${this.extension}`;
200
+ const streamConfig = {
201
+ fileUri,
202
+ mimeType: `audio/${this.extension}`,
203
+ bitDepth: this.bitDepth,
204
+ channels: recordingConfig.channels ?? 1,
205
+ sampleRate: recordingConfig.sampleRate ?? 44100,
206
+ compression: recordingConfig.output?.compressed?.enabled
207
+ ? {
208
+ ...recordingConfig.output.compressed,
209
+ bitrate: recordingConfig.output.compressed.bitrate ?? 128000,
210
+ size: 0,
211
+ mimeType: 'audio/webm',
212
+ format: recordingConfig.output.compressed.format ?? 'opus',
213
+ compressedFileUri: '',
214
+ }
215
+ : undefined,
216
+ };
217
+ return streamConfig;
218
+ }
219
+ /**
220
+ * Centralized handler for recording interruptions
221
+ */
222
+ handleRecordingInterruption(event) {
223
+ this.logger?.debug(`Received recording interruption: ${event.reason}`);
224
+ // Update local state if the interruption should pause recording
225
+ if (event.isPaused) {
226
+ this.isPaused = true;
227
+ // If this is a device disconnection, handle according to behavior setting
228
+ if (event.reason === 'deviceDisconnected') {
229
+ this.pausedTime = Date.now();
230
+ // Check if we should try fallback to another device
231
+ if (this.recordingConfig?.deviceDisconnectionBehavior ===
232
+ 'fallback') {
233
+ this.logger?.debug('Device disconnected with fallback behavior - attempting to switch to default device');
234
+ // Try to restart with default device
235
+ this.handleDeviceFallback().catch((error) => {
236
+ // If fallback fails, emit warning
237
+ this.logger?.error('Device fallback failed:', error);
238
+ this.emit('onRecordingInterrupted', {
239
+ reason: 'deviceSwitchFailed',
240
+ isPaused: true,
241
+ timestamp: Date.now(),
242
+ message: 'Failed to switch to fallback device. Recording paused.',
243
+ });
244
+ });
245
+ }
246
+ else {
247
+ // Just warn about disconnection if fallback not enabled
248
+ this.logger?.warn('Device disconnected - recording paused automatically');
249
+ this.emit('onRecordingInterrupted', event);
250
+ }
251
+ }
252
+ else {
253
+ // For other interruption types, just emit the event
254
+ this.emit('onRecordingInterrupted', event);
255
+ }
256
+ }
257
+ else {
258
+ // If not causing a pause, just forward the event
259
+ this.emit('onRecordingInterrupted', event);
260
+ }
261
+ }
262
+ /**
263
+ * Handler for audio events from the WebRecorder
264
+ */
265
+ customRecorderEventCallback({ data, position, compression, }) {
266
+ // Keep only the latest chunks based on maxBufferSize
267
+ this.audioChunks.push(new Float32Array(data));
268
+ if (this.audioChunks.length > this.maxBufferSize) {
269
+ this.audioChunks.shift(); // Remove oldest chunk
270
+ }
271
+ this.currentSize += data.byteLength;
272
+ this.emitAudioEvent({ data, position, compression });
273
+ this.lastEmittedTime = Date.now();
274
+ this.lastEmittedSize = this.currentSize;
275
+ this.lastEmittedCompressionSize = compression?.size ?? 0;
276
+ }
277
+ /**
278
+ * Handler for audio analysis events from the WebRecorder
279
+ */
280
+ customRecorderAnalysisCallback(audioAnalysisData) {
281
+ this.emit('AudioAnalysis', audioAnalysisData);
282
+ }
283
+ // Get recording duration
284
+ getRecordingDuration() {
285
+ if (!this.isRecording) {
286
+ return 0;
287
+ }
288
+ return this.currentDurationMs;
289
+ }
290
+ emitAudioEvent({ data, position, compression }) {
291
+ const fileUri = `${this.streamUuid}.${this.extension}`;
292
+ if (compression?.size) {
293
+ this.lastEmittedCompressionSize = compression.size;
294
+ this.totalCompressedSize = compression.totalSize;
295
+ }
296
+ // Update latest position for tracking
297
+ this.latestPosition = position;
298
+ // Calculate duration of this chunk in ms
299
+ const sampleRate = this.recordingConfig?.sampleRate || 44100;
300
+ const chunkDurationMs = (data.length / sampleRate) * 1000;
301
+ // Handle duration calculation
302
+ if (this.customRecorder?.isFirstChunkAfterSwitch) {
303
+ this.logger?.debug(`Processing first chunk after device switch, duration preserved at ${this.currentDurationMs}ms`);
304
+ this.customRecorder.isFirstChunkAfterSwitch = false;
305
+ }
306
+ else {
307
+ this.currentDurationMs += chunkDurationMs;
308
+ }
309
+ const audioEventPayload = {
310
+ fileUri,
311
+ mimeType: `audio/${this.extension}`,
312
+ lastEmittedSize: this.lastEmittedSize,
313
+ deltaSize: data.byteLength,
314
+ position,
315
+ totalSize: this.currentSize,
316
+ buffer: data,
317
+ streamUuid: this.streamUuid ?? '',
318
+ compression: compression
319
+ ? {
320
+ data: compression?.data,
321
+ totalSize: this.totalCompressedSize,
322
+ eventDataSize: compression?.size ?? 0,
323
+ position,
324
+ }
325
+ : undefined,
326
+ };
327
+ this.emit('AudioData', audioEventPayload);
328
+ }
329
+ // Stop recording
330
+ async stopRecording() {
331
+ if (!this.customRecorder) {
332
+ throw new Error('Recorder is not initialized');
333
+ }
334
+ this.logger?.debug('Starting stop process');
335
+ try {
336
+ const { compressedBlob, uncompressedBlob } = await this.customRecorder.stop();
337
+ this.isRecording = false;
338
+ this.isPaused = false;
339
+ let compression;
340
+ let fileUri = `${this.streamUuid}.${this.extension}`;
341
+ let mimeType = `audio/${this.extension}`;
342
+ // Handle both compressed and uncompressed blobs according to new output configuration
343
+ const primaryEnabled = this.recordingConfig?.output?.primary?.enabled ?? true;
344
+ const compressedEnabled = this.recordingConfig?.output?.compressed?.enabled ?? false;
345
+ // Process compressed blob if available and enabled
346
+ if (compressedBlob && compressedEnabled) {
347
+ const compressedUri = URL.createObjectURL(compressedBlob);
348
+ const compressedInfo = {
349
+ compressedFileUri: compressedUri,
350
+ size: compressedBlob.size,
351
+ mimeType: 'audio/webm',
352
+ format: this.recordingConfig?.output?.compressed?.format ??
353
+ 'opus',
354
+ bitrate: this.recordingConfig?.output?.compressed?.bitrate ??
355
+ 128000,
356
+ };
357
+ // Store compression info
358
+ compression = compressedInfo;
359
+ // If primary is disabled, use compressed as main file
360
+ if (!primaryEnabled) {
361
+ this.logger?.debug('Using compressed audio as primary output (primary disabled)');
362
+ fileUri = compressedUri;
363
+ mimeType = 'audio/webm';
364
+ }
365
+ }
366
+ // Process uncompressed WAV if available and primary is enabled
367
+ if (uncompressedBlob && primaryEnabled) {
368
+ const wavUri = URL.createObjectURL(uncompressedBlob);
369
+ fileUri = wavUri;
370
+ mimeType = 'audio/wav';
371
+ }
372
+ else if (!primaryEnabled && !compressedEnabled) {
373
+ // No outputs enabled - streaming only mode
374
+ this.logger?.debug('No outputs enabled - streaming only mode');
375
+ fileUri = '';
376
+ mimeType = 'audio/wav';
377
+ }
378
+ // Use the stored streamUuid for the final filename
379
+ const filename = fileUri
380
+ ? `${this.streamUuid}.${this.extension}`
381
+ : 'stream-only';
382
+ const result = {
383
+ fileUri,
384
+ filename,
385
+ bitDepth: this.bitDepth,
386
+ createdAt: this.recordingStartTime,
387
+ channels: this.recordingConfig?.channels ?? 1,
388
+ sampleRate: this.recordingConfig?.sampleRate ?? 44100,
389
+ durationMs: this.currentDurationMs,
390
+ size: primaryEnabled ? this.currentSize : 0,
391
+ mimeType,
392
+ compression,
393
+ };
394
+ // Reset after creating the result
395
+ this.streamUuid = null;
396
+ // Reset recording state variables to prepare for next recording
397
+ this.currentDurationMs = 0;
398
+ this.currentSize = 0;
399
+ this.lastEmittedSize = 0;
400
+ this.totalCompressedSize = 0;
401
+ this.lastEmittedCompressionSize = 0;
402
+ this.audioChunks = [];
403
+ return result;
404
+ }
405
+ catch (error) {
406
+ this.logger?.error('Error stopping recording:', error);
407
+ throw error;
408
+ }
409
+ }
410
+ // Pause recording
411
+ async pauseRecording() {
412
+ if (!this.isRecording) {
413
+ throw new Error('Recording is not active');
414
+ }
415
+ if (this.isPaused) {
416
+ this.logger?.debug('Recording already paused, skipping');
417
+ return;
418
+ }
419
+ try {
420
+ if (this.customRecorder) {
421
+ this.customRecorder.pause();
422
+ }
423
+ this.isPaused = true;
424
+ this.pausedTime = Date.now();
425
+ }
426
+ catch (error) {
427
+ this.logger?.error('Error in pauseRecording', error);
428
+ // Even if the pause operation failed, make sure our state is consistent
429
+ this.isPaused = true;
430
+ this.pausedTime = Date.now();
431
+ }
432
+ }
433
+ // Resume recording
434
+ async resumeRecording() {
435
+ if (!this.isPaused) {
436
+ throw new Error('Recording is not paused');
437
+ }
438
+ this.logger?.debug('Resuming recording', {
439
+ deviceDisconnectionBehavior: this.recordingConfig?.deviceDisconnectionBehavior,
440
+ isDeviceDisconnected: this.customRecorder?.isDeviceDisconnected,
441
+ });
442
+ try {
443
+ // If we have no recorder, or if the device is disconnected, always attempt fallback
444
+ if (!this.customRecorder ||
445
+ this.customRecorder.isDeviceDisconnected) {
446
+ this.logger?.debug('No recorder exists or device disconnected - attempting fallback on resume');
447
+ await this.handleDeviceFallback();
448
+ // handleDeviceFallback will manage resuming if successful, or emit error if failed.
449
+ return;
450
+ }
451
+ // Normal resume path - device is still connected
452
+ this.customRecorder.resume();
453
+ this.isPaused = false;
454
+ // Adjust the recording start time to account for the pause duration
455
+ const pauseDuration = Date.now() - this.pausedTime;
456
+ this.recordingStartTime += pauseDuration;
457
+ this.pausedTime = 0;
458
+ this.emit('onRecordingInterrupted', {
459
+ reason: 'userResumed',
460
+ isPaused: false,
461
+ timestamp: Date.now(),
462
+ });
463
+ }
464
+ catch (error) {
465
+ this.logger?.error('Resume failed:', error);
466
+ // Fallback to emitting a general failure if resume fails unexpectedly
467
+ this.emit('onRecordingInterrupted', {
468
+ reason: 'resumeFailed', // Use a more specific reason
469
+ isPaused: true, // Remain paused if resume fails
470
+ timestamp: Date.now(),
471
+ message: 'Failed to resume recording. Please stop and start again.',
472
+ });
473
+ }
474
+ }
475
+ // Get current status
476
+ status() {
477
+ const durationMs = this.getRecordingDuration();
478
+ const status = {
479
+ isRecording: this.isRecording,
480
+ isPaused: this.isPaused,
481
+ durationMs,
482
+ size: this.currentSize,
483
+ interval: this.currentInterval,
484
+ intervalAnalysis: this.currentIntervalAnalysis,
485
+ mimeType: `audio/${this.extension}`,
486
+ compression: this.recordingConfig?.output?.compressed?.enabled
487
+ ? {
488
+ size: this.totalCompressedSize,
489
+ mimeType: 'audio/webm',
490
+ format: this.recordingConfig.output.compressed.format ??
491
+ 'opus',
492
+ bitrate: this.recordingConfig.output.compressed.bitrate ??
493
+ 128000,
494
+ compressedFileUri: `${this.streamUuid}.webm`,
495
+ }
496
+ : undefined,
497
+ };
498
+ return status;
499
+ }
500
+ /**
501
+ * Handles device fallback when the current device is disconnected
502
+ */
503
+ async handleDeviceFallback() {
504
+ this.logger?.debug('Starting device fallback procedure');
505
+ if (!this.isRecording) {
506
+ return false;
507
+ }
508
+ try {
509
+ // Save important state before switching
510
+ const currentPosition = this.latestPosition;
511
+ const existingAudioChunks = [...this.audioChunks];
512
+ // Save compressed chunks if available
513
+ let compressedChunks = [];
514
+ if (this.customRecorder) {
515
+ try {
516
+ compressedChunks = this.customRecorder.getCompressedChunks();
517
+ }
518
+ catch (err) {
519
+ this.logger?.warn('Failed to get compressed chunks:', err);
520
+ }
521
+ }
522
+ // Save the current counter value for continuity
523
+ let currentDataPointCounter = 0;
524
+ if (this.customRecorder) {
525
+ currentDataPointCounter =
526
+ this.customRecorder.getDataPointCounter();
527
+ }
528
+ // Clean up existing recorder
529
+ if (this.customRecorder) {
530
+ try {
531
+ this.customRecorder.cleanup();
532
+ }
533
+ catch (cleanupError) {
534
+ this.logger?.warn('Error during cleanup:', cleanupError);
535
+ }
536
+ }
537
+ // Keep recording state true but mark as paused
538
+ this.isPaused = true;
539
+ this.pausedTime = Date.now();
540
+ // Store current size and other stats
541
+ const previousTotalSize = this.currentSize;
542
+ const previousLastEmittedSize = this.lastEmittedSize;
543
+ const previousCompressedSize = this.totalCompressedSize;
544
+ // Try to get a fallback device
545
+ const fallbackDeviceInfo = await this.getFallbackDevice();
546
+ if (!fallbackDeviceInfo) {
547
+ this.emit('onRecordingInterrupted', {
548
+ reason: 'deviceSwitchFailed',
549
+ isPaused: true,
550
+ timestamp: Date.now(),
551
+ message: 'Failed to switch to fallback device. Recording paused.',
552
+ });
553
+ return false;
554
+ }
555
+ // Start recording with the new device
556
+ try {
557
+ const stream = await this.requestPermissionsAndGetUserMedia(fallbackDeviceInfo.deviceId);
558
+ const audioContext = new (window.AudioContext ||
559
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
560
+ // @ts-ignore - Allow webkitAudioContext for Safari
561
+ window.webkitAudioContext)();
562
+ const source = audioContext.createMediaStreamSource(stream);
563
+ // Create a new recorder with the fallback device
564
+ this.customRecorder = new WebRecorder_web_1.WebRecorder({
565
+ logger: this.logger,
566
+ audioContext,
567
+ source,
568
+ recordingConfig: this.recordingConfig || {},
569
+ emitAudioEventCallback: this.customRecorderEventCallback.bind(this),
570
+ emitAudioAnalysisCallback: this.customRecorderAnalysisCallback.bind(this),
571
+ onInterruption: this.handleRecordingInterruption.bind(this),
572
+ });
573
+ await this.customRecorder.init();
574
+ // Set the initial position to continue from the previous device
575
+ this.customRecorder.setPosition(currentPosition);
576
+ // Reset the data point counter to continue from where the previous device left off
577
+ if (currentDataPointCounter > 0) {
578
+ this.customRecorder.resetDataPointCounter(currentDataPointCounter);
579
+ }
580
+ // Prepare the recorder to handle the device switch properly
581
+ this.customRecorder.prepareForDeviceSwitch();
582
+ // Restore the existing audio chunks
583
+ if (existingAudioChunks.length > 0) {
584
+ this.audioChunks = existingAudioChunks;
585
+ }
586
+ // Restore compressed chunks if available
587
+ if (compressedChunks.length > 0) {
588
+ this.customRecorder.setCompressedChunks(compressedChunks);
589
+ }
590
+ // Start the new recorder while preserving counters
591
+ this.customRecorder.start(true);
592
+ // Update recording state
593
+ this.isPaused = false;
594
+ this.recordingStartTime = Date.now();
595
+ // Restore size counters to maintain continuity
596
+ this.currentSize = previousTotalSize;
597
+ this.lastEmittedSize = previousLastEmittedSize;
598
+ this.totalCompressedSize = previousCompressedSize;
599
+ // Notify that we switched to a fallback device
600
+ if (this.eventCallback) {
601
+ this.eventCallback({
602
+ type: 'deviceFallback',
603
+ device: fallbackDeviceInfo.deviceId,
604
+ timestamp: new Date(),
605
+ });
606
+ }
607
+ return true;
608
+ }
609
+ catch (error) {
610
+ this.logger?.error('Failed to start recording with fallback device', error);
611
+ this.isPaused = true;
612
+ this.emit('onRecordingInterrupted', {
613
+ reason: 'deviceSwitchFailed',
614
+ isPaused: true,
615
+ timestamp: Date.now(),
616
+ message: 'Failed to switch to fallback device. Recording paused.',
617
+ });
618
+ return false;
619
+ }
620
+ }
621
+ catch (error) {
622
+ this.logger?.error('Failed to use fallback device', error);
623
+ this.isPaused = true;
624
+ this.emit('onRecordingInterrupted', {
625
+ reason: 'deviceSwitchFailed',
626
+ isPaused: true,
627
+ timestamp: Date.now(),
628
+ message: 'Failed to switch to fallback device. Recording paused.',
629
+ });
630
+ return false;
631
+ }
632
+ }
633
+ /**
634
+ * Attempts to get a fallback audio device
635
+ */
636
+ async getFallbackDevice() {
637
+ try {
638
+ // Get list of available audio input devices
639
+ const devices = await navigator.mediaDevices.enumerateDevices();
640
+ const audioInputDevices = devices.filter((device) => device.kind === 'audioinput');
641
+ if (audioInputDevices.length === 0) {
642
+ return null;
643
+ }
644
+ // Try to find a device that's not the current one
645
+ if (this.customRecorder) {
646
+ try {
647
+ // Use mediaDevices.enumerateDevices to find the current active device
648
+ const tracks = navigator.mediaDevices
649
+ .getUserMedia({ audio: true })
650
+ .then((stream) => {
651
+ const track = stream.getAudioTracks()[0];
652
+ return track ? track.label : '';
653
+ })
654
+ .catch(() => '');
655
+ const currentTrackLabel = await tracks;
656
+ if (currentTrackLabel) {
657
+ // Find a device with a different label
658
+ const differentDevice = audioInputDevices.find((device) => device.label &&
659
+ device.label !== currentTrackLabel);
660
+ if (differentDevice) {
661
+ return differentDevice;
662
+ }
663
+ }
664
+ }
665
+ catch (err) {
666
+ this.logger?.warn('Error determining current device, using default', err);
667
+ }
668
+ }
669
+ // Return the first available device (default device)
670
+ return audioInputDevices[0];
671
+ }
672
+ catch (error) {
673
+ this.logger?.error('Error finding fallback device:', error);
674
+ return null;
675
+ }
676
+ }
677
+ /**
678
+ * Gets user media with specific device ID
679
+ */
680
+ async requestPermissionsAndGetUserMedia(deviceId) {
681
+ try {
682
+ // Request the specific device
683
+ return await navigator.mediaDevices.getUserMedia({
684
+ audio: {
685
+ deviceId: { exact: deviceId },
686
+ },
687
+ });
688
+ }
689
+ catch (error) {
690
+ this.logger?.error(`Failed to get media for device ${deviceId}`, error);
691
+ // Try with default constraints as fallback
692
+ return await navigator.mediaDevices.getUserMedia({ audio: true });
693
+ }
694
+ }
695
+ init(options) {
696
+ try {
697
+ this.logger = options?.logger;
698
+ this.eventCallback = options?.eventCallback;
699
+ return Promise.resolve();
700
+ }
701
+ catch (error) {
702
+ this.logger?.error('Error initializing AudioStudio', error);
703
+ return Promise.reject(error);
704
+ }
705
+ }
706
+ }
707
+ exports.AudioStudioWeb = AudioStudioWeb;
708
+ //# sourceMappingURL=AudioStudio.web.js.map