@siteed/expo-audio-studio 2.18.6 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/README.md +13 -297
  2. package/index.d.ts +1 -0
  3. package/index.js +1 -0
  4. package/package.json +7 -136
  5. package/CHANGELOG.md +0 -501
  6. package/LICENSE +0 -21
  7. package/android/build.gradle +0 -129
  8. package/android/src/androidTest/assets/chorus.wav +0 -0
  9. package/android/src/androidTest/assets/jfk.wav +0 -0
  10. package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
  11. package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
  12. package/android/src/androidTest/java/net/siteed/audiostream/AudioProcessorInstrumentedTest.kt +0 -197
  13. package/android/src/androidTest/java/net/siteed/audiostream/AudioRecorderInstrumentedTest.kt +0 -541
  14. package/android/src/androidTest/java/net/siteed/audiostream/AudioRecorderPerformanceInstrumentedTest.kt +0 -234
  15. package/android/src/androidTest/java/net/siteed/audiostream/integration/AudioFocusStrategyIntegrationTest.kt +0 -332
  16. package/android/src/androidTest/java/net/siteed/audiostream/integration/BufferDurationIntegrationTest.kt +0 -324
  17. package/android/src/androidTest/java/net/siteed/audiostream/integration/CompressedOnlyOutputTest.kt +0 -253
  18. package/android/src/androidTest/java/net/siteed/audiostream/integration/DeviceDisconnectionFallbackTest.kt +0 -218
  19. package/android/src/androidTest/java/net/siteed/audiostream/integration/EventEmissionIntervalTest.kt +0 -120
  20. package/android/src/androidTest/java/net/siteed/audiostream/integration/M4aFormatTest.kt +0 -345
  21. package/android/src/androidTest/java/net/siteed/audiostream/integration/OutputControlIntegrationTest.kt +0 -340
  22. package/android/src/androidTest/java/net/siteed/audiostream/integration/PcmStreamingDurationTest.kt +0 -252
  23. package/android/src/androidTest/java/net/siteed/audiostream/integration/README.md +0 -95
  24. package/android/src/androidTest/java/net/siteed/audiostream/integration/run_integration_tests.sh +0 -43
  25. package/android/src/main/AndroidManifest.xml +0 -30
  26. package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +0 -188
  27. package/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +0 -9
  28. package/android/src/main/java/net/siteed/audiostream/AudioDeviceManager.kt +0 -1741
  29. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +0 -136
  30. package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +0 -354
  31. package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +0 -439
  32. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +0 -2237
  33. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +0 -2141
  34. package/android/src/main/java/net/siteed/audiostream/AudioRecordingService.kt +0 -167
  35. package/android/src/main/java/net/siteed/audiostream/AudioTrimmer.kt +0 -1099
  36. package/android/src/main/java/net/siteed/audiostream/Constants.kt +0 -37
  37. package/android/src/main/java/net/siteed/audiostream/EventSender.kt +0 -7
  38. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +0 -1113
  39. package/android/src/main/java/net/siteed/audiostream/FFT.kt +0 -99
  40. package/android/src/main/java/net/siteed/audiostream/Features.kt +0 -98
  41. package/android/src/main/java/net/siteed/audiostream/LogUtils.kt +0 -93
  42. package/android/src/main/java/net/siteed/audiostream/NotificationConfig.kt +0 -72
  43. package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +0 -68
  44. package/android/src/main/java/net/siteed/audiostream/RecordingActionReceiver.kt +0 -59
  45. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +0 -257
  46. package/android/src/main/java/net/siteed/audiostream/WaveformConfig.kt +0 -19
  47. package/android/src/main/java/net/siteed/audiostream/WaveformRenderer.kt +0 -159
  48. package/android/src/main/res/drawable/ic_default_action_icon.xml +0 -16
  49. package/android/src/main/res/drawable/ic_microphone.xml +0 -13
  50. package/android/src/main/res/drawable/ic_pause.xml +0 -10
  51. package/android/src/main/res/drawable/ic_play.xml +0 -10
  52. package/android/src/main/res/drawable/ic_stop.xml +0 -10
  53. package/android/src/main/res/layout/notification_recording.xml +0 -37
  54. package/android/src/test/java/net/siteed/audiostream/AudioFileHandlerTest.kt +0 -279
  55. package/android/src/test/java/net/siteed/audiostream/AudioFocusStrategyTest.kt +0 -249
  56. package/android/src/test/java/net/siteed/audiostream/AudioFormatTest.kt +0 -151
  57. package/android/src/test/java/net/siteed/audiostream/AudioFormatUtilsTest.kt +0 -273
  58. package/android/src/test/java/net/siteed/audiostream/DeviceDisconnectionFallbackUnitTest.kt +0 -140
  59. package/android/src/test/resources/chorus.wav +0 -0
  60. package/android/src/test/resources/generate_test_audio.py +0 -94
  61. package/android/src/test/resources/jfk.wav +0 -0
  62. package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
  63. package/android/src/test/resources/recorder_hello_world.wav +0 -0
  64. package/app.plugin.js +0 -3
  65. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js +0 -4
  66. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +0 -1
  67. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js +0 -210
  68. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
  69. package/build/cjs/AudioAnalysis/extractAudioData.js +0 -21
  70. package/build/cjs/AudioAnalysis/extractAudioData.js.map +0 -1
  71. package/build/cjs/AudioAnalysis/extractMelSpectrogram.js +0 -92
  72. package/build/cjs/AudioAnalysis/extractMelSpectrogram.js.map +0 -1
  73. package/build/cjs/AudioAnalysis/extractPreview.js +0 -28
  74. package/build/cjs/AudioAnalysis/extractPreview.js.map +0 -1
  75. package/build/cjs/AudioAnalysis/extractWaveform.js +0 -18
  76. package/build/cjs/AudioAnalysis/extractWaveform.js.map +0 -1
  77. package/build/cjs/AudioDeviceManager.js +0 -689
  78. package/build/cjs/AudioDeviceManager.js.map +0 -1
  79. package/build/cjs/AudioRecorder.provider.js +0 -78
  80. package/build/cjs/AudioRecorder.provider.js.map +0 -1
  81. package/build/cjs/ExpoAudioStream.native.js +0 -8
  82. package/build/cjs/ExpoAudioStream.native.js.map +0 -1
  83. package/build/cjs/ExpoAudioStream.types.js +0 -11
  84. package/build/cjs/ExpoAudioStream.types.js.map +0 -1
  85. package/build/cjs/ExpoAudioStream.web.js +0 -708
  86. package/build/cjs/ExpoAudioStream.web.js.map +0 -1
  87. package/build/cjs/ExpoAudioStreamModule.js +0 -718
  88. package/build/cjs/ExpoAudioStreamModule.js.map +0 -1
  89. package/build/cjs/WebRecorder.web.js +0 -777
  90. package/build/cjs/WebRecorder.web.js.map +0 -1
  91. package/build/cjs/constants/platformLimitations.js +0 -99
  92. package/build/cjs/constants/platformLimitations.js.map +0 -1
  93. package/build/cjs/constants.js +0 -17
  94. package/build/cjs/constants.js.map +0 -1
  95. package/build/cjs/events.js +0 -29
  96. package/build/cjs/events.js.map +0 -1
  97. package/build/cjs/hooks/useAudioDevices.js +0 -179
  98. package/build/cjs/hooks/useAudioDevices.js.map +0 -1
  99. package/build/cjs/index.js +0 -58
  100. package/build/cjs/index.js.map +0 -1
  101. package/build/cjs/trimAudio.js +0 -76
  102. package/build/cjs/trimAudio.js.map +0 -1
  103. package/build/cjs/useAudioRecorder.js +0 -518
  104. package/build/cjs/useAudioRecorder.js.map +0 -1
  105. package/build/cjs/utils/BlobFix.js +0 -502
  106. package/build/cjs/utils/BlobFix.js.map +0 -1
  107. package/build/cjs/utils/audioProcessing.js +0 -136
  108. package/build/cjs/utils/audioProcessing.js.map +0 -1
  109. package/build/cjs/utils/cleanNativeOptions.js +0 -22
  110. package/build/cjs/utils/cleanNativeOptions.js.map +0 -1
  111. package/build/cjs/utils/concatenateBuffers.js +0 -25
  112. package/build/cjs/utils/concatenateBuffers.js.map +0 -1
  113. package/build/cjs/utils/convertPCMToFloat32.js +0 -124
  114. package/build/cjs/utils/convertPCMToFloat32.js.map +0 -1
  115. package/build/cjs/utils/crc32.js +0 -52
  116. package/build/cjs/utils/crc32.js.map +0 -1
  117. package/build/cjs/utils/encodingToBitDepth.js +0 -17
  118. package/build/cjs/utils/encodingToBitDepth.js.map +0 -1
  119. package/build/cjs/utils/getWavFileInfo.js +0 -96
  120. package/build/cjs/utils/getWavFileInfo.js.map +0 -1
  121. package/build/cjs/utils/writeWavHeader.js +0 -88
  122. package/build/cjs/utils/writeWavHeader.js.map +0 -1
  123. package/build/cjs/workers/InlineFeaturesExtractor.web.js +0 -859
  124. package/build/cjs/workers/InlineFeaturesExtractor.web.js.map +0 -1
  125. package/build/cjs/workers/inlineAudioWebWorker.web.js +0 -184
  126. package/build/cjs/workers/inlineAudioWebWorker.web.js.map +0 -1
  127. package/build/esm/AudioAnalysis/AudioAnalysis.types.js +0 -3
  128. package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +0 -1
  129. package/build/esm/AudioAnalysis/extractAudioAnalysis.js +0 -202
  130. package/build/esm/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
  131. package/build/esm/AudioAnalysis/extractAudioData.js +0 -14
  132. package/build/esm/AudioAnalysis/extractAudioData.js.map +0 -1
  133. package/build/esm/AudioAnalysis/extractMelSpectrogram.js +0 -89
  134. package/build/esm/AudioAnalysis/extractMelSpectrogram.js.map +0 -1
  135. package/build/esm/AudioAnalysis/extractPreview.js +0 -25
  136. package/build/esm/AudioAnalysis/extractPreview.js.map +0 -1
  137. package/build/esm/AudioAnalysis/extractWaveform.js +0 -11
  138. package/build/esm/AudioAnalysis/extractWaveform.js.map +0 -1
  139. package/build/esm/AudioDeviceManager.js +0 -682
  140. package/build/esm/AudioDeviceManager.js.map +0 -1
  141. package/build/esm/AudioRecorder.provider.js +0 -40
  142. package/build/esm/AudioRecorder.provider.js.map +0 -1
  143. package/build/esm/ExpoAudioStream.native.js +0 -6
  144. package/build/esm/ExpoAudioStream.native.js.map +0 -1
  145. package/build/esm/ExpoAudioStream.types.js +0 -8
  146. package/build/esm/ExpoAudioStream.types.js.map +0 -1
  147. package/build/esm/ExpoAudioStream.web.js +0 -704
  148. package/build/esm/ExpoAudioStream.web.js.map +0 -1
  149. package/build/esm/ExpoAudioStreamModule.js +0 -713
  150. package/build/esm/ExpoAudioStreamModule.js.map +0 -1
  151. package/build/esm/WebRecorder.web.js +0 -773
  152. package/build/esm/WebRecorder.web.js.map +0 -1
  153. package/build/esm/constants/platformLimitations.js +0 -90
  154. package/build/esm/constants/platformLimitations.js.map +0 -1
  155. package/build/esm/constants.js +0 -14
  156. package/build/esm/constants.js.map +0 -1
  157. package/build/esm/events.js +0 -21
  158. package/build/esm/events.js.map +0 -1
  159. package/build/esm/hooks/useAudioDevices.js +0 -176
  160. package/build/esm/hooks/useAudioDevices.js.map +0 -1
  161. package/build/esm/index.js +0 -20
  162. package/build/esm/index.js.map +0 -1
  163. package/build/esm/trimAudio.js +0 -69
  164. package/build/esm/trimAudio.js.map +0 -1
  165. package/build/esm/useAudioRecorder.js +0 -512
  166. package/build/esm/useAudioRecorder.js.map +0 -1
  167. package/build/esm/utils/BlobFix.js +0 -498
  168. package/build/esm/utils/BlobFix.js.map +0 -1
  169. package/build/esm/utils/audioProcessing.js +0 -133
  170. package/build/esm/utils/audioProcessing.js.map +0 -1
  171. package/build/esm/utils/cleanNativeOptions.js +0 -19
  172. package/build/esm/utils/cleanNativeOptions.js.map +0 -1
  173. package/build/esm/utils/concatenateBuffers.js +0 -21
  174. package/build/esm/utils/concatenateBuffers.js.map +0 -1
  175. package/build/esm/utils/convertPCMToFloat32.js +0 -120
  176. package/build/esm/utils/convertPCMToFloat32.js.map +0 -1
  177. package/build/esm/utils/crc32.js +0 -50
  178. package/build/esm/utils/crc32.js.map +0 -1
  179. package/build/esm/utils/encodingToBitDepth.js +0 -13
  180. package/build/esm/utils/encodingToBitDepth.js.map +0 -1
  181. package/build/esm/utils/getWavFileInfo.js +0 -92
  182. package/build/esm/utils/getWavFileInfo.js.map +0 -1
  183. package/build/esm/utils/writeWavHeader.js +0 -84
  184. package/build/esm/utils/writeWavHeader.js.map +0 -1
  185. package/build/esm/workers/InlineFeaturesExtractor.web.js +0 -856
  186. package/build/esm/workers/InlineFeaturesExtractor.web.js.map +0 -1
  187. package/build/esm/workers/inlineAudioWebWorker.web.js +0 -181
  188. package/build/esm/workers/inlineAudioWebWorker.web.js.map +0 -1
  189. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts +0 -196
  190. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +0 -1
  191. package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts +0 -74
  192. package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts.map +0 -1
  193. package/build/types/AudioAnalysis/extractAudioData.d.ts +0 -3
  194. package/build/types/AudioAnalysis/extractAudioData.d.ts.map +0 -1
  195. package/build/types/AudioAnalysis/extractMelSpectrogram.d.ts +0 -14
  196. package/build/types/AudioAnalysis/extractMelSpectrogram.d.ts.map +0 -1
  197. package/build/types/AudioAnalysis/extractPreview.d.ts +0 -11
  198. package/build/types/AudioAnalysis/extractPreview.d.ts.map +0 -1
  199. package/build/types/AudioAnalysis/extractWaveform.d.ts +0 -8
  200. package/build/types/AudioAnalysis/extractWaveform.d.ts.map +0 -1
  201. package/build/types/AudioDeviceManager.d.ts +0 -187
  202. package/build/types/AudioDeviceManager.d.ts.map +0 -1
  203. package/build/types/AudioRecorder.provider.d.ts +0 -11
  204. package/build/types/AudioRecorder.provider.d.ts.map +0 -1
  205. package/build/types/ExpoAudioStream.native.d.ts +0 -3
  206. package/build/types/ExpoAudioStream.native.d.ts.map +0 -1
  207. package/build/types/ExpoAudioStream.types.d.ts +0 -738
  208. package/build/types/ExpoAudioStream.types.d.ts.map +0 -1
  209. package/build/types/ExpoAudioStream.web.d.ts +0 -96
  210. package/build/types/ExpoAudioStream.web.d.ts.map +0 -1
  211. package/build/types/ExpoAudioStreamModule.d.ts +0 -3
  212. package/build/types/ExpoAudioStreamModule.d.ts.map +0 -1
  213. package/build/types/WebRecorder.web.d.ts +0 -198
  214. package/build/types/WebRecorder.web.d.ts.map +0 -1
  215. package/build/types/constants/platformLimitations.d.ts +0 -40
  216. package/build/types/constants/platformLimitations.d.ts.map +0 -1
  217. package/build/types/constants.d.ts +0 -11
  218. package/build/types/constants.d.ts.map +0 -1
  219. package/build/types/events.d.ts +0 -26
  220. package/build/types/events.d.ts.map +0 -1
  221. package/build/types/hooks/useAudioDevices.d.ts +0 -15
  222. package/build/types/hooks/useAudioDevices.d.ts.map +0 -1
  223. package/build/types/index.d.ts +0 -18
  224. package/build/types/index.d.ts.map +0 -1
  225. package/build/types/trimAudio.d.ts +0 -25
  226. package/build/types/trimAudio.d.ts.map +0 -1
  227. package/build/types/useAudioRecorder.d.ts +0 -22
  228. package/build/types/useAudioRecorder.d.ts.map +0 -1
  229. package/build/types/utils/BlobFix.d.ts +0 -9
  230. package/build/types/utils/BlobFix.d.ts.map +0 -1
  231. package/build/types/utils/audioProcessing.d.ts +0 -24
  232. package/build/types/utils/audioProcessing.d.ts.map +0 -1
  233. package/build/types/utils/cleanNativeOptions.d.ts +0 -15
  234. package/build/types/utils/cleanNativeOptions.d.ts.map +0 -1
  235. package/build/types/utils/concatenateBuffers.d.ts +0 -8
  236. package/build/types/utils/concatenateBuffers.d.ts.map +0 -1
  237. package/build/types/utils/convertPCMToFloat32.d.ts +0 -13
  238. package/build/types/utils/convertPCMToFloat32.d.ts.map +0 -1
  239. package/build/types/utils/crc32.d.ts +0 -7
  240. package/build/types/utils/crc32.d.ts.map +0 -1
  241. package/build/types/utils/encodingToBitDepth.d.ts +0 -5
  242. package/build/types/utils/encodingToBitDepth.d.ts.map +0 -1
  243. package/build/types/utils/getWavFileInfo.d.ts +0 -26
  244. package/build/types/utils/getWavFileInfo.d.ts.map +0 -1
  245. package/build/types/utils/writeWavHeader.d.ts +0 -34
  246. package/build/types/utils/writeWavHeader.d.ts.map +0 -1
  247. package/build/types/workers/InlineFeaturesExtractor.web.d.ts +0 -2
  248. package/build/types/workers/InlineFeaturesExtractor.web.d.ts.map +0 -1
  249. package/build/types/workers/inlineAudioWebWorker.web.d.ts +0 -2
  250. package/build/types/workers/inlineAudioWebWorker.web.d.ts.map +0 -1
  251. package/expo-module.config.json +0 -10
  252. package/ios/AudioAnalysisData.swift +0 -74
  253. package/ios/AudioDeviceManager.swift +0 -670
  254. package/ios/AudioNotificationManager.swift +0 -154
  255. package/ios/AudioProcessingHelpers.swift +0 -743
  256. package/ios/AudioProcessor.swift +0 -1151
  257. package/ios/AudioStreamError.swift +0 -7
  258. package/ios/AudioStreamManager.swift +0 -2369
  259. package/ios/AudioStreamManagerDelegate.swift +0 -16
  260. package/ios/DataPoint.swift +0 -54
  261. package/ios/DecodingConfig.swift +0 -59
  262. package/ios/ExpoAudioStream.podspec +0 -33
  263. package/ios/ExpoAudioStreamModule.swift +0 -1019
  264. package/ios/ExpoAudioStudioTests/AudioFileHandlerTests.swift +0 -338
  265. package/ios/ExpoAudioStudioTests/AudioFormatUtilsTests.swift +0 -331
  266. package/ios/ExpoAudioStudioTests/AudioTestHelpers.swift +0 -130
  267. package/ios/ExpoAudioStudioTests/CompressedOnlyOutputTests.swift +0 -294
  268. package/ios/ExpoAudioStudioTests/EventEmissionIntervalTests.swift +0 -105
  269. package/ios/ExpoAudioStudioTests/Info.plist +0 -22
  270. package/ios/ExpoAudioStudioTests/README.md +0 -39
  271. package/ios/ExpoAudioStudioTests/SimpleAudioTest.swift +0 -98
  272. package/ios/ExpoAudioStudioTests/TestAudioGenerator.swift +0 -75
  273. package/ios/FFT.swift +0 -62
  274. package/ios/Features.swift +0 -95
  275. package/ios/ISSUE_IOS.md +0 -68
  276. package/ios/Logger.swift +0 -39
  277. package/ios/NotificationExtension.swift +0 -15
  278. package/ios/RecordingResult.swift +0 -22
  279. package/ios/RecordingSettings.swift +0 -308
  280. package/ios/WaveformExtractor.swift +0 -105
  281. package/ios/tests/README.md +0 -41
  282. package/ios/tests/integration/buffer_and_fallback_test.swift +0 -178
  283. package/ios/tests/integration/buffer_duration_test.swift +0 -185
  284. package/ios/tests/integration/compressed_only_output_test.swift +0 -271
  285. package/ios/tests/integration/output_control_test.swift +0 -322
  286. package/ios/tests/integration/run_integration_tests.sh +0 -37
  287. package/ios/tests/opus_support_test_macos.swift +0 -154
  288. package/ios/tests/standalone/audio_processing_test.swift +0 -144
  289. package/ios/tests/standalone/audio_recording_test.swift +0 -277
  290. package/ios/tests/standalone/audio_streaming_test.swift +0 -249
  291. package/ios/tests/standalone/standalone_test.swift +0 -144
  292. package/plugin/build/index.cjs +0 -194
  293. package/plugin/build/index.d.cts +0 -22
  294. package/plugin/build/index.js +0 -194
  295. package/plugin/src/index.ts +0 -285
  296. package/plugin/tsconfig.json +0 -10
  297. package/plugin/tsconfig.tsbuildinfo +0 -1
  298. package/src/AudioAnalysis/AudioAnalysis.types.ts +0 -224
  299. package/src/AudioAnalysis/extractAudioAnalysis.ts +0 -344
  300. package/src/AudioAnalysis/extractAudioData.ts +0 -17
  301. package/src/AudioAnalysis/extractMelSpectrogram.ts +0 -154
  302. package/src/AudioAnalysis/extractPreview.ts +0 -34
  303. package/src/AudioAnalysis/extractWaveform.ts +0 -22
  304. package/src/AudioDeviceManager.ts +0 -803
  305. package/src/AudioRecorder.provider.tsx +0 -57
  306. package/src/ExpoAudioStream.native.ts +0 -6
  307. package/src/ExpoAudioStream.types.ts +0 -874
  308. package/src/ExpoAudioStream.web.ts +0 -905
  309. package/src/ExpoAudioStreamModule.ts +0 -990
  310. package/src/WebRecorder.web.ts +0 -1005
  311. package/src/constants/platformLimitations.ts +0 -118
  312. package/src/constants.ts +0 -18
  313. package/src/events.ts +0 -60
  314. package/src/hooks/useAudioDevices.ts +0 -213
  315. package/src/index.ts +0 -54
  316. package/src/trimAudio.ts +0 -94
  317. package/src/types/crc-32.d.ts +0 -9
  318. package/src/useAudioRecorder.tsx +0 -766
  319. package/src/utils/BlobFix.ts +0 -561
  320. package/src/utils/audioProcessing.ts +0 -205
  321. package/src/utils/cleanNativeOptions.ts +0 -18
  322. package/src/utils/concatenateBuffers.ts +0 -24
  323. package/src/utils/convertPCMToFloat32.ts +0 -170
  324. package/src/utils/crc32.ts +0 -59
  325. package/src/utils/encodingToBitDepth.ts +0 -18
  326. package/src/utils/getWavFileInfo.ts +0 -132
  327. package/src/utils/writeWavHeader.ts +0 -115
  328. package/src/workers/InlineFeaturesExtractor.web.tsx +0 -855
  329. package/src/workers/inlineAudioWebWorker.web.tsx +0 -180
@@ -1,905 +0,0 @@
1
- // src/ExpoAudioStreamModule.web.ts
2
- import { LegacyEventEmitter } from 'expo-modules-core'
3
-
4
- import { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'
5
- import {
6
- AudioRecording,
7
- AudioStreamStatus,
8
- BitDepth,
9
- ConsoleLike,
10
- RecordingConfig,
11
- RecordingInterruptionReason,
12
- StartRecordingResult,
13
- } from './ExpoAudioStream.types'
14
- import { WebRecorder } from './WebRecorder.web'
15
- import { AudioEventPayload } from './events'
16
- import { encodingToBitDepth } from './utils/encodingToBitDepth'
17
-
18
- export interface AudioStreamEvent {
19
- type: string
20
- device?: string
21
- timestamp: Date
22
- }
23
-
24
- export interface ExpoAudioStreamOptions {
25
- logger?: ConsoleLike
26
- eventCallback?: (event: AudioStreamEvent) => void
27
- }
28
-
29
- export interface EmitAudioEventProps {
30
- data: Float32Array
31
- position: number
32
- compression?: {
33
- data: Blob
34
- size: number
35
- totalSize: number
36
- mimeType: string
37
- format: string
38
- bitrate: number
39
- }
40
- }
41
- export type EmitAudioEventFunction = (_: EmitAudioEventProps) => void
42
- export type EmitAudioAnalysisFunction = (_: AudioAnalysis) => void
43
-
44
- export interface ExpoAudioStreamWebProps {
45
- logger?: ConsoleLike
46
- audioWorkletUrl: string
47
- featuresExtratorUrl: string
48
- maxBufferSize?: number // Maximum number of chunks to keep in memory
49
- }
50
-
51
- export class ExpoAudioStreamWeb extends LegacyEventEmitter {
52
- customRecorder: WebRecorder | null
53
- audioChunks: Float32Array[]
54
- isRecording: boolean
55
- isPaused: boolean
56
- recordingStartTime: number
57
- pausedTime: number
58
- currentDurationMs: number
59
- currentSize: number
60
- currentInterval: number
61
- currentIntervalAnalysis: number
62
- lastEmittedSize: number
63
- lastEmittedTime: number
64
- lastEmittedCompressionSize: number
65
- lastEmittedAnalysisTime: number
66
- streamUuid: string | null
67
- extension: 'webm' | 'wav' = 'wav' // Default extension is 'wav'
68
- recordingConfig?: RecordingConfig
69
- bitDepth: BitDepth // Bit depth of the audio
70
- audioWorkletUrl: string
71
- featuresExtratorUrl: string
72
- logger?: ConsoleLike
73
- latestPosition: number = 0
74
- totalCompressedSize: number = 0
75
- private readonly maxBufferSize: number
76
- private eventCallback?: (event: AudioStreamEvent) => void
77
-
78
- constructor({
79
- audioWorkletUrl,
80
- featuresExtratorUrl,
81
- logger,
82
- maxBufferSize = 100, // Default to storing last 100 chunks (1 chunk = 0.5 seconds)
83
- }: ExpoAudioStreamWebProps) {
84
- const mockNativeModule = {
85
- addListener: () => {},
86
- removeListeners: () => {},
87
- }
88
- super(mockNativeModule) // Pass the mock native module to the parent class
89
-
90
- this.logger = logger
91
- this.customRecorder = null
92
- this.audioChunks = []
93
- this.isRecording = false
94
- this.isPaused = false
95
- this.recordingStartTime = 0
96
- this.pausedTime = 0
97
- this.currentDurationMs = 0
98
- this.currentSize = 0
99
- this.bitDepth = 32 // Default
100
- this.currentInterval = 1000 // Default interval in ms
101
- this.currentIntervalAnalysis = 500 // Default analysis interval in ms
102
- this.lastEmittedSize = 0
103
- this.lastEmittedTime = 0
104
- this.latestPosition = 0
105
- this.lastEmittedCompressionSize = 0
106
- this.lastEmittedAnalysisTime = 0
107
- this.streamUuid = null // Initialize UUID on first recording start
108
- this.audioWorkletUrl = audioWorkletUrl
109
- this.featuresExtratorUrl = featuresExtratorUrl
110
- this.maxBufferSize = maxBufferSize
111
- }
112
-
113
- // Utility to handle user media stream
114
- async getMediaStream() {
115
- try {
116
- this.logger?.debug('Requesting user media (microphone)...')
117
-
118
- // First check if the browser supports the necessary audio APIs
119
- if (!navigator?.mediaDevices?.getUserMedia) {
120
- this.logger?.error(
121
- 'Browser does not support mediaDevices.getUserMedia'
122
- )
123
- throw new Error('Browser does not support audio recording')
124
- }
125
-
126
- // Get media with detailed audio constraints for better diagnostics
127
- const constraints = {
128
- audio: {
129
- echoCancellation: true,
130
- noiseSuppression: true,
131
- autoGainControl: true,
132
- // Add deviceId constraint if specified
133
- ...(this.recordingConfig?.deviceId
134
- ? {
135
- deviceId: {
136
- exact: this.recordingConfig.deviceId,
137
- },
138
- }
139
- : {}),
140
- },
141
- }
142
-
143
- this.logger?.debug('Media constraints:', constraints)
144
-
145
- const stream =
146
- await navigator.mediaDevices.getUserMedia(constraints)
147
-
148
- // Get detailed info about the audio track for debugging
149
- const audioTracks = stream.getAudioTracks()
150
- if (audioTracks.length > 0) {
151
- const track = audioTracks[0]
152
- const settings = track.getSettings()
153
- this.logger?.debug('Audio track obtained:', {
154
- label: track.label,
155
- id: track.id,
156
- enabled: track.enabled,
157
- muted: track.muted,
158
- readyState: track.readyState,
159
- settings,
160
- })
161
- } else {
162
- this.logger?.warn('Stream has no audio tracks!')
163
- }
164
-
165
- return stream
166
- } catch (error) {
167
- this.logger?.error('Failed to get media stream:', error)
168
- throw error
169
- }
170
- }
171
-
172
- // Prepare recording with options
173
- async prepareRecording(
174
- recordingConfig: RecordingConfig = {}
175
- ): Promise<boolean> {
176
- if (this.isRecording) {
177
- this.logger?.warn(
178
- 'Cannot prepare: Recording is already in progress'
179
- )
180
- return false
181
- }
182
-
183
- try {
184
- // Check permissions and initialize basic settings
185
- await this.getMediaStream().then((stream) => {
186
- // Just verify we can access the microphone by getting a stream, then release it
187
- stream.getTracks().forEach((track) => track.stop())
188
- })
189
-
190
- this.bitDepth = encodingToBitDepth({
191
- encoding: recordingConfig.encoding ?? 'pcm_32bit',
192
- })
193
-
194
- // Store recording configuration for later use
195
- this.recordingConfig = recordingConfig
196
-
197
- // Use custom filename if provided, otherwise fallback to timestamp
198
- if (recordingConfig.filename) {
199
- // Remove any existing extension from the filename
200
- this.streamUuid = recordingConfig.filename.replace(
201
- /\.[^/.]+$/,
202
- ''
203
- )
204
- } else {
205
- this.streamUuid = Date.now().toString()
206
- }
207
-
208
- this.logger?.debug('Recording preparation completed successfully')
209
- return true
210
- } catch (error) {
211
- this.logger?.error('Error preparing recording:', error)
212
- return false
213
- }
214
- }
215
-
216
- // Start recording with options
217
- async startRecording(
218
- recordingConfig: RecordingConfig = {}
219
- ): Promise<StartRecordingResult> {
220
- if (this.isRecording) {
221
- throw new Error('Recording is already in progress')
222
- }
223
-
224
- // If we haven't prepared or have different settings, prepare now
225
- if (
226
- !this.recordingConfig ||
227
- this.recordingConfig.sampleRate !== recordingConfig.sampleRate ||
228
- this.recordingConfig.channels !== recordingConfig.channels ||
229
- this.recordingConfig.encoding !== recordingConfig.encoding
230
- ) {
231
- await this.prepareRecording(recordingConfig)
232
- } else {
233
- this.logger?.debug(
234
- 'Using previously prepared recording configuration'
235
- )
236
- }
237
-
238
- // Save recording config for reference
239
- this.recordingConfig = recordingConfig
240
-
241
- const audioContext = new (window.AudioContext ||
242
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
243
- // @ts-ignore - Allow webkitAudioContext for Safari
244
- window.webkitAudioContext)()
245
- const stream = await this.getMediaStream()
246
-
247
- const source = audioContext.createMediaStreamSource(stream)
248
-
249
- this.customRecorder = new WebRecorder({
250
- logger: this.logger,
251
- audioContext,
252
- source,
253
- recordingConfig,
254
- emitAudioEventCallback: this.customRecorderEventCallback.bind(this),
255
- emitAudioAnalysisCallback:
256
- this.customRecorderAnalysisCallback.bind(this),
257
- onInterruption: this.handleRecordingInterruption.bind(this),
258
- })
259
- await this.customRecorder.init()
260
- this.customRecorder.start()
261
-
262
- this.isRecording = true
263
- this.recordingStartTime = Date.now()
264
- this.pausedTime = 0
265
- this.isPaused = false
266
- this.lastEmittedSize = 0
267
- this.lastEmittedTime = 0
268
- this.lastEmittedCompressionSize = 0
269
- this.currentInterval = recordingConfig.interval ?? 1000
270
- this.currentIntervalAnalysis = recordingConfig.intervalAnalysis ?? 500
271
- this.lastEmittedAnalysisTime = Date.now()
272
-
273
- // Use custom filename if provided, otherwise fallback to timestamp
274
- if (recordingConfig.filename) {
275
- // Remove any existing extension from the filename
276
- this.streamUuid = recordingConfig.filename.replace(/\.[^/.]+$/, '')
277
- } else {
278
- this.streamUuid = Date.now().toString()
279
- }
280
-
281
- const fileUri = `${this.streamUuid}.${this.extension}`
282
- const streamConfig: StartRecordingResult = {
283
- fileUri,
284
- mimeType: `audio/${this.extension}`,
285
- bitDepth: this.bitDepth,
286
- channels: recordingConfig.channels ?? 1,
287
- sampleRate: recordingConfig.sampleRate ?? 44100,
288
- compression: recordingConfig.output?.compressed?.enabled
289
- ? {
290
- ...recordingConfig.output.compressed,
291
- bitrate:
292
- recordingConfig.output.compressed.bitrate ?? 128000,
293
- size: 0,
294
- mimeType: 'audio/webm',
295
- format:
296
- recordingConfig.output.compressed.format ?? 'opus',
297
- compressedFileUri: '',
298
- }
299
- : undefined,
300
- }
301
- return streamConfig
302
- }
303
-
304
- /**
305
- * Centralized handler for recording interruptions
306
- */
307
- private handleRecordingInterruption(event: {
308
- reason: RecordingInterruptionReason | string
309
- isPaused: boolean
310
- timestamp: number
311
- message?: string
312
- }): void {
313
- this.logger?.debug(`Received recording interruption: ${event.reason}`)
314
-
315
- // Update local state if the interruption should pause recording
316
- if (event.isPaused) {
317
- this.isPaused = true
318
-
319
- // If this is a device disconnection, handle according to behavior setting
320
- if (event.reason === 'deviceDisconnected') {
321
- this.pausedTime = Date.now()
322
-
323
- // Check if we should try fallback to another device
324
- if (
325
- this.recordingConfig?.deviceDisconnectionBehavior ===
326
- 'fallback'
327
- ) {
328
- this.logger?.debug(
329
- 'Device disconnected with fallback behavior - attempting to switch to default device'
330
- )
331
-
332
- // Try to restart with default device
333
- this.handleDeviceFallback().catch((error) => {
334
- // If fallback fails, emit warning
335
- this.logger?.error('Device fallback failed:', error)
336
- this.emit('onRecordingInterrupted', {
337
- reason: 'deviceSwitchFailed',
338
- isPaused: true,
339
- timestamp: Date.now(),
340
- message:
341
- 'Failed to switch to fallback device. Recording paused.',
342
- })
343
- })
344
- } else {
345
- // Just warn about disconnection if fallback not enabled
346
- this.logger?.warn(
347
- 'Device disconnected - recording paused automatically'
348
- )
349
- this.emit('onRecordingInterrupted', event)
350
- }
351
- } else {
352
- // For other interruption types, just emit the event
353
- this.emit('onRecordingInterrupted', event)
354
- }
355
- } else {
356
- // If not causing a pause, just forward the event
357
- this.emit('onRecordingInterrupted', event)
358
- }
359
- }
360
-
361
- /**
362
- * Handler for audio events from the WebRecorder
363
- */
364
- private customRecorderEventCallback({
365
- data,
366
- position,
367
- compression,
368
- }: EmitAudioEventProps): void {
369
- // Keep only the latest chunks based on maxBufferSize
370
- this.audioChunks.push(new Float32Array(data))
371
- if (this.audioChunks.length > this.maxBufferSize) {
372
- this.audioChunks.shift() // Remove oldest chunk
373
- }
374
- this.currentSize += data.byteLength
375
- this.emitAudioEvent({ data, position, compression })
376
- this.lastEmittedTime = Date.now()
377
- this.lastEmittedSize = this.currentSize
378
- this.lastEmittedCompressionSize = compression?.size ?? 0
379
- }
380
-
381
- /**
382
- * Handler for audio analysis events from the WebRecorder
383
- */
384
- private customRecorderAnalysisCallback(
385
- audioAnalysisData: AudioAnalysis
386
- ): void {
387
- this.emit('AudioAnalysis', audioAnalysisData)
388
- }
389
-
390
- // Get recording duration
391
- private getRecordingDuration(): number {
392
- if (!this.isRecording) {
393
- return 0
394
- }
395
-
396
- return this.currentDurationMs
397
- }
398
-
399
- emitAudioEvent({ data, position, compression }: EmitAudioEventProps) {
400
- const fileUri = `${this.streamUuid}.${this.extension}`
401
- if (compression?.size) {
402
- this.lastEmittedCompressionSize = compression.size
403
- this.totalCompressedSize = compression.totalSize
404
- }
405
-
406
- // Update latest position for tracking
407
- this.latestPosition = position
408
-
409
- // Calculate duration of this chunk in ms
410
- const sampleRate = this.recordingConfig?.sampleRate || 44100
411
- const chunkDurationMs = (data.length / sampleRate) * 1000
412
-
413
- // Handle duration calculation
414
- if (this.customRecorder?.isFirstChunkAfterSwitch) {
415
- this.logger?.debug(
416
- `Processing first chunk after device switch, duration preserved at ${this.currentDurationMs}ms`
417
- )
418
- this.customRecorder.isFirstChunkAfterSwitch = false
419
- } else {
420
- this.currentDurationMs += chunkDurationMs
421
- }
422
-
423
- const audioEventPayload: AudioEventPayload = {
424
- fileUri,
425
- mimeType: `audio/${this.extension}`,
426
- lastEmittedSize: this.lastEmittedSize,
427
- deltaSize: data.byteLength,
428
- position,
429
- totalSize: this.currentSize,
430
- buffer: data,
431
- streamUuid: this.streamUuid ?? '',
432
- compression: compression
433
- ? {
434
- data: compression?.data,
435
- totalSize: this.totalCompressedSize,
436
- eventDataSize: compression?.size ?? 0,
437
- position,
438
- }
439
- : undefined,
440
- }
441
-
442
- this.emit('AudioData', audioEventPayload)
443
- }
444
-
445
- // Stop recording
446
- async stopRecording(): Promise<AudioRecording> {
447
- if (!this.customRecorder) {
448
- throw new Error('Recorder is not initialized')
449
- }
450
-
451
- this.logger?.debug('Starting stop process')
452
-
453
- try {
454
- const { compressedBlob, uncompressedBlob } =
455
- await this.customRecorder.stop()
456
-
457
- this.isRecording = false
458
- this.isPaused = false
459
-
460
- let compression: AudioRecording['compression']
461
- let fileUri = `${this.streamUuid}.${this.extension}`
462
- let mimeType = `audio/${this.extension}`
463
-
464
- // Handle both compressed and uncompressed blobs according to new output configuration
465
- const primaryEnabled =
466
- this.recordingConfig?.output?.primary?.enabled ?? true
467
- const compressedEnabled =
468
- this.recordingConfig?.output?.compressed?.enabled ?? false
469
-
470
- // Process compressed blob if available and enabled
471
- if (compressedBlob && compressedEnabled) {
472
- const compressedUri = URL.createObjectURL(compressedBlob)
473
- const compressedInfo = {
474
- compressedFileUri: compressedUri,
475
- size: compressedBlob.size,
476
- mimeType: 'audio/webm',
477
- format:
478
- this.recordingConfig?.output?.compressed?.format ??
479
- 'opus',
480
- bitrate:
481
- this.recordingConfig?.output?.compressed?.bitrate ??
482
- 128000,
483
- }
484
-
485
- // Store compression info
486
- compression = compressedInfo
487
-
488
- // If primary is disabled, use compressed as main file
489
- if (!primaryEnabled) {
490
- this.logger?.debug(
491
- 'Using compressed audio as primary output (primary disabled)'
492
- )
493
- fileUri = compressedUri
494
- mimeType = 'audio/webm'
495
- }
496
- }
497
-
498
- // Process uncompressed WAV if available and primary is enabled
499
- if (uncompressedBlob && primaryEnabled) {
500
- const wavUri = URL.createObjectURL(uncompressedBlob)
501
- fileUri = wavUri
502
- mimeType = 'audio/wav'
503
- } else if (!primaryEnabled && !compressedEnabled) {
504
- // No outputs enabled - streaming only mode
505
- this.logger?.debug('No outputs enabled - streaming only mode')
506
- fileUri = ''
507
- mimeType = 'audio/wav'
508
- }
509
-
510
- // Use the stored streamUuid for the final filename
511
- const filename = fileUri
512
- ? `${this.streamUuid}.${this.extension}`
513
- : 'stream-only'
514
- const result: AudioRecording = {
515
- fileUri,
516
- filename,
517
- bitDepth: this.bitDepth,
518
- createdAt: this.recordingStartTime,
519
- channels: this.recordingConfig?.channels ?? 1,
520
- sampleRate: this.recordingConfig?.sampleRate ?? 44100,
521
- durationMs: this.currentDurationMs,
522
- size: primaryEnabled ? this.currentSize : 0,
523
- mimeType,
524
- compression,
525
- }
526
-
527
- // Reset after creating the result
528
- this.streamUuid = null
529
-
530
- // Reset recording state variables to prepare for next recording
531
- this.currentDurationMs = 0
532
- this.currentSize = 0
533
- this.lastEmittedSize = 0
534
- this.totalCompressedSize = 0
535
- this.lastEmittedCompressionSize = 0
536
- this.audioChunks = []
537
-
538
- return result
539
- } catch (error) {
540
- this.logger?.error('Error stopping recording:', error)
541
- throw error
542
- }
543
- }
544
-
545
- // Pause recording
546
- async pauseRecording() {
547
- if (!this.isRecording) {
548
- throw new Error('Recording is not active')
549
- }
550
-
551
- if (this.isPaused) {
552
- this.logger?.debug('Recording already paused, skipping')
553
- return
554
- }
555
-
556
- try {
557
- if (this.customRecorder) {
558
- this.customRecorder.pause()
559
- }
560
- this.isPaused = true
561
- this.pausedTime = Date.now()
562
- } catch (error) {
563
- this.logger?.error('Error in pauseRecording', error)
564
- // Even if the pause operation failed, make sure our state is consistent
565
- this.isPaused = true
566
- this.pausedTime = Date.now()
567
- }
568
- }
569
-
570
- // Resume recording
571
- async resumeRecording() {
572
- if (!this.isPaused) {
573
- throw new Error('Recording is not paused')
574
- }
575
-
576
- this.logger?.debug('Resuming recording', {
577
- deviceDisconnectionBehavior:
578
- this.recordingConfig?.deviceDisconnectionBehavior,
579
- isDeviceDisconnected: this.customRecorder?.isDeviceDisconnected,
580
- })
581
-
582
- try {
583
- // If we have no recorder, or if the device is disconnected, always attempt fallback
584
- if (
585
- !this.customRecorder ||
586
- this.customRecorder.isDeviceDisconnected
587
- ) {
588
- this.logger?.debug(
589
- 'No recorder exists or device disconnected - attempting fallback on resume'
590
- )
591
- await this.handleDeviceFallback()
592
- // handleDeviceFallback will manage resuming if successful, or emit error if failed.
593
- return
594
- }
595
-
596
- // Normal resume path - device is still connected
597
- this.customRecorder.resume()
598
- this.isPaused = false
599
-
600
- // Adjust the recording start time to account for the pause duration
601
- const pauseDuration = Date.now() - this.pausedTime
602
- this.recordingStartTime += pauseDuration
603
- this.pausedTime = 0
604
-
605
- this.emit('onRecordingInterrupted', {
606
- reason: 'userResumed',
607
- isPaused: false,
608
- timestamp: Date.now(),
609
- })
610
- } catch (error) {
611
- this.logger?.error('Resume failed:', error)
612
- // Fallback to emitting a general failure if resume fails unexpectedly
613
- this.emit('onRecordingInterrupted', {
614
- reason: 'resumeFailed', // Use a more specific reason
615
- isPaused: true, // Remain paused if resume fails
616
- timestamp: Date.now(),
617
- message:
618
- 'Failed to resume recording. Please stop and start again.',
619
- })
620
- }
621
- }
622
-
623
- // Get current status
624
- status() {
625
- const durationMs = this.getRecordingDuration()
626
-
627
- const status: AudioStreamStatus = {
628
- isRecording: this.isRecording,
629
- isPaused: this.isPaused,
630
- durationMs,
631
- size: this.currentSize,
632
- interval: this.currentInterval,
633
- intervalAnalysis: this.currentIntervalAnalysis,
634
- mimeType: `audio/${this.extension}`,
635
- compression: this.recordingConfig?.output?.compressed?.enabled
636
- ? {
637
- size: this.totalCompressedSize,
638
- mimeType: 'audio/webm',
639
- format:
640
- this.recordingConfig.output.compressed.format ??
641
- 'opus',
642
- bitrate:
643
- this.recordingConfig.output.compressed.bitrate ??
644
- 128000,
645
- compressedFileUri: `${this.streamUuid}.webm`,
646
- }
647
- : undefined,
648
- }
649
- return status
650
- }
651
-
652
- /**
653
- * Handles device fallback when the current device is disconnected
654
- */
655
- private async handleDeviceFallback(): Promise<boolean> {
656
- this.logger?.debug('Starting device fallback procedure')
657
-
658
- if (!this.isRecording) {
659
- return false
660
- }
661
-
662
- try {
663
- // Save important state before switching
664
- const currentPosition = this.latestPosition
665
- const existingAudioChunks = [...this.audioChunks]
666
-
667
- // Save compressed chunks if available
668
- let compressedChunks: Blob[] = []
669
- if (this.customRecorder) {
670
- try {
671
- compressedChunks = this.customRecorder.getCompressedChunks()
672
- } catch (err) {
673
- this.logger?.warn('Failed to get compressed chunks:', err)
674
- }
675
- }
676
-
677
- // Save the current counter value for continuity
678
- let currentDataPointCounter = 0
679
- if (this.customRecorder) {
680
- currentDataPointCounter =
681
- this.customRecorder.getDataPointCounter()
682
- }
683
-
684
- // Clean up existing recorder
685
- if (this.customRecorder) {
686
- try {
687
- this.customRecorder.cleanup()
688
- } catch (cleanupError) {
689
- this.logger?.warn('Error during cleanup:', cleanupError)
690
- }
691
- }
692
-
693
- // Keep recording state true but mark as paused
694
- this.isPaused = true
695
- this.pausedTime = Date.now()
696
-
697
- // Store current size and other stats
698
- const previousTotalSize = this.currentSize
699
- const previousLastEmittedSize = this.lastEmittedSize
700
- const previousCompressedSize = this.totalCompressedSize
701
-
702
- // Try to get a fallback device
703
- const fallbackDeviceInfo = await this.getFallbackDevice()
704
- if (!fallbackDeviceInfo) {
705
- this.emit('onRecordingInterrupted', {
706
- reason: 'deviceSwitchFailed',
707
- isPaused: true,
708
- timestamp: Date.now(),
709
- message:
710
- 'Failed to switch to fallback device. Recording paused.',
711
- })
712
- return false
713
- }
714
-
715
- // Start recording with the new device
716
- try {
717
- const stream = await this.requestPermissionsAndGetUserMedia(
718
- fallbackDeviceInfo.deviceId
719
- )
720
- const audioContext = new (window.AudioContext ||
721
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
722
- // @ts-ignore - Allow webkitAudioContext for Safari
723
- window.webkitAudioContext)()
724
-
725
- const source = audioContext.createMediaStreamSource(stream)
726
-
727
- // Create a new recorder with the fallback device
728
- this.customRecorder = new WebRecorder({
729
- logger: this.logger,
730
- audioContext,
731
- source,
732
- recordingConfig: this.recordingConfig || {},
733
- emitAudioEventCallback:
734
- this.customRecorderEventCallback.bind(this),
735
- emitAudioAnalysisCallback:
736
- this.customRecorderAnalysisCallback.bind(this),
737
- onInterruption: this.handleRecordingInterruption.bind(this),
738
- })
739
-
740
- await this.customRecorder.init()
741
-
742
- // Set the initial position to continue from the previous device
743
- this.customRecorder.setPosition(currentPosition)
744
-
745
- // Reset the data point counter to continue from where the previous device left off
746
- if (currentDataPointCounter > 0) {
747
- this.customRecorder.resetDataPointCounter(
748
- currentDataPointCounter
749
- )
750
- }
751
-
752
- // Prepare the recorder to handle the device switch properly
753
- this.customRecorder.prepareForDeviceSwitch()
754
-
755
- // Restore the existing audio chunks
756
- if (existingAudioChunks.length > 0) {
757
- this.audioChunks = existingAudioChunks
758
- }
759
-
760
- // Restore compressed chunks if available
761
- if (compressedChunks.length > 0) {
762
- this.customRecorder.setCompressedChunks(compressedChunks)
763
- }
764
-
765
- // Start the new recorder while preserving counters
766
- this.customRecorder.start(true)
767
-
768
- // Update recording state
769
- this.isPaused = false
770
- this.recordingStartTime = Date.now()
771
-
772
- // Restore size counters to maintain continuity
773
- this.currentSize = previousTotalSize
774
- this.lastEmittedSize = previousLastEmittedSize
775
- this.totalCompressedSize = previousCompressedSize
776
-
777
- // Notify that we switched to a fallback device
778
- if (this.eventCallback) {
779
- this.eventCallback({
780
- type: 'deviceFallback',
781
- device: fallbackDeviceInfo.deviceId,
782
- timestamp: new Date(),
783
- })
784
- }
785
- return true
786
- } catch (error) {
787
- this.logger?.error(
788
- 'Failed to start recording with fallback device',
789
- error
790
- )
791
- this.isPaused = true
792
- this.emit('onRecordingInterrupted', {
793
- reason: 'deviceSwitchFailed',
794
- isPaused: true,
795
- timestamp: Date.now(),
796
- message:
797
- 'Failed to switch to fallback device. Recording paused.',
798
- })
799
- return false
800
- }
801
- } catch (error) {
802
- this.logger?.error('Failed to use fallback device', error)
803
- this.isPaused = true
804
- this.emit('onRecordingInterrupted', {
805
- reason: 'deviceSwitchFailed',
806
- isPaused: true,
807
- timestamp: Date.now(),
808
- message:
809
- 'Failed to switch to fallback device. Recording paused.',
810
- })
811
- return false
812
- }
813
- }
814
-
815
- /**
816
- * Attempts to get a fallback audio device
817
- */
818
- private async getFallbackDevice(): Promise<MediaDeviceInfo | null> {
819
- try {
820
- // Get list of available audio input devices
821
- const devices = await navigator.mediaDevices.enumerateDevices()
822
- const audioInputDevices = devices.filter(
823
- (device) => device.kind === 'audioinput'
824
- )
825
-
826
- if (audioInputDevices.length === 0) {
827
- return null
828
- }
829
-
830
- // Try to find a device that's not the current one
831
- if (this.customRecorder) {
832
- try {
833
- // Use mediaDevices.enumerateDevices to find the current active device
834
- const tracks = navigator.mediaDevices
835
- .getUserMedia({ audio: true })
836
- .then((stream) => {
837
- const track = stream.getAudioTracks()[0]
838
- return track ? track.label : ''
839
- })
840
- .catch(() => '')
841
-
842
- const currentTrackLabel = await tracks
843
-
844
- if (currentTrackLabel) {
845
- // Find a device with a different label
846
- const differentDevice = audioInputDevices.find(
847
- (device) =>
848
- device.label &&
849
- device.label !== currentTrackLabel
850
- )
851
-
852
- if (differentDevice) {
853
- return differentDevice
854
- }
855
- }
856
- } catch (err) {
857
- this.logger?.warn(
858
- 'Error determining current device, using default',
859
- err
860
- )
861
- }
862
- }
863
-
864
- // Return the first available device (default device)
865
- return audioInputDevices[0]
866
- } catch (error) {
867
- this.logger?.error('Error finding fallback device:', error)
868
- return null
869
- }
870
- }
871
-
872
- /**
873
- * Gets user media with specific device ID
874
- */
875
- private async requestPermissionsAndGetUserMedia(
876
- deviceId: string
877
- ): Promise<MediaStream> {
878
- try {
879
- // Request the specific device
880
- return await navigator.mediaDevices.getUserMedia({
881
- audio: {
882
- deviceId: { exact: deviceId },
883
- },
884
- })
885
- } catch (error) {
886
- this.logger?.error(
887
- `Failed to get media for device ${deviceId}`,
888
- error
889
- )
890
- // Try with default constraints as fallback
891
- return await navigator.mediaDevices.getUserMedia({ audio: true })
892
- }
893
- }
894
-
895
- init(options?: ExpoAudioStreamOptions): Promise<void> {
896
- try {
897
- this.logger = options?.logger
898
- this.eventCallback = options?.eventCallback
899
- return Promise.resolve()
900
- } catch (error) {
901
- this.logger?.error('Error initializing ExpoAudioStream', error)
902
- return Promise.reject(error)
903
- }
904
- }
905
- }