@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,1113 +0,0 @@
1
- // packages/expo-audio-stream/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt
2
- package net.siteed.audiostream
3
-
4
- import android.Manifest
5
- import android.os.Build
6
- import android.os.Bundle
7
- import android.util.Log
8
- import android.content.pm.PackageManager
9
- import androidx.annotation.RequiresApi
10
- import androidx.core.content.ContextCompat
11
- import androidx.core.os.bundleOf
12
- import expo.modules.kotlin.Promise
13
- import expo.modules.kotlin.modules.Module
14
- import expo.modules.kotlin.modules.ModuleDefinition
15
- import expo.modules.interfaces.permissions.Permissions
16
- import java.util.zip.CRC32
17
- import kotlinx.coroutines.CoroutineScope
18
- import kotlinx.coroutines.Dispatchers
19
- import kotlinx.coroutines.launch
20
- import kotlinx.coroutines.withContext
21
- import net.siteed.audiostream.LogUtils
22
-
23
- class ExpoAudioStreamModule : Module(), EventSender {
24
- companion object {
25
- private const val CLASS_NAME = "ExpoAudioStreamModule"
26
- }
27
-
28
- private lateinit var audioRecorderManager: AudioRecorderManager
29
- private lateinit var audioProcessor: AudioProcessor
30
- private lateinit var audioDeviceManager: AudioDeviceManager
31
- private var enablePhoneStateHandling: Boolean = false // Default to false until we check manifest
32
- private var enableNotificationHandling: Boolean = false // Default to false until we check manifest
33
- private var enableBackgroundAudio: Boolean = false // Default to false until we check manifest
34
- private var enableDeviceDetection: Boolean = false // Default to false until we check manifest
35
- private val coroutineScope = CoroutineScope(Dispatchers.Main)
36
-
37
- private val audioFileHandler by lazy {
38
- AudioFileHandler(appContext.reactContext?.filesDir ?: throw IllegalStateException("React context not available"))
39
- }
40
-
41
- private val audioTrimmer by lazy {
42
- AudioTrimmer(
43
- appContext.reactContext ?: throw IllegalStateException("React context not available"),
44
- audioFileHandler
45
- )
46
- }
47
-
48
- @RequiresApi(Build.VERSION_CODES.R)
49
- override fun definition() = ModuleDefinition {
50
- // The module will be accessible from `requireNativeModule('ExpoAudioStream')` in JavaScript.
51
- Name("ExpoAudioStream")
52
-
53
- // Check permissions declared in the manifest
54
- try {
55
- val context = appContext.reactContext ?: throw IllegalStateException("React context not available")
56
- val packageInfo = context.packageManager.getPackageInfo(
57
- context.packageName,
58
- PackageManager.GET_PERMISSIONS
59
- )
60
-
61
- // Check if READ_PHONE_STATE is in the requested permissions
62
- enablePhoneStateHandling = packageInfo.requestedPermissions?.contains(Manifest.permission.READ_PHONE_STATE) ?: false
63
-
64
- // Check if POST_NOTIFICATIONS is in the requested permissions
65
- enableNotificationHandling = packageInfo.requestedPermissions?.contains(Manifest.permission.POST_NOTIFICATIONS) ?: false
66
-
67
- // Check if background audio is enabled by looking for FOREGROUND_SERVICE_MICROPHONE permission
68
- enableBackgroundAudio = packageInfo.requestedPermissions?.contains(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE) ?: false
69
-
70
- // Check if device detection is enabled by looking for BLUETOOTH_CONNECT permission
71
- enableDeviceDetection = packageInfo.requestedPermissions?.contains(Manifest.permission.BLUETOOTH_CONNECT) ?: false
72
-
73
- LogUtils.d(CLASS_NAME, "Phone state handling ${if (enablePhoneStateHandling) "enabled" else "disabled"} based on manifest permissions")
74
- LogUtils.d(CLASS_NAME, "Notification handling ${if (enableNotificationHandling) "enabled" else "disabled"} based on manifest permissions")
75
- LogUtils.d(CLASS_NAME, "Background audio handling ${if (enableBackgroundAudio) "enabled" else "disabled"} based on manifest permissions")
76
- LogUtils.d(CLASS_NAME, "Device detection ${if (enableDeviceDetection) "enabled" else "disabled"} based on manifest permissions")
77
- } catch (e: Exception) {
78
- LogUtils.e(CLASS_NAME, "Failed to check manifest permissions: ${e.message}", e)
79
- enablePhoneStateHandling = false
80
- enableNotificationHandling = false
81
- enableBackgroundAudio = false
82
- enableDeviceDetection = false
83
- }
84
-
85
- Events(
86
- Constants.AUDIO_EVENT_NAME,
87
- Constants.AUDIO_ANALYSIS_EVENT_NAME,
88
- Constants.RECORDING_INTERRUPTED_EVENT_NAME,
89
- Constants.TRIM_PROGRESS_EVENT,
90
- Constants.DEVICE_CHANGED_EVENT // Add device changed event name
91
- )
92
-
93
- // Initialize Managers
94
- initializeManager()
95
-
96
- // Add a convenience function to check for foreground service permission separately
97
- fun isForegroundServiceMicRequired(): Boolean {
98
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && enableBackgroundAudio
99
- }
100
-
101
- // Helper function to check if device detection is enabled
102
- fun isDeviceDetectionEnabled(): Boolean {
103
- return enableDeviceDetection
104
- }
105
-
106
- // Add device-related functions to the module
107
-
108
- // Gets available audio input devices with an optional refresh parameter
109
- AsyncFunction("getAvailableInputDevices") { options: Map<String, Any>?, promise: Promise ->
110
- try {
111
- LogUtils.d(CLASS_NAME, "getAvailableInputDevices called. Refresh: ${options?.get("refresh") ?: false}")
112
-
113
- // Check if refresh is requested
114
- if (options?.get("refresh") as? Boolean == true) {
115
- audioDeviceManager.forceRefreshAudioDevices()
116
- }
117
-
118
- // Get the list of devices
119
- audioDeviceManager.getAvailableInputDevices(promise)
120
- } catch (e: Exception) {
121
- LogUtils.e(CLASS_NAME, "Error getting available input devices: ${e.message}", e)
122
- promise.reject("DEVICE_ERROR", "Failed to get available audio devices: ${e.message}", e)
123
- }
124
- }
125
-
126
- // Gets the currently selected audio input device
127
- AsyncFunction("getCurrentInputDevice") { promise: Promise ->
128
- try {
129
- LogUtils.d(CLASS_NAME, "getCurrentInputDevice called")
130
- audioDeviceManager.getCurrentInputDevice(promise)
131
- } catch (e: Exception) {
132
- LogUtils.e(CLASS_NAME, "Error getting current input device: ${e.message}", e)
133
- promise.reject("DEVICE_ERROR", "Failed to get current audio device: ${e.message}", e)
134
- }
135
- }
136
-
137
- // Selects a specific audio input device for recording
138
- AsyncFunction("selectInputDevice") { deviceId: String, promise: Promise ->
139
- try {
140
- LogUtils.d(CLASS_NAME, "selectInputDevice called with ID: $deviceId")
141
- audioDeviceManager.selectInputDevice(deviceId, promise)
142
-
143
- // Update recording if in progress
144
- if (audioRecorderManager.isRecording || audioRecorderManager.isPrepared) {
145
- LogUtils.d(CLASS_NAME, "selectInputDevice: Notifying recorder of device change")
146
- audioRecorderManager.handleDeviceChange()
147
- }
148
- } catch (e: Exception) {
149
- LogUtils.e(CLASS_NAME, "Error selecting input device: ${e.message}", e)
150
- promise.reject("DEVICE_ERROR", "Failed to select audio device: ${e.message}", e)
151
- }
152
- }
153
-
154
- // Resets to the default audio input device
155
- AsyncFunction("resetToDefaultDevice") { promise: Promise ->
156
- try {
157
- LogUtils.d(CLASS_NAME, "resetToDefaultDevice called")
158
- audioDeviceManager.resetToDefaultDevice { success, error ->
159
- if (success) {
160
- // Update recording if in progress
161
- if (audioRecorderManager.isRecording || audioRecorderManager.isPrepared) {
162
- LogUtils.d(CLASS_NAME, "resetToDefaultDevice: Notifying recorder of device change")
163
- audioRecorderManager.handleDeviceChange()
164
- }
165
- promise.resolve(true)
166
- } else {
167
- LogUtils.e(CLASS_NAME, "Failed to reset to default device: ${error?.message}")
168
- promise.reject("DEVICE_ERROR", "Failed to reset to default device: ${error?.message}", error)
169
- }
170
- }
171
- } catch (e: Exception) {
172
- LogUtils.e(CLASS_NAME, "Error resetting to default device: ${e.message}", e)
173
- promise.reject("DEVICE_ERROR", "Failed to reset to default device: ${e.message}", e)
174
- }
175
- }
176
-
177
- // Refreshes the audio devices list
178
- Function("refreshAudioDevices") {
179
- LogUtils.d(CLASS_NAME, "refreshAudioDevices called")
180
- val success = audioDeviceManager.forceRefreshAudioDevices()
181
- return@Function mapOf("success" to success)
182
- }
183
-
184
-
185
-
186
- AsyncFunction("prepareRecording") { options: Map<String, Any?>, promise: Promise ->
187
- try {
188
- // If notifications are requested but permission not in manifest, modify options
189
- if (options["showNotification"] as? Boolean == true && !enableNotificationHandling) {
190
- val modifiedOptions = options.toMutableMap()
191
- modifiedOptions["showNotification"] = false
192
- LogUtils.d(CLASS_NAME, "Notification permission not in manifest, disabling showNotification")
193
-
194
- if (audioRecorderManager.prepareRecording(modifiedOptions)) {
195
- promise.resolve(true)
196
- } else {
197
- promise.reject("PREPARE_ERROR", "Failed to prepare recording", null)
198
- }
199
- } else {
200
- if (audioRecorderManager.prepareRecording(options)) {
201
- promise.resolve(true)
202
- } else {
203
- promise.reject("PREPARE_ERROR", "Failed to prepare recording", null)
204
- }
205
- }
206
- } catch (e: Exception) {
207
- LogUtils.e(CLASS_NAME, "Error preparing recording", e)
208
- promise.reject("PREPARE_ERROR", "Failed to prepare recording: ${e.message}", e)
209
- }
210
- }
211
-
212
- AsyncFunction("startRecording") { options: Map<String, Any?>, promise: Promise ->
213
- // If notifications are requested but permission not in manifest, modify options
214
- if (options["showNotification"] as? Boolean == true && !enableNotificationHandling) {
215
- val modifiedOptions = options.toMutableMap()
216
- modifiedOptions["showNotification"] = false
217
- LogUtils.d(CLASS_NAME, "Notification permission not in manifest, disabling showNotification")
218
- audioRecorderManager.startRecording(modifiedOptions, promise)
219
- } else {
220
- audioRecorderManager.startRecording(options, promise)
221
- }
222
- }
223
-
224
- Function("clearAudioFiles") {
225
- audioRecorderManager.clearAudioStorage()
226
- }
227
-
228
- Function("status") {
229
- return@Function audioRecorderManager.getStatus()
230
- }
231
-
232
- AsyncFunction("listAudioFiles") { promise: Promise ->
233
- audioRecorderManager.listAudioFiles(promise)
234
- }
235
-
236
- AsyncFunction("pauseRecording") { promise: Promise ->
237
- audioRecorderManager.pauseRecording(promise)
238
- }
239
-
240
- AsyncFunction("resumeRecording") { promise: Promise ->
241
- LogUtils.d(CLASS_NAME, "⏺️ resumeRecording() called from JS layer")
242
- try {
243
- audioRecorderManager.resumeRecording(object : Promise {
244
- override fun resolve(value: Any?) {
245
- LogUtils.d(CLASS_NAME, "⏺️ resumeRecording completed successfully")
246
- promise.resolve(value)
247
- }
248
- override fun reject(code: String, message: String?, cause: Throwable?) {
249
- LogUtils.e(CLASS_NAME, "⏺️ resumeRecording failed: $code - $message", cause)
250
- promise.reject(code, message, cause)
251
- }
252
- })
253
- } catch (e: Exception) {
254
- LogUtils.e(CLASS_NAME, "⏺️ Exception when calling resumeRecording: ${e.message}", e)
255
- promise.reject("RESUME_ERROR", "Failed to resume recording: ${e.message}", e)
256
- }
257
- }
258
-
259
- AsyncFunction("stopRecording") { promise: Promise ->
260
- audioRecorderManager.stopRecording(promise)
261
- }
262
-
263
- AsyncFunction("requestPermissionsAsync") { promise: Promise ->
264
- try {
265
- val permissions = mutableListOf(
266
- Manifest.permission.RECORD_AUDIO
267
- )
268
-
269
- // Only add phone state permission if enabled
270
- if (enablePhoneStateHandling) {
271
- permissions.add(Manifest.permission.READ_PHONE_STATE)
272
- }
273
-
274
- // Add foreground service permission for Android 14+ only if background audio is enabled
275
- if (isForegroundServiceMicRequired()) {
276
- LogUtils.d(CLASS_NAME, "Adding FOREGROUND_SERVICE_MICROPHONE permission request")
277
- permissions.add(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE)
278
- }
279
-
280
- // Add device detection permissions if device detection is enabled
281
- if (isDeviceDetectionEnabled()) {
282
- // BLUETOOTH_CONNECT is needed on Android 12+ to access device names/addresses
283
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
284
- permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
285
- LogUtils.d(CLASS_NAME, "Adding BLUETOOTH_CONNECT permission request for device detection")
286
- }
287
- }
288
-
289
- LogUtils.d(CLASS_NAME, "Requesting permissions: $permissions")
290
- Permissions.askForPermissionsWithPermissionsManager(
291
- appContext.permissions,
292
- promise,
293
- *permissions.toTypedArray()
294
- )
295
- } catch (e: Exception) {
296
- LogUtils.e(CLASS_NAME, "Error requesting permissions", e)
297
- promise.reject("PERMISSION_ERROR", "Failed to request permissions: ${e.message}", e)
298
- }
299
- }
300
-
301
- AsyncFunction("getPermissionsAsync") { promise: Promise ->
302
- val permissions = mutableListOf(
303
- Manifest.permission.RECORD_AUDIO
304
- )
305
-
306
- // Only add phone state permission if enabled
307
- if (enablePhoneStateHandling) {
308
- permissions.add(Manifest.permission.READ_PHONE_STATE)
309
- }
310
-
311
- // Only check foreground service permission when background audio is enabled
312
- if (isForegroundServiceMicRequired()) {
313
- permissions.add(Manifest.permission.FOREGROUND_SERVICE_MICROPHONE)
314
- }
315
-
316
- // Add device detection permissions if enabled
317
- if (isDeviceDetectionEnabled()) {
318
- // BLUETOOTH_CONNECT is needed on Android 12+ to access device names/addresses
319
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
320
- permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
321
- }
322
- }
323
-
324
- Permissions.getPermissionsWithPermissionsManager(
325
- appContext.permissions,
326
- promise,
327
- *permissions.toTypedArray()
328
- )
329
- }
330
-
331
- AsyncFunction("requestNotificationPermissionsAsync") { promise: Promise ->
332
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && enableNotificationHandling) {
333
- // Only request notification permissions if enabled in manifest
334
- Permissions.askForPermissionsWithPermissionsManager(
335
- appContext.permissions,
336
- promise,
337
- Manifest.permission.POST_NOTIFICATIONS
338
- )
339
- } else {
340
- // Either notifications not required or running on Android < 13
341
- promise.resolve(
342
- bundleOf(
343
- "status" to "granted",
344
- "expires" to "never",
345
- "granted" to true
346
- )
347
- )
348
- }
349
- }
350
-
351
- AsyncFunction("getNotificationPermissionsAsync") { promise: Promise ->
352
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && enableNotificationHandling) {
353
- // Only check notification permissions if enabled in manifest
354
- Permissions.getPermissionsWithPermissionsManager(
355
- appContext.permissions,
356
- promise,
357
- Manifest.permission.POST_NOTIFICATIONS
358
- )
359
- } else {
360
- // Either notifications not required or running on Android < 13
361
- promise.resolve(
362
- bundleOf(
363
- "status" to "granted",
364
- "expires" to "never",
365
- "granted" to true
366
- )
367
- )
368
- }
369
- }
370
-
371
- AsyncFunction("trimAudio") { options: Map<String, Any>, promise: Promise ->
372
- try {
373
- val fileUri = options["fileUri"] as? String ?: run {
374
- promise.reject("INVALID_URI", "fileUri is required", null)
375
- return@AsyncFunction
376
- }
377
-
378
- LogUtils.d(CLASS_NAME, "trimAudio called with fileUri: $fileUri")
379
- LogUtils.d(CLASS_NAME, "Full options: $options")
380
-
381
- val mode = options["mode"] as? String ?: "single"
382
- val startTimeMs = (options["startTimeMs"] as? Number)?.toLong()
383
- val endTimeMs = (options["endTimeMs"] as? Number)?.toLong()
384
-
385
- @Suppress("UNCHECKED_CAST")
386
- val ranges = options["ranges"] as? List<Map<String, Long>>
387
-
388
- val outputFileName = options["outputFileName"] as? String
389
-
390
- @Suppress("UNCHECKED_CAST")
391
- var outputFormatMap = options["outputFormat"] as? Map<String, Any>
392
-
393
- // Validate output format if provided
394
- if (outputFormatMap != null) {
395
- val format = outputFormatMap["format"] as? String
396
- if (format != null && format != "wav" && format != "aac" && format != "opus") {
397
- LogUtils.w(CLASS_NAME, "Requested format '$format' is not fully supported. Using 'aac' instead.")
398
- // Create a new map with the corrected format
399
- val newOutputFormat = HashMap<String, Any>(outputFormatMap)
400
- newOutputFormat["format"] = "aac"
401
- outputFormatMap = newOutputFormat
402
- }
403
- }
404
-
405
- LogUtils.d(CLASS_NAME, "Output format options: $outputFormatMap")
406
-
407
- // Create progress listener
408
- val progressListener = object : AudioTrimmer.ProgressListener {
409
- override fun onProgress(progress: Float, bytesProcessed: Long, totalBytes: Long) {
410
- sendEvent(Constants.TRIM_PROGRESS_EVENT, mapOf(
411
- "progress" to progress,
412
- "bytesProcessed" to bytesProcessed,
413
- "totalBytes" to totalBytes
414
- ))
415
- }
416
- }
417
-
418
- // Record start time
419
- val startTime = System.currentTimeMillis()
420
-
421
- // Perform the trim operation
422
- val result = audioTrimmer.trimAudio(
423
- fileUri = fileUri,
424
- mode = mode,
425
- startTimeMs = startTimeMs,
426
- endTimeMs = endTimeMs,
427
- ranges = ranges,
428
- outputFileName = outputFileName,
429
- outputFormat = outputFormatMap,
430
- progressListener = progressListener
431
- )
432
-
433
- // Calculate processing time
434
- val processingTimeMs = System.currentTimeMillis() - startTime
435
-
436
- // Add processing time to result
437
- val resultWithProcessingTime = result.toMutableMap()
438
- resultWithProcessingTime["processingInfo"] = mapOf(
439
- "durationMs" to processingTimeMs
440
- )
441
-
442
- LogUtils.d(CLASS_NAME, "Trim operation completed successfully in ${processingTimeMs}ms: $result")
443
- promise.resolve(resultWithProcessingTime)
444
- } catch (e: Exception) {
445
- LogUtils.e(CLASS_NAME, "Error trimming audio: ${e.message}", e)
446
- promise.reject("TRIM_ERROR", "Error trimming audio: ${e.message}", e)
447
- }
448
- }
449
-
450
- AsyncFunction("extractMelSpectrogram") { options: Map<String, Any>, promise: Promise ->
451
- try {
452
- // Log all incoming options for debugging
453
- LogUtils.d(CLASS_NAME, "extractMelSpectrogram called with options: $options")
454
-
455
- // Extract required parameters with detailed logging
456
- val fileUri = options["fileUri"] as? String
457
- LogUtils.d(CLASS_NAME, "fileUri: $fileUri")
458
- if (fileUri == null) {
459
- LogUtils.e(CLASS_NAME, "Missing required parameter: fileUri")
460
- throw IllegalArgumentException("fileUri is required")
461
- }
462
-
463
- val windowSizeMs = options["windowSizeMs"] as? Double
464
- LogUtils.d(CLASS_NAME, "windowSizeMs: $windowSizeMs")
465
- if (windowSizeMs == null) {
466
- LogUtils.e(CLASS_NAME, "Missing required parameter: windowSizeMs")
467
- throw IllegalArgumentException("windowSizeMs is required")
468
- }
469
-
470
- val hopLengthMs = options["hopLengthMs"] as? Double
471
- LogUtils.d(CLASS_NAME, "hopLengthMs: $hopLengthMs")
472
- if (hopLengthMs == null) {
473
- LogUtils.e(CLASS_NAME, "Missing required parameter: hopLengthMs")
474
- throw IllegalArgumentException("hopLengthMs is required")
475
- }
476
-
477
- // Handle nMels which might come as Double from JavaScript
478
- val nMelsValue = options["nMels"]
479
- LogUtils.d(CLASS_NAME, "Raw nMels value: $nMelsValue (type: ${nMelsValue?.javaClass?.name})")
480
-
481
- val nMels = when (nMelsValue) {
482
- is Int -> nMelsValue
483
- is Double -> nMelsValue.toInt()
484
- is Number -> nMelsValue.toInt()
485
- else -> {
486
- LogUtils.e(CLASS_NAME, "Missing or invalid required parameter: nMels")
487
- throw IllegalArgumentException("nMels is required and must be a number")
488
- }
489
- }
490
-
491
- LogUtils.d(CLASS_NAME, "Converted nMels: $nMels (from ${nMelsValue?.javaClass?.name})")
492
-
493
- // Extract optional parameters with defaults
494
- val fMin = options["fMin"] as? Double ?: 0.0
495
- val fMax = options["fMax"] as? Double
496
- val windowType = options["windowType"] as? String ?: "hann"
497
- val normalize = options["normalize"] as? Boolean ?: false
498
- val logScale = options["logScale"] as? Boolean ?: true
499
-
500
- // Fix the conversion from Number to Long to preserve decimal values
501
- val startTimeMsNumber = options["startTimeMs"] as? Number
502
- val endTimeMsNumber = options["endTimeMs"] as? Number
503
- val startTimeMs = startTimeMsNumber?.toLong() ?: startTimeMsNumber?.toDouble()?.toLong()
504
- val endTimeMs = endTimeMsNumber?.toLong() ?: endTimeMsNumber?.toDouble()?.toLong()
505
-
506
- LogUtils.d(CLASS_NAME, """
507
- Optional parameters:
508
- - fMin: $fMin
509
- - fMax: $fMax
510
- - windowType: $windowType
511
- - normalize: $normalize
512
- - logScale: $logScale
513
- - startTimeMs: $startTimeMs (original: $startTimeMsNumber)
514
- - endTimeMs: $endTimeMs (original: $endTimeMsNumber)
515
- """.trimIndent())
516
-
517
- // Handle decoding options
518
- val decodingOptions = options["decodingOptions"] as? Map<String, Any>
519
- LogUtils.d(CLASS_NAME, "Decoding options: $decodingOptions")
520
-
521
- val config = decodingOptions?.let {
522
- val targetSampleRateValue = it["targetSampleRate"]
523
- val targetSampleRate = when (targetSampleRateValue) {
524
- is Int -> targetSampleRateValue
525
- is Double -> targetSampleRateValue.toInt()
526
- is Number -> targetSampleRateValue.toInt()
527
- else -> null
528
- }
529
-
530
- val targetChannelsValue = it["targetChannels"]
531
- val targetChannels = when (targetChannelsValue) {
532
- is Int -> targetChannelsValue
533
- is Double -> targetChannelsValue.toInt()
534
- is Number -> targetChannelsValue.toInt()
535
- else -> 1
536
- }
537
-
538
- val targetBitDepthValue = it["targetBitDepth"]
539
- val targetBitDepth = when (targetBitDepthValue) {
540
- is Int -> targetBitDepthValue
541
- is Double -> targetBitDepthValue.toInt()
542
- is Number -> targetBitDepthValue.toInt()
543
- else -> 16
544
- }
545
-
546
- val normalizeAudio = it["normalizeAudio"] as? Boolean ?: false
547
-
548
- DecodingConfig(
549
- targetSampleRate = targetSampleRate,
550
- targetChannels = targetChannels,
551
- targetBitDepth = targetBitDepth,
552
- normalizeAudio = normalizeAudio
553
- ).also { config ->
554
- LogUtils.d(CLASS_NAME, """
555
- Using decoding config:
556
- - targetSampleRate: ${config.targetSampleRate ?: "original"}
557
- - targetChannels: ${config.targetChannels ?: "original"}
558
- - targetBitDepth: ${config.targetBitDepth}
559
- - normalizeAudio: ${config.normalizeAudio}
560
- """.trimIndent())
561
- }
562
- } ?: DecodingConfig(targetSampleRate = null, targetChannels = 1, targetBitDepth = 16).also {
563
- LogUtils.d(CLASS_NAME, "Using default decoding config")
564
- }
565
-
566
- // Check if the audio data is too short
567
- if (startTimeMs != null && endTimeMs != null) {
568
- val durationMs = endTimeMs - startTimeMs
569
- LogUtils.d(CLASS_NAME, "Audio duration for spectrogram: $durationMs ms")
570
- if (durationMs < 25) { // 25ms is minimum for a single window
571
- LogUtils.w(CLASS_NAME, "Audio duration is too short for spectrogram analysis: $durationMs ms")
572
- throw IllegalArgumentException("Audio duration must be at least 25ms for spectrogram analysis")
573
- }
574
- }
575
-
576
- // Load audio data with optional time range
577
- LogUtils.d(CLASS_NAME, "Loading audio data...")
578
- val audioData = when {
579
- startTimeMs != null && endTimeMs != null -> {
580
- LogUtils.d(CLASS_NAME, "Loading audio range: $startTimeMs to $endTimeMs ms")
581
- audioProcessor.loadAudioRange(fileUri, startTimeMs, endTimeMs, config)
582
- }
583
- else -> {
584
- LogUtils.d(CLASS_NAME, "Loading entire audio file")
585
- audioProcessor.loadAudioFromAnyFormat(fileUri, config)
586
- }
587
- }
588
-
589
- if (audioData == null) {
590
- LogUtils.e(CLASS_NAME, "Failed to load audio data")
591
- throw IllegalStateException("Failed to load audio data")
592
- }
593
-
594
- LogUtils.d(CLASS_NAME, """
595
- Audio data loaded successfully:
596
- - data size: ${audioData.data.size} bytes
597
- - sampleRate: ${audioData.sampleRate}
598
- - channels: ${audioData.channels}
599
- - bitDepth: ${audioData.bitDepth}
600
- - durationMs: ${audioData.durationMs}
601
- """.trimIndent())
602
-
603
- // Validate that we have enough audio data for processing
604
- if (audioData.data.size == 0 || audioData.durationMs < windowSizeMs) {
605
- LogUtils.e(CLASS_NAME, "Audio data is too short for spectrogram analysis: ${audioData.durationMs}ms, data size: ${audioData.data.size} bytes")
606
- throw IllegalArgumentException(
607
- "Audio data is too short for spectrogram analysis. " +
608
- "Duration: ${audioData.durationMs}ms, minimum required: ${windowSizeMs}ms"
609
- )
610
- }
611
-
612
- // Compute mel-spectrogram
613
- LogUtils.d(CLASS_NAME, "Computing mel-spectrogram...")
614
- val spectrogramData = audioProcessor.extractMelSpectrogram(
615
- audioData = audioData,
616
- windowSizeMs = windowSizeMs.toFloat(),
617
- hopLengthMs = hopLengthMs.toFloat(),
618
- nMels = nMels,
619
- fMin = fMin.toFloat(),
620
- fMax = fMax?.toFloat() ?: (audioData.sampleRate.toFloat() / 2),
621
- normalize = normalize,
622
- logScaling = logScale,
623
- windowType = windowType
624
- )
625
-
626
- LogUtils.d(CLASS_NAME, "Mel-spectrogram computed successfully with ${spectrogramData.spectrogram.size} time steps")
627
-
628
- // Convert to map for React Native
629
- val result = mapOf(
630
- "spectrogram" to spectrogramData.spectrogram.map { it.toList() },
631
- "sampleRate" to audioData.sampleRate,
632
- "nMels" to nMels,
633
- "timeSteps" to spectrogramData.spectrogram.size,
634
- "durationMs" to audioData.durationMs
635
- )
636
-
637
- LogUtils.d(CLASS_NAME, "Returning result with ${result["timeSteps"]} time steps and $nMels mel bands")
638
- promise.resolve(result)
639
- } catch (e: Exception) {
640
- LogUtils.e(CLASS_NAME, "Failed to extract mel-spectrogram: ${e.message}")
641
- LogUtils.e(CLASS_NAME, "Stack trace: ${e.stackTraceToString()}")
642
- promise.reject("SPECTROGRAM_ERROR", e.message ?: "Unknown error", e)
643
- }
644
- }
645
-
646
- OnDestroy {
647
- AudioRecorderManager.destroy()
648
- }
649
-
650
- // Add a new function to check if recording is actually running
651
- AsyncFunction("checkRecordingStatus") { promise: Promise ->
652
- val isServiceRunning = AudioRecordingService.isServiceRunning()
653
-
654
- val status = audioRecorderManager.getStatus()
655
-
656
- // If service is running but isRecording is false, we need to cleanup
657
- if (isServiceRunning && !status.getBoolean("isRecording")) {
658
- audioRecorderManager.cleanup()
659
- AudioRecordingService.stopService(appContext.reactContext!!)
660
- }
661
-
662
- promise.resolve(status)
663
- }
664
-
665
-
666
- AsyncFunction("extractAudioAnalysis") { options: Map<String, Any>, promise: Promise ->
667
- try {
668
- val fileUri = requireNotNull(options["fileUri"] as? String) { "fileUri is required" }
669
-
670
- // Get time or byte range options
671
- val startTimeMs = options["startTimeMs"] as? Number
672
- val endTimeMs = options["endTimeMs"] as? Number
673
- val position = options["position"] as? Number
674
- val length = options["length"] as? Number
675
- val segmentDurationMs = (options["segmentDurationMs"] as? Number)?.toInt() ?: 100
676
-
677
- // Validate ranges - can have time range OR byte range OR no range
678
- val hasTimeRange = startTimeMs != null && endTimeMs != null
679
- val hasByteRange = position != null && length != null
680
-
681
- // Only throw if both ranges are provided
682
- if (hasTimeRange && hasByteRange) {
683
- throw IllegalArgumentException("Cannot specify both time range and byte range")
684
- }
685
-
686
- // Get decoding options with default configuration
687
- val defaultConfig = DecodingConfig(
688
- targetSampleRate = null,
689
- targetChannels = 1, // Default to mono
690
- targetBitDepth = 16,
691
- normalizeAudio = false
692
- )
693
-
694
- val config = (options["decodingOptions"] as? Map<String, Any>)?.let { decodingOptionsMap ->
695
- DecodingConfig(
696
- targetSampleRate = decodingOptionsMap["targetSampleRate"] as? Int,
697
- targetChannels = decodingOptionsMap["targetChannels"] as? Int,
698
- targetBitDepth = (decodingOptionsMap["targetBitDepth"] as? Int) ?: 16,
699
- normalizeAudio = (decodingOptionsMap["normalizeAudio"] as? Boolean) ?: false
700
- )
701
- } ?: defaultConfig
702
-
703
- // Load audio data based on range type (or full file if no range specified)
704
- val audioData = when {
705
- hasByteRange -> {
706
- val format = audioProcessor.getAudioFormat(fileUri)
707
- ?: throw IllegalArgumentException("Could not determine audio format")
708
-
709
- // Calculate time range from byte position
710
- val bytesPerSecond = format.sampleRate * format.channels * (format.bitDepth / 8)
711
- val effectiveStartTimeMs = (position!!.toLong() * 1000) / bytesPerSecond
712
- val effectiveEndTimeMs = effectiveStartTimeMs + (length!!.toLong() * 1000) / bytesPerSecond
713
-
714
- LogUtils.d(CLASS_NAME, "Loading audio with byte range: position=$position, length=$length")
715
-
716
- audioProcessor.loadAudioRange(
717
- fileUri = fileUri,
718
- startTimeMs = effectiveStartTimeMs,
719
- endTimeMs = effectiveEndTimeMs,
720
- config = config
721
- )
722
- }
723
- hasTimeRange -> {
724
- LogUtils.d(CLASS_NAME, "Loading audio with time range: startTimeMs=$startTimeMs, endTimeMs=$endTimeMs")
725
-
726
- audioProcessor.loadAudioRange(
727
- fileUri = fileUri,
728
- startTimeMs = startTimeMs!!.toLong(),
729
- endTimeMs = endTimeMs!!.toLong(),
730
- config = config
731
- )
732
- }
733
- else -> {
734
- LogUtils.d(CLASS_NAME, "Loading entire audio file")
735
- audioProcessor.loadAudioFromAnyFormat(fileUri, config)
736
- }
737
- } ?: throw IllegalStateException("Failed to load audio data")
738
-
739
- val featuresMap = options["features"] as? Map<*, *>
740
- val features = Features.parseFeatureOptions(featuresMap)
741
-
742
- val recordingConfig = RecordingConfig(
743
- sampleRate = audioData.sampleRate,
744
- channels = audioData.channels,
745
- encoding = when (audioData.bitDepth) {
746
- 8 -> "pcm_8bit"
747
- 16 -> "pcm_16bit"
748
- 32 -> "pcm_32bit"
749
- else -> throw IllegalArgumentException("Unsupported bit depth: ${audioData.bitDepth}")
750
- },
751
- segmentDurationMs = segmentDurationMs,
752
- features = features
753
- )
754
-
755
- LogUtils.d(CLASS_NAME, "extractAudioAnalysis: $recordingConfig")
756
- audioProcessor.resetCumulativeAmplitudeRange()
757
-
758
- val analysisData = audioProcessor.processAudioData(audioData.data, recordingConfig)
759
- promise.resolve(analysisData.toDictionary())
760
- } catch (e: Exception) {
761
- LogUtils.e(CLASS_NAME, "Failed to extract audio analysis: ${e.message}", e)
762
- promise.reject("PROCESSING_ERROR", e.message ?: "Unknown error", e)
763
- }
764
- }
765
-
766
- AsyncFunction("extractAudioData") { options: Map<String, Any>, promise: Promise ->
767
- try {
768
- val fileUri = requireNotNull(options["fileUri"] as? String) { "fileUri is required" }
769
- val startTimeMs = options["startTimeMs"] as? Number
770
- val endTimeMs = options["endTimeMs"] as? Number
771
- val position = options["position"] as? Number
772
- val length = options["length"] as? Number
773
-
774
- // Validate that we have either time range or byte range, but not both and not neither
775
- val hasTimeRange = startTimeMs != null && endTimeMs != null
776
- val hasByteRange = position != null && length != null
777
-
778
- if (!hasTimeRange && !hasByteRange) {
779
- throw IllegalArgumentException("Must specify either time range (startTimeMs, endTimeMs) or byte range (position, length)")
780
- }
781
- if (hasTimeRange && hasByteRange) {
782
- throw IllegalArgumentException("Cannot specify both time range and byte range")
783
- }
784
-
785
- // Get decoding options
786
- val decodingOptionsMap = options["decodingOptions"] as? Map<String, Any>
787
- val decodingConfig = if (decodingOptionsMap != null) {
788
- DecodingConfig(
789
- targetSampleRate = decodingOptionsMap["targetSampleRate"] as? Int,
790
- targetChannels = decodingOptionsMap["targetChannels"] as? Int,
791
- targetBitDepth = (decodingOptionsMap["targetBitDepth"] as? Int) ?: 16,
792
- normalizeAudio = (decodingOptionsMap["normalizeAudio"] as? Boolean) ?: false
793
- ).also {
794
- LogUtils.d(CLASS_NAME, """
795
- Using decoding config:
796
- - targetSampleRate: ${it.targetSampleRate ?: "original"}
797
- - targetChannels: ${it.targetChannels ?: "original"}
798
- - targetBitDepth: ${it.targetBitDepth}
799
- - normalizeAudio: ${it.normalizeAudio}
800
- """.trimIndent())
801
- }
802
- } else null
803
-
804
- val audioData = if (hasByteRange) {
805
- val format = audioProcessor.getAudioFormat(fileUri)
806
- ?: throw IllegalArgumentException("Could not determine audio format")
807
-
808
- // Calculate time range from byte position
809
- val bytesPerSecond = format.sampleRate * format.channels * (format.bitDepth / 8)
810
- val effectiveStartTimeMs = (position!!.toLong() * 1000) / bytesPerSecond
811
- val effectiveEndTimeMs = effectiveStartTimeMs + (length!!.toLong() * 1000) / bytesPerSecond
812
-
813
- LogUtils.d(CLASS_NAME, """
814
- Converting byte range to time range:
815
- - position: $position bytes
816
- - length: $length bytes
817
- - bytesPerSecond: $bytesPerSecond
818
- - effectiveStartTimeMs: $effectiveStartTimeMs
819
- - effectiveEndTimeMs: $effectiveEndTimeMs
820
- """.trimIndent())
821
-
822
- audioProcessor.loadAudioRange(
823
- fileUri = fileUri,
824
- startTimeMs = effectiveStartTimeMs,
825
- endTimeMs = effectiveEndTimeMs,
826
- config = decodingConfig
827
- )
828
- } else {
829
- // Must be time range due to earlier validation
830
- LogUtils.d(CLASS_NAME, """
831
- Using time range:
832
- - startTimeMs: $startTimeMs
833
- - endTimeMs: $endTimeMs
834
- """.trimIndent())
835
-
836
- audioProcessor.loadAudioRange(
837
- fileUri = fileUri,
838
- startTimeMs = startTimeMs!!.toLong(),
839
- endTimeMs = endTimeMs!!.toLong(),
840
- config = decodingConfig
841
- )
842
- } ?: throw IllegalStateException("Failed to load audio data")
843
-
844
- LogUtils.d(CLASS_NAME, """
845
- Audio data loaded successfully:
846
- - data size: ${audioData.data.size} bytes
847
- - sampleRate: ${audioData.sampleRate}
848
- - channels: ${audioData.channels}
849
- - bitDepth: ${audioData.bitDepth}
850
- - durationMs: ${audioData.durationMs}
851
- """.trimIndent())
852
-
853
- val includeNormalizedData = options["includeNormalizedData"] as? Boolean ?: false
854
- val includeBase64Data = options["includeBase64Data"] as? Boolean ?: false
855
- val includeWavHeader = options["includeWavHeader"] as? Boolean ?: false
856
- val bytesPerSample = audioData.bitDepth / 8
857
- val samples = audioData.data.size / (bytesPerSample * audioData.channels)
858
-
859
- // Create the result map
860
- val resultMap = mutableMapOf<String, Any>()
861
-
862
- // Add WAV header if requested
863
- if (includeWavHeader) {
864
- // Use ByteArrayOutputStream to write the WAV header and data
865
- val outputStream = java.io.ByteArrayOutputStream()
866
- val audioFileHandler = AudioFileHandler(appContext.reactContext!!.filesDir)
867
-
868
- // Write the WAV header
869
- audioFileHandler.writeWavHeader(
870
- outputStream,
871
- audioData.sampleRate,
872
- audioData.channels,
873
- audioData.bitDepth
874
- )
875
-
876
- // Write the PCM data
877
- outputStream.write(audioData.data)
878
-
879
- // Get the complete WAV data
880
- val wavData = outputStream.toByteArray()
881
-
882
- resultMap["pcmData"] = wavData
883
- resultMap["hasWavHeader"] = true
884
-
885
- LogUtils.d(CLASS_NAME, "Added WAV header to PCM data, total size: ${wavData.size} bytes")
886
- } else {
887
- resultMap["pcmData"] = audioData.data
888
- resultMap["hasWavHeader"] = false
889
- }
890
-
891
- // Add the rest of the data
892
- resultMap.putAll(mapOf(
893
- "sampleRate" to audioData.sampleRate,
894
- "channels" to audioData.channels,
895
- "bitDepth" to audioData.bitDepth,
896
- "durationMs" to audioData.durationMs,
897
- "format" to "pcm_${audioData.bitDepth}bit",
898
- "samples" to samples
899
- ))
900
-
901
- // Add checksum if requested
902
- if (options["computeChecksum"] == true) {
903
- val crc32 = CRC32()
904
- crc32.update(audioData.data)
905
- resultMap["checksum"] = crc32.value.toInt()
906
-
907
- LogUtils.d(CLASS_NAME, "Computed CRC32 checksum: ${crc32.value}")
908
- }
909
-
910
- if (includeNormalizedData) {
911
- val float32Data = AudioFormatUtils.convertByteArrayToFloatArray(
912
- audioData.data,
913
- "pcm_${audioData.bitDepth}bit"
914
- )
915
- resultMap["normalizedData"] = float32Data
916
- }
917
-
918
- if (includeBase64Data) {
919
- // Convert the PCM data to a base64 string
920
- val base64Data = android.util.Base64.encodeToString(
921
- audioData.data,
922
- android.util.Base64.NO_WRAP
923
- )
924
- resultMap["base64Data"] = base64Data
925
- }
926
-
927
- promise.resolve(resultMap)
928
- } catch (e: Exception) {
929
- LogUtils.e(CLASS_NAME, "Failed to extract audio data: ${e.message}")
930
- LogUtils.e(CLASS_NAME, "Stack trace: ${e.stackTraceToString()}")
931
- promise.reject("PROCESSING_ERROR", e.message ?: "Unknown error", e)
932
- }
933
- }
934
- }
935
-
936
- private fun initializeManager() {
937
- val context = appContext.reactContext ?: throw IllegalStateException("React context not available")
938
- val filesDir = context.filesDir
939
- val permissionUtils = PermissionUtils(context)
940
- val audioDataEncoder = AudioDataEncoder()
941
-
942
- // Initialize AudioDeviceManager
943
- LogUtils.d(CLASS_NAME, "🔧 Initializing AudioDeviceManager...")
944
- LogUtils.d(CLASS_NAME, "🔧 Device detection enabled: $enableDeviceDetection")
945
- audioDeviceManager = AudioDeviceManager(context, enableDeviceDetection)
946
- LogUtils.d(CLASS_NAME, "🔧 AudioDeviceManager initialized")
947
-
948
- // Initialize AudioRecorderManager with AudioDeviceManager integration
949
- audioRecorderManager = AudioRecorderManager.initialize(
950
- context,
951
- filesDir,
952
- permissionUtils,
953
- audioDataEncoder,
954
- this,
955
- enablePhoneStateHandling,
956
- enableBackgroundAudio
957
- )
958
-
959
- // Set up the delegate for the AudioDeviceManager
960
- audioDeviceManager.delegate = object : AudioDeviceManagerDelegate {
961
- override fun onDeviceDisconnected(deviceId: String) {
962
- LogUtils.d(CLASS_NAME, "📱 Device disconnected: $deviceId")
963
- // Handle device disconnection
964
- coroutineScope.launch {
965
- try {
966
- // If recording is active, handle the disconnection based on the recording config
967
- if (audioRecorderManager.isRecording) {
968
- handleDeviceDisconnection(deviceId)
969
- }
970
-
971
- // Notify JS about the disconnection
972
- sendEvent(Constants.DEVICE_CHANGED_EVENT, bundleOf(
973
- "type" to "deviceDisconnected",
974
- "deviceId" to deviceId
975
- ))
976
- } catch (e: Exception) {
977
- LogUtils.e(CLASS_NAME, "📱 Error handling device disconnection: ${e.message}", e)
978
- }
979
- }
980
- }
981
- }
982
-
983
- // Set up connection callback
984
- audioDeviceManager.onDeviceConnected = { deviceId ->
985
- LogUtils.d(CLASS_NAME, "📱 Device connected: $deviceId")
986
- // Notify JS about the connection
987
- sendEvent(Constants.DEVICE_CHANGED_EVENT, bundleOf(
988
- "type" to "deviceConnected",
989
- "deviceId" to deviceId
990
- ))
991
- }
992
-
993
- // Set up disconnection callback
994
- audioDeviceManager.onDeviceDisconnected = { deviceId ->
995
- LogUtils.d(CLASS_NAME, "📱 Device disconnected: $deviceId")
996
- // Notify JS about the disconnection
997
- sendEvent(Constants.DEVICE_CHANGED_EVENT, bundleOf(
998
- "type" to "deviceDisconnected",
999
- "deviceId" to deviceId
1000
- ))
1001
- }
1002
-
1003
- audioProcessor = AudioProcessor(filesDir)
1004
- }
1005
-
1006
- /**
1007
- * Handles audio device disconnection based on the recording configuration
1008
- */
1009
- private suspend fun handleDeviceDisconnection(deviceId: String) {
1010
- LogUtils.d(CLASS_NAME, "📱 handleDeviceDisconnection called for device: $deviceId")
1011
- // Get disconnection behavior from recorder config
1012
- val behavior = audioRecorderManager.getDeviceDisconnectionBehavior()
1013
- LogUtils.d(CLASS_NAME, "📱 Device disconnection behavior configured as: $behavior")
1014
-
1015
- when (behavior) {
1016
- "fallback" -> {
1017
- LogUtils.d(CLASS_NAME, "📱 Using fallback behavior, getting default device")
1018
- // Get default device
1019
- val defaultDevice = withContext(Dispatchers.IO) {
1020
- audioDeviceManager.getDefaultInputDevice()
1021
- }
1022
-
1023
- if (defaultDevice != null) {
1024
- LogUtils.d(CLASS_NAME, "📱 Falling back to default device: ${defaultDevice["name"]}")
1025
-
1026
- // Select default device
1027
- val deviceId = defaultDevice["id"] as String
1028
- LogUtils.d(CLASS_NAME, "📱 Attempting to select default device: $deviceId")
1029
- val success = audioDeviceManager.selectDevice(deviceId)
1030
-
1031
- if (success) {
1032
- LogUtils.d(CLASS_NAME, "📱 Successfully selected default device, notifying AudioRecorderManager")
1033
- // Notify AudioRecorderManager to update its recording source
1034
- audioRecorderManager.handleDeviceChange()
1035
-
1036
- // Notify JS about fallback
1037
- LogUtils.d(CLASS_NAME, "📱 Sending deviceFallback event to JS")
1038
- sendEvent(Constants.RECORDING_INTERRUPTED_EVENT_NAME, bundleOf(
1039
- "reason" to "deviceFallback",
1040
- "isPaused" to false,
1041
- "deviceId" to deviceId
1042
- ))
1043
- } else {
1044
- LogUtils.e(CLASS_NAME, "📱 Failed to select default device, pausing recording")
1045
-
1046
- // Fall back to pause if we can't select the default device
1047
- audioRecorderManager.pauseRecording(object : Promise {
1048
- override fun resolve(value: Any?) {
1049
- LogUtils.d(CLASS_NAME, "📱 Recording successfully paused, notifying AudioRecorderManager")
1050
- // Notify AudioRecorderManager to handle device change while paused
1051
- audioRecorderManager.handleDeviceChange()
1052
-
1053
- sendEvent(Constants.RECORDING_INTERRUPTED_EVENT_NAME, bundleOf(
1054
- "reason" to "deviceSwitchFailed",
1055
- "isPaused" to true
1056
- ))
1057
- }
1058
- override fun reject(code: String, message: String?, cause: Throwable?) {
1059
- LogUtils.e(CLASS_NAME, "📱 Failed to pause recording after device disconnection: $message")
1060
- }
1061
- })
1062
- }
1063
- } else {
1064
- LogUtils.e(CLASS_NAME, "📱 No default device found, pausing recording")
1065
-
1066
- // Fall back to pause if we can't find a default device
1067
- audioRecorderManager.pauseRecording(object : Promise {
1068
- override fun resolve(value: Any?) {
1069
- LogUtils.d(CLASS_NAME, "📱 Recording successfully paused when no default device found")
1070
- // Notify AudioRecorderManager to handle device change while paused
1071
- audioRecorderManager.handleDeviceChange()
1072
-
1073
- sendEvent(Constants.RECORDING_INTERRUPTED_EVENT_NAME, bundleOf(
1074
- "reason" to "deviceDisconnected",
1075
- "isPaused" to true
1076
- ))
1077
- }
1078
- override fun reject(code: String, message: String?, cause: Throwable?) {
1079
- LogUtils.e(CLASS_NAME, "📱 Failed to pause recording after device disconnection: $message")
1080
- }
1081
- })
1082
- }
1083
- }
1084
-
1085
- else -> { // Default to pause behavior
1086
- LogUtils.d(CLASS_NAME, "📱 Using pause behavior for device disconnection")
1087
-
1088
- // Pause recording
1089
- audioRecorderManager.pauseRecording(object : Promise {
1090
- override fun resolve(value: Any?) {
1091
- LogUtils.d(CLASS_NAME, "📱 Recording successfully paused after device disconnection")
1092
- // Notify AudioRecorderManager to handle device change while paused
1093
- audioRecorderManager.handleDeviceChange()
1094
-
1095
- sendEvent(Constants.RECORDING_INTERRUPTED_EVENT_NAME, bundleOf(
1096
- "reason" to "deviceDisconnected",
1097
- "isPaused" to true
1098
- ))
1099
- }
1100
- override fun reject(code: String, message: String?, cause: Throwable?) {
1101
- LogUtils.e(CLASS_NAME, "📱 Failed to pause recording after device disconnection: $message")
1102
- }
1103
- })
1104
- }
1105
- }
1106
- LogUtils.d(CLASS_NAME, "📱 handleDeviceDisconnection completed")
1107
- }
1108
-
1109
- override fun sendExpoEvent(eventName: String, params: Bundle) {
1110
- LogUtils.d(CLASS_NAME, "Sending event: $eventName")
1111
- this@ExpoAudioStreamModule.sendEvent(eventName, params)
1112
- }
1113
- }