@siteed/audio-studio 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. package/CHANGELOG.md +535 -0
  2. package/LICENSE +21 -0
  3. package/README.md +167 -0
  4. package/android/build.gradle +143 -0
  5. package/android/src/androidTest/assets/chorus.wav +0 -0
  6. package/android/src/androidTest/assets/jfk.wav +0 -0
  7. package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
  8. package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
  9. package/android/src/androidTest/java/net/siteed/audiostudio/AudioProcessorInstrumentedTest.kt +197 -0
  10. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderInstrumentedTest.kt +541 -0
  11. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderPerformanceInstrumentedTest.kt +234 -0
  12. package/android/src/androidTest/java/net/siteed/audiostudio/integration/AudioFocusStrategyIntegrationTest.kt +332 -0
  13. package/android/src/androidTest/java/net/siteed/audiostudio/integration/BufferDurationIntegrationTest.kt +324 -0
  14. package/android/src/androidTest/java/net/siteed/audiostudio/integration/CompressedOnlyOutputTest.kt +253 -0
  15. package/android/src/androidTest/java/net/siteed/audiostudio/integration/DeviceDisconnectionFallbackTest.kt +218 -0
  16. package/android/src/androidTest/java/net/siteed/audiostudio/integration/EventEmissionIntervalTest.kt +120 -0
  17. package/android/src/androidTest/java/net/siteed/audiostudio/integration/M4aFormatTest.kt +345 -0
  18. package/android/src/androidTest/java/net/siteed/audiostudio/integration/OutputControlIntegrationTest.kt +340 -0
  19. package/android/src/androidTest/java/net/siteed/audiostudio/integration/PcmStreamingDurationTest.kt +252 -0
  20. package/android/src/androidTest/java/net/siteed/audiostudio/integration/README.md +95 -0
  21. package/android/src/androidTest/java/net/siteed/audiostudio/integration/run_integration_tests.sh +43 -0
  22. package/android/src/main/AndroidManifest.xml +30 -0
  23. package/android/src/main/CMakeLists.txt +29 -0
  24. package/android/src/main/java/net/siteed/audiostudio/AudioAnalysisData.kt +188 -0
  25. package/android/src/main/java/net/siteed/audiostudio/AudioDataEncoder.kt +9 -0
  26. package/android/src/main/java/net/siteed/audiostudio/AudioDeviceManager.kt +1741 -0
  27. package/android/src/main/java/net/siteed/audiostudio/AudioFeaturesNative.kt +26 -0
  28. package/android/src/main/java/net/siteed/audiostudio/AudioFileHandler.kt +136 -0
  29. package/android/src/main/java/net/siteed/audiostudio/AudioFormatUtils.kt +354 -0
  30. package/android/src/main/java/net/siteed/audiostudio/AudioNotificationsManager.kt +439 -0
  31. package/android/src/main/java/net/siteed/audiostudio/AudioProcessor.kt +2237 -0
  32. package/android/src/main/java/net/siteed/audiostudio/AudioRecorderManager.kt +2163 -0
  33. package/android/src/main/java/net/siteed/audiostudio/AudioRecordingService.kt +167 -0
  34. package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +1112 -0
  35. package/android/src/main/java/net/siteed/audiostudio/AudioTrimmer.kt +1099 -0
  36. package/android/src/main/java/net/siteed/audiostudio/Constants.kt +37 -0
  37. package/android/src/main/java/net/siteed/audiostudio/EventSender.kt +7 -0
  38. package/android/src/main/java/net/siteed/audiostudio/FFT.kt +100 -0
  39. package/android/src/main/java/net/siteed/audiostudio/Features.kt +98 -0
  40. package/android/src/main/java/net/siteed/audiostudio/LogUtils.kt +93 -0
  41. package/android/src/main/java/net/siteed/audiostudio/MelSpectrogramNative.kt +36 -0
  42. package/android/src/main/java/net/siteed/audiostudio/NotificationConfig.kt +72 -0
  43. package/android/src/main/java/net/siteed/audiostudio/PermissionUtils.kt +68 -0
  44. package/android/src/main/java/net/siteed/audiostudio/RecordingActionReceiver.kt +59 -0
  45. package/android/src/main/java/net/siteed/audiostudio/RecordingConfig.kt +259 -0
  46. package/android/src/main/java/net/siteed/audiostudio/WaveformConfig.kt +19 -0
  47. package/android/src/main/java/net/siteed/audiostudio/WaveformRenderer.kt +159 -0
  48. package/android/src/main/jni/AudioFeaturesJNI.cpp +152 -0
  49. package/android/src/main/jni/MelSpectrogramJNI.cpp +165 -0
  50. package/android/src/main/res/drawable/ic_default_action_icon.xml +16 -0
  51. package/android/src/main/res/drawable/ic_microphone.xml +13 -0
  52. package/android/src/main/res/drawable/ic_pause.xml +10 -0
  53. package/android/src/main/res/drawable/ic_play.xml +10 -0
  54. package/android/src/main/res/drawable/ic_stop.xml +10 -0
  55. package/android/src/main/res/layout/notification_recording.xml +37 -0
  56. package/android/src/test/java/net/siteed/audiostudio/AudioFileHandlerTest.kt +279 -0
  57. package/android/src/test/java/net/siteed/audiostudio/AudioFocusStrategyTest.kt +249 -0
  58. package/android/src/test/java/net/siteed/audiostudio/AudioFormatTest.kt +151 -0
  59. package/android/src/test/java/net/siteed/audiostudio/AudioFormatUtilsTest.kt +273 -0
  60. package/android/src/test/java/net/siteed/audiostudio/DeviceDisconnectionFallbackUnitTest.kt +140 -0
  61. package/android/src/test/resources/chorus.wav +0 -0
  62. package/android/src/test/resources/generate_test_audio.py +94 -0
  63. package/android/src/test/resources/jfk.wav +0 -0
  64. package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
  65. package/android/src/test/resources/recorder_hello_world.wav +0 -0
  66. package/app.plugin.js +3 -0
  67. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js +4 -0
  68. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
  69. package/build/cjs/AudioAnalysis/audioFeaturesWasm.js +164 -0
  70. package/build/cjs/AudioAnalysis/audioFeaturesWasm.js.map +1 -0
  71. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js +213 -0
  72. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
  73. package/build/cjs/AudioAnalysis/extractAudioData.js +21 -0
  74. package/build/cjs/AudioAnalysis/extractAudioData.js.map +1 -0
  75. package/build/cjs/AudioAnalysis/extractMelSpectrogram.js +90 -0
  76. package/build/cjs/AudioAnalysis/extractMelSpectrogram.js.map +1 -0
  77. package/build/cjs/AudioAnalysis/extractPreview.js +28 -0
  78. package/build/cjs/AudioAnalysis/extractPreview.js.map +1 -0
  79. package/build/cjs/AudioAnalysis/extractWaveform.js +18 -0
  80. package/build/cjs/AudioAnalysis/extractWaveform.js.map +1 -0
  81. package/build/cjs/AudioAnalysis/melSpectrogramWasm.js +149 -0
  82. package/build/cjs/AudioAnalysis/melSpectrogramWasm.js.map +1 -0
  83. package/build/cjs/AudioDeviceManager.js +688 -0
  84. package/build/cjs/AudioDeviceManager.js.map +1 -0
  85. package/build/cjs/AudioRecorder.provider.js +78 -0
  86. package/build/cjs/AudioRecorder.provider.js.map +1 -0
  87. package/build/cjs/AudioStudio.native.js +8 -0
  88. package/build/cjs/AudioStudio.native.js.map +1 -0
  89. package/build/cjs/AudioStudio.types.js +11 -0
  90. package/build/cjs/AudioStudio.types.js.map +1 -0
  91. package/build/cjs/AudioStudio.web.js +708 -0
  92. package/build/cjs/AudioStudio.web.js.map +1 -0
  93. package/build/cjs/AudioStudioModule.js +718 -0
  94. package/build/cjs/AudioStudioModule.js.map +1 -0
  95. package/build/cjs/WebRecorder.web.js +865 -0
  96. package/build/cjs/WebRecorder.web.js.map +1 -0
  97. package/build/cjs/constants/platformLimitations.js +99 -0
  98. package/build/cjs/constants/platformLimitations.js.map +1 -0
  99. package/build/cjs/constants.js +20 -0
  100. package/build/cjs/constants.js.map +1 -0
  101. package/build/cjs/events.js +29 -0
  102. package/build/cjs/events.js.map +1 -0
  103. package/build/cjs/hooks/useAudioDevices.js +179 -0
  104. package/build/cjs/hooks/useAudioDevices.js.map +1 -0
  105. package/build/cjs/index.js +64 -0
  106. package/build/cjs/index.js.map +1 -0
  107. package/build/cjs/trimAudio.js +76 -0
  108. package/build/cjs/trimAudio.js.map +1 -0
  109. package/build/cjs/useAudioRecorder.js +535 -0
  110. package/build/cjs/useAudioRecorder.js.map +1 -0
  111. package/build/cjs/utils/BlobFix.js +502 -0
  112. package/build/cjs/utils/BlobFix.js.map +1 -0
  113. package/build/cjs/utils/audioProcessing.js +136 -0
  114. package/build/cjs/utils/audioProcessing.js.map +1 -0
  115. package/build/cjs/utils/cleanNativeOptions.js +22 -0
  116. package/build/cjs/utils/cleanNativeOptions.js.map +1 -0
  117. package/build/cjs/utils/concatenateBuffers.js +25 -0
  118. package/build/cjs/utils/concatenateBuffers.js.map +1 -0
  119. package/build/cjs/utils/convertPCMToFloat32.js +124 -0
  120. package/build/cjs/utils/convertPCMToFloat32.js.map +1 -0
  121. package/build/cjs/utils/crc32.js +52 -0
  122. package/build/cjs/utils/crc32.js.map +1 -0
  123. package/build/cjs/utils/encodingToBitDepth.js +17 -0
  124. package/build/cjs/utils/encodingToBitDepth.js.map +1 -0
  125. package/build/cjs/utils/getWavFileInfo.js +96 -0
  126. package/build/cjs/utils/getWavFileInfo.js.map +1 -0
  127. package/build/cjs/utils/writeWavHeader.js +88 -0
  128. package/build/cjs/utils/writeWavHeader.js.map +1 -0
  129. package/build/cjs/workers/InlineFeaturesExtractor.web.js +294 -0
  130. package/build/cjs/workers/InlineFeaturesExtractor.web.js.map +1 -0
  131. package/build/cjs/workers/inlineAudioWebWorker.web.js +190 -0
  132. package/build/cjs/workers/inlineAudioWebWorker.web.js.map +1 -0
  133. package/build/cjs/workers/wasmGlueString.web.js +27 -0
  134. package/build/cjs/workers/wasmGlueString.web.js.map +1 -0
  135. package/build/esm/AudioAnalysis/AudioAnalysis.types.js +3 -0
  136. package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
  137. package/build/esm/AudioAnalysis/audioFeaturesWasm.js +126 -0
  138. package/build/esm/AudioAnalysis/audioFeaturesWasm.js.map +1 -0
  139. package/build/esm/AudioAnalysis/extractAudioAnalysis.js +205 -0
  140. package/build/esm/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
  141. package/build/esm/AudioAnalysis/extractAudioData.js +14 -0
  142. package/build/esm/AudioAnalysis/extractAudioData.js.map +1 -0
  143. package/build/esm/AudioAnalysis/extractMelSpectrogram.js +86 -0
  144. package/build/esm/AudioAnalysis/extractMelSpectrogram.js.map +1 -0
  145. package/build/esm/AudioAnalysis/extractPreview.js +25 -0
  146. package/build/esm/AudioAnalysis/extractPreview.js.map +1 -0
  147. package/build/esm/AudioAnalysis/extractWaveform.js +11 -0
  148. package/build/esm/AudioAnalysis/extractWaveform.js.map +1 -0
  149. package/build/esm/AudioAnalysis/melSpectrogramWasm.js +111 -0
  150. package/build/esm/AudioAnalysis/melSpectrogramWasm.js.map +1 -0
  151. package/build/esm/AudioDeviceManager.js +681 -0
  152. package/build/esm/AudioDeviceManager.js.map +1 -0
  153. package/build/esm/AudioRecorder.provider.js +40 -0
  154. package/build/esm/AudioRecorder.provider.js.map +1 -0
  155. package/build/esm/AudioStudio.native.js +6 -0
  156. package/build/esm/AudioStudio.native.js.map +1 -0
  157. package/build/esm/AudioStudio.types.js +8 -0
  158. package/build/esm/AudioStudio.types.js.map +1 -0
  159. package/build/esm/AudioStudio.web.js +704 -0
  160. package/build/esm/AudioStudio.web.js.map +1 -0
  161. package/build/esm/AudioStudioModule.js +713 -0
  162. package/build/esm/AudioStudioModule.js.map +1 -0
  163. package/build/esm/WebRecorder.web.js +861 -0
  164. package/build/esm/WebRecorder.web.js.map +1 -0
  165. package/build/esm/constants/platformLimitations.js +90 -0
  166. package/build/esm/constants/platformLimitations.js.map +1 -0
  167. package/build/esm/constants.js +17 -0
  168. package/build/esm/constants.js.map +1 -0
  169. package/build/esm/events.js +21 -0
  170. package/build/esm/events.js.map +1 -0
  171. package/build/esm/hooks/useAudioDevices.js +176 -0
  172. package/build/esm/hooks/useAudioDevices.js.map +1 -0
  173. package/build/esm/index.js +23 -0
  174. package/build/esm/index.js.map +1 -0
  175. package/build/esm/trimAudio.js +69 -0
  176. package/build/esm/trimAudio.js.map +1 -0
  177. package/build/esm/useAudioRecorder.js +529 -0
  178. package/build/esm/useAudioRecorder.js.map +1 -0
  179. package/build/esm/utils/BlobFix.js +498 -0
  180. package/build/esm/utils/BlobFix.js.map +1 -0
  181. package/build/esm/utils/audioProcessing.js +133 -0
  182. package/build/esm/utils/audioProcessing.js.map +1 -0
  183. package/build/esm/utils/cleanNativeOptions.js +19 -0
  184. package/build/esm/utils/cleanNativeOptions.js.map +1 -0
  185. package/build/esm/utils/concatenateBuffers.js +21 -0
  186. package/build/esm/utils/concatenateBuffers.js.map +1 -0
  187. package/build/esm/utils/convertPCMToFloat32.js +120 -0
  188. package/build/esm/utils/convertPCMToFloat32.js.map +1 -0
  189. package/build/esm/utils/crc32.js +50 -0
  190. package/build/esm/utils/crc32.js.map +1 -0
  191. package/build/esm/utils/encodingToBitDepth.js +13 -0
  192. package/build/esm/utils/encodingToBitDepth.js.map +1 -0
  193. package/build/esm/utils/getWavFileInfo.js +92 -0
  194. package/build/esm/utils/getWavFileInfo.js.map +1 -0
  195. package/build/esm/utils/writeWavHeader.js +84 -0
  196. package/build/esm/utils/writeWavHeader.js.map +1 -0
  197. package/build/esm/workers/InlineFeaturesExtractor.web.js +291 -0
  198. package/build/esm/workers/InlineFeaturesExtractor.web.js.map +1 -0
  199. package/build/esm/workers/inlineAudioWebWorker.web.js +187 -0
  200. package/build/esm/workers/inlineAudioWebWorker.web.js.map +1 -0
  201. package/build/esm/workers/wasmGlueString.web.js +24 -0
  202. package/build/esm/workers/wasmGlueString.web.js.map +1 -0
  203. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts +198 -0
  204. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -0
  205. package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts +24 -0
  206. package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts.map +1 -0
  207. package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts +74 -0
  208. package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -0
  209. package/build/types/AudioAnalysis/extractAudioData.d.ts +3 -0
  210. package/build/types/AudioAnalysis/extractAudioData.d.ts.map +1 -0
  211. package/build/types/AudioAnalysis/extractMelSpectrogram.d.ts +20 -0
  212. package/build/types/AudioAnalysis/extractMelSpectrogram.d.ts.map +1 -0
  213. package/build/types/AudioAnalysis/extractPreview.d.ts +11 -0
  214. package/build/types/AudioAnalysis/extractPreview.d.ts.map +1 -0
  215. package/build/types/AudioAnalysis/extractWaveform.d.ts +8 -0
  216. package/build/types/AudioAnalysis/extractWaveform.d.ts.map +1 -0
  217. package/build/types/AudioAnalysis/melSpectrogramWasm.d.ts +16 -0
  218. package/build/types/AudioAnalysis/melSpectrogramWasm.d.ts.map +1 -0
  219. package/build/types/AudioDeviceManager.d.ts +187 -0
  220. package/build/types/AudioDeviceManager.d.ts.map +1 -0
  221. package/build/types/AudioRecorder.provider.d.ts +11 -0
  222. package/build/types/AudioRecorder.provider.d.ts.map +1 -0
  223. package/build/types/AudioStudio.native.d.ts +3 -0
  224. package/build/types/AudioStudio.native.d.ts.map +1 -0
  225. package/build/types/AudioStudio.types.d.ts +760 -0
  226. package/build/types/AudioStudio.types.d.ts.map +1 -0
  227. package/build/types/AudioStudio.web.d.ts +96 -0
  228. package/build/types/AudioStudio.web.d.ts.map +1 -0
  229. package/build/types/AudioStudioModule.d.ts +3 -0
  230. package/build/types/AudioStudioModule.d.ts.map +1 -0
  231. package/build/types/WebRecorder.web.d.ts +208 -0
  232. package/build/types/WebRecorder.web.d.ts.map +1 -0
  233. package/build/types/constants/platformLimitations.d.ts +40 -0
  234. package/build/types/constants/platformLimitations.d.ts.map +1 -0
  235. package/build/types/constants.d.ts +14 -0
  236. package/build/types/constants.d.ts.map +1 -0
  237. package/build/types/events.d.ts +29 -0
  238. package/build/types/events.d.ts.map +1 -0
  239. package/build/types/hooks/useAudioDevices.d.ts +15 -0
  240. package/build/types/hooks/useAudioDevices.d.ts.map +1 -0
  241. package/build/types/index.d.ts +21 -0
  242. package/build/types/index.d.ts.map +1 -0
  243. package/build/types/trimAudio.d.ts +25 -0
  244. package/build/types/trimAudio.d.ts.map +1 -0
  245. package/build/types/useAudioRecorder.d.ts +22 -0
  246. package/build/types/useAudioRecorder.d.ts.map +1 -0
  247. package/build/types/utils/BlobFix.d.ts +9 -0
  248. package/build/types/utils/BlobFix.d.ts.map +1 -0
  249. package/build/types/utils/audioProcessing.d.ts +24 -0
  250. package/build/types/utils/audioProcessing.d.ts.map +1 -0
  251. package/build/types/utils/cleanNativeOptions.d.ts +15 -0
  252. package/build/types/utils/cleanNativeOptions.d.ts.map +1 -0
  253. package/build/types/utils/concatenateBuffers.d.ts +8 -0
  254. package/build/types/utils/concatenateBuffers.d.ts.map +1 -0
  255. package/build/types/utils/convertPCMToFloat32.d.ts +13 -0
  256. package/build/types/utils/convertPCMToFloat32.d.ts.map +1 -0
  257. package/build/types/utils/crc32.d.ts +7 -0
  258. package/build/types/utils/crc32.d.ts.map +1 -0
  259. package/build/types/utils/encodingToBitDepth.d.ts +5 -0
  260. package/build/types/utils/encodingToBitDepth.d.ts.map +1 -0
  261. package/build/types/utils/getWavFileInfo.d.ts +26 -0
  262. package/build/types/utils/getWavFileInfo.d.ts.map +1 -0
  263. package/build/types/utils/writeWavHeader.d.ts +34 -0
  264. package/build/types/utils/writeWavHeader.d.ts.map +1 -0
  265. package/build/types/workers/InlineFeaturesExtractor.web.d.ts +2 -0
  266. package/build/types/workers/InlineFeaturesExtractor.web.d.ts.map +1 -0
  267. package/build/types/workers/inlineAudioWebWorker.web.d.ts +2 -0
  268. package/build/types/workers/inlineAudioWebWorker.web.d.ts.map +1 -0
  269. package/build/types/workers/wasmGlueString.web.d.ts +2 -0
  270. package/build/types/workers/wasmGlueString.web.d.ts.map +1 -0
  271. package/cpp/AudioFeatures.cpp +274 -0
  272. package/cpp/AudioFeatures.h +85 -0
  273. package/cpp/AudioFeaturesBridge.cpp +146 -0
  274. package/cpp/AudioFeaturesBridge.h +47 -0
  275. package/cpp/MelSpectrogram.cpp +227 -0
  276. package/cpp/MelSpectrogram.h +82 -0
  277. package/cpp/MelSpectrogramBridge.cpp +112 -0
  278. package/cpp/MelSpectrogramBridge.h +33 -0
  279. package/cpp/kiss_fft/COPYING +11 -0
  280. package/cpp/kiss_fft/_kiss_fft_guts.h +167 -0
  281. package/cpp/kiss_fft/kiss_fft.c +424 -0
  282. package/cpp/kiss_fft/kiss_fft.h +160 -0
  283. package/cpp/kiss_fft/kiss_fft_log.h +36 -0
  284. package/cpp/kiss_fft/kiss_fftr.c +155 -0
  285. package/cpp/kiss_fft/kiss_fftr.h +54 -0
  286. package/expo-module.config.json +10 -0
  287. package/ios/AudioAnalysisData.swift +74 -0
  288. package/ios/AudioDeviceManager.swift +670 -0
  289. package/ios/AudioFeaturesWrapper.h +21 -0
  290. package/ios/AudioFeaturesWrapper.mm +63 -0
  291. package/ios/AudioNotificationManager.swift +154 -0
  292. package/ios/AudioProcessingHelpers.swift +797 -0
  293. package/ios/AudioProcessor.swift +1191 -0
  294. package/ios/AudioStreamError.swift +7 -0
  295. package/ios/AudioStreamManager.swift +2369 -0
  296. package/ios/AudioStreamManagerDelegate.swift +16 -0
  297. package/ios/AudioStudio.podspec +39 -0
  298. package/ios/AudioStudioModule.swift +1111 -0
  299. package/ios/AudioStudioTests/AudioFileHandlerTests.swift +338 -0
  300. package/ios/AudioStudioTests/AudioFormatUtilsTests.swift +331 -0
  301. package/ios/AudioStudioTests/AudioTestHelpers.swift +130 -0
  302. package/ios/AudioStudioTests/CompressedOnlyOutputTests.swift +294 -0
  303. package/ios/AudioStudioTests/EventEmissionIntervalTests.swift +105 -0
  304. package/ios/AudioStudioTests/Info.plist +22 -0
  305. package/ios/AudioStudioTests/README.md +39 -0
  306. package/ios/AudioStudioTests/SimpleAudioTest.swift +98 -0
  307. package/ios/AudioStudioTests/TestAudioGenerator.swift +75 -0
  308. package/ios/DataPoint.swift +54 -0
  309. package/ios/DecodingConfig.swift +59 -0
  310. package/ios/FFT.swift +62 -0
  311. package/ios/Features.swift +95 -0
  312. package/ios/ISSUE_IOS.md +68 -0
  313. package/ios/Logger.swift +39 -0
  314. package/ios/MelSpectrogramWrapper.h +30 -0
  315. package/ios/MelSpectrogramWrapper.mm +97 -0
  316. package/ios/NotificationExtension.swift +15 -0
  317. package/ios/RecordingResult.swift +22 -0
  318. package/ios/RecordingSettings.swift +311 -0
  319. package/ios/WaveformExtractor.swift +105 -0
  320. package/ios/tests/README.md +41 -0
  321. package/ios/tests/integration/buffer_and_fallback_test.swift +178 -0
  322. package/ios/tests/integration/buffer_duration_test.swift +185 -0
  323. package/ios/tests/integration/compressed_only_output_test.swift +271 -0
  324. package/ios/tests/integration/output_control_test.swift +322 -0
  325. package/ios/tests/integration/run_integration_tests.sh +37 -0
  326. package/ios/tests/opus_support_test_macos.swift +154 -0
  327. package/ios/tests/standalone/audio_processing_test.swift +144 -0
  328. package/ios/tests/standalone/audio_recording_test.swift +277 -0
  329. package/ios/tests/standalone/audio_streaming_test.swift +249 -0
  330. package/ios/tests/standalone/standalone_test.swift +144 -0
  331. package/package.json +146 -0
  332. package/plugin/build/index.cjs +194 -0
  333. package/plugin/build/index.d.cts +22 -0
  334. package/plugin/build/index.js +194 -0
  335. package/plugin/src/index.ts +285 -0
  336. package/plugin/tsconfig.json +10 -0
  337. package/plugin/tsconfig.tsbuildinfo +1 -0
  338. package/prebuilt/wasm/mel-spectrogram.js +18 -0
  339. package/src/AudioAnalysis/AudioAnalysis.types.ts +226 -0
  340. package/src/AudioAnalysis/audio-features-wasm.d.ts +37 -0
  341. package/src/AudioAnalysis/audioFeaturesWasm.ts +200 -0
  342. package/src/AudioAnalysis/extractAudioAnalysis.ts +350 -0
  343. package/src/AudioAnalysis/extractAudioData.ts +17 -0
  344. package/src/AudioAnalysis/extractMelSpectrogram.ts +140 -0
  345. package/src/AudioAnalysis/extractPreview.ts +34 -0
  346. package/src/AudioAnalysis/extractWaveform.ts +22 -0
  347. package/src/AudioAnalysis/mel-spectrogram-wasm.d.ts +48 -0
  348. package/src/AudioAnalysis/melSpectrogramWasm.ts +179 -0
  349. package/src/AudioDeviceManager.ts +800 -0
  350. package/src/AudioRecorder.provider.tsx +57 -0
  351. package/src/AudioStudio.native.ts +6 -0
  352. package/src/AudioStudio.types.ts +899 -0
  353. package/src/AudioStudio.web.ts +911 -0
  354. package/src/AudioStudioModule.ts +984 -0
  355. package/src/WebRecorder.web.ts +1114 -0
  356. package/src/constants/platformLimitations.ts +118 -0
  357. package/src/constants.ts +21 -0
  358. package/src/events.ts +63 -0
  359. package/src/hooks/useAudioDevices.ts +213 -0
  360. package/src/index.ts +67 -0
  361. package/src/trimAudio.ts +94 -0
  362. package/src/types/crc-32.d.ts +9 -0
  363. package/src/useAudioRecorder.tsx +784 -0
  364. package/src/utils/BlobFix.ts +561 -0
  365. package/src/utils/audioProcessing.ts +205 -0
  366. package/src/utils/cleanNativeOptions.ts +18 -0
  367. package/src/utils/concatenateBuffers.ts +24 -0
  368. package/src/utils/convertPCMToFloat32.ts +170 -0
  369. package/src/utils/crc32.ts +59 -0
  370. package/src/utils/encodingToBitDepth.ts +18 -0
  371. package/src/utils/getWavFileInfo.ts +132 -0
  372. package/src/utils/writeWavHeader.ts +115 -0
  373. package/src/workers/InlineFeaturesExtractor.web.tsx +291 -0
  374. package/src/workers/inlineAudioWebWorker.web.tsx +186 -0
  375. package/src/workers/wasmGlueString.web.ts +23 -0
@@ -0,0 +1,324 @@
1
+ package net.siteed.audiostudio.integration
2
+
3
+ import android.media.AudioFormat
4
+ import android.media.AudioRecord
5
+ import android.media.MediaRecorder
6
+ import android.os.Build
7
+ import androidx.test.ext.junit.runners.AndroidJUnit4
8
+ import androidx.test.platform.app.InstrumentationRegistry
9
+ import org.junit.After
10
+ import org.junit.Before
11
+ import org.junit.Test
12
+ import org.junit.runner.RunWith
13
+ import java.util.concurrent.CountDownLatch
14
+ import java.util.concurrent.TimeUnit
15
+ import kotlin.math.abs
16
+
17
+ /**
18
+ * Integration test for Buffer Duration feature
19
+ * This tests the ACTUAL behavior of Android AudioRecord with different buffer sizes
20
+ */
21
+ @RunWith(AndroidJUnit4::class)
22
+ class BufferDurationIntegrationTest {
23
+
24
+ private val context = InstrumentationRegistry.getInstrumentation().targetContext
25
+ private val results = mutableListOf<TestResult>()
26
+ private var audioRecord: AudioRecord? = null
27
+
28
+ data class TestResult(
29
+ val name: String,
30
+ val passed: Boolean,
31
+ val message: String
32
+ )
33
+
34
+ @Before
35
+ fun setup() {
36
+ println("🧪 Buffer Duration Integration Test")
37
+ println("===================================\n")
38
+ }
39
+
40
+ @After
41
+ fun tearDown() {
42
+ audioRecord?.release()
43
+ printResults()
44
+ }
45
+
46
+ @Test
47
+ fun testDefaultBufferSize() {
48
+ println("Test 1: Default Buffer Size")
49
+ println("---------------------------")
50
+
51
+ val sampleRate = 48000
52
+ val channelConfig = AudioFormat.CHANNEL_IN_MONO
53
+ val audioFormat = AudioFormat.ENCODING_PCM_16BIT
54
+
55
+ // Get minimum buffer size
56
+ val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
57
+ println("✓ Android minimum buffer size: $minBufferSize bytes")
58
+
59
+ // Calculate frames from bytes (16-bit = 2 bytes per sample)
60
+ val minFrames = minBufferSize / 2
61
+ println("✓ Minimum frames: $minFrames")
62
+
63
+ // Test with default 1024 frames (2048 bytes)
64
+ val requestedBytes = 1024 * 2
65
+ val actualBufferSize = if (requestedBytes < minBufferSize) minBufferSize else requestedBytes
66
+
67
+ audioRecord = AudioRecord(
68
+ MediaRecorder.AudioSource.MIC,
69
+ sampleRate,
70
+ channelConfig,
71
+ audioFormat,
72
+ actualBufferSize
73
+ )
74
+
75
+ val state = audioRecord?.state
76
+ val passed = state == AudioRecord.STATE_INITIALIZED
77
+
78
+ results.add(TestResult(
79
+ name = "Default Buffer Size",
80
+ passed = passed,
81
+ message = "Requested: 1024 frames, Min required: $minFrames frames, State: ${if (passed) "INITIALIZED" else "UNINITIALIZED"}"
82
+ ))
83
+
84
+ println("✓ Requested: 1024 frames (${requestedBytes} bytes)")
85
+ println("✓ Actual buffer: ${actualBufferSize / 2} frames ($actualBufferSize bytes)")
86
+ println("✓ Initialization: ${if (passed) "SUCCESS" else "FAILED"}\n")
87
+ }
88
+
89
+ @Test
90
+ fun testCustomBufferSizes() {
91
+ println("Test 2: Custom Buffer Sizes")
92
+ println("---------------------------")
93
+
94
+ val sampleRate = 48000
95
+ val channelConfig = AudioFormat.CHANNEL_IN_MONO
96
+ val audioFormat = AudioFormat.ENCODING_PCM_16BIT
97
+ val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
98
+
99
+ val testCases = listOf(
100
+ 0.01 to "10ms",
101
+ 0.05 to "50ms",
102
+ 0.1 to "100ms",
103
+ 0.2 to "200ms",
104
+ 0.5 to "500ms"
105
+ )
106
+
107
+ for ((duration, name) in testCases) {
108
+ val requestedFrames = (duration * sampleRate).toInt()
109
+ val requestedBytes = requestedFrames * 2 // 16-bit = 2 bytes
110
+ val actualBufferSize = if (requestedBytes < minBufferSize) minBufferSize else requestedBytes
111
+
112
+ audioRecord?.release()
113
+ audioRecord = AudioRecord(
114
+ MediaRecorder.AudioSource.MIC,
115
+ sampleRate,
116
+ channelConfig,
117
+ audioFormat,
118
+ actualBufferSize
119
+ )
120
+
121
+ val state = audioRecord?.state
122
+ val passed = state == AudioRecord.STATE_INITIALIZED
123
+
124
+ // Test actual read behavior
125
+ if (passed) {
126
+ audioRecord?.startRecording()
127
+ val buffer = ByteArray(requestedBytes)
128
+ val bytesRead = audioRecord?.read(buffer, 0, buffer.size) ?: -1
129
+ audioRecord?.stop()
130
+
131
+ val framesRead = if (bytesRead > 0) bytesRead / 2 else 0
132
+
133
+ results.add(TestResult(
134
+ name = "Buffer $name",
135
+ passed = bytesRead > 0,
136
+ message = "Requested: $requestedFrames frames, Read: $framesRead frames"
137
+ ))
138
+
139
+ println(" $name: Requested $requestedFrames → Read $framesRead frames")
140
+ } else {
141
+ results.add(TestResult(
142
+ name = "Buffer $name",
143
+ passed = false,
144
+ message = "Failed to initialize AudioRecord"
145
+ ))
146
+ println(" $name: Failed to initialize")
147
+ }
148
+ }
149
+ println()
150
+ }
151
+
152
+ @Test
153
+ fun testBufferSizeLimits() {
154
+ println("Test 3: Buffer Size Limits")
155
+ println("--------------------------")
156
+
157
+ val sampleRate = 48000
158
+ val channelConfig = AudioFormat.CHANNEL_IN_MONO
159
+ val audioFormat = AudioFormat.ENCODING_PCM_16BIT
160
+
161
+ val extremeCases = listOf(
162
+ 100 to "Very small (100 frames)",
163
+ 50000 to "Very large (50000 frames)"
164
+ )
165
+
166
+ for ((frames, name) in extremeCases) {
167
+ val requestedBytes = frames * 2
168
+ val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
169
+ val actualBufferSize = if (requestedBytes < minBufferSize) minBufferSize else requestedBytes
170
+
171
+ audioRecord?.release()
172
+ audioRecord = AudioRecord(
173
+ MediaRecorder.AudioSource.MIC,
174
+ sampleRate,
175
+ channelConfig,
176
+ audioFormat,
177
+ actualBufferSize
178
+ )
179
+
180
+ val state = audioRecord?.state
181
+ val passed = state == AudioRecord.STATE_INITIALIZED
182
+
183
+ results.add(TestResult(
184
+ name = name,
185
+ passed = passed,
186
+ message = "Requested: $frames frames, Buffer size: ${actualBufferSize / 2} frames"
187
+ ))
188
+
189
+ println(" $name: $frames → ${actualBufferSize / 2} frames")
190
+ }
191
+ println()
192
+ }
193
+
194
+ @Test
195
+ fun testBufferAccumulation() {
196
+ println("Test 4: Buffer Accumulation for Small Durations")
197
+ println("-----------------------------------------------")
198
+
199
+ val sampleRate = 48000
200
+ val channelConfig = AudioFormat.CHANNEL_IN_MONO
201
+ val audioFormat = AudioFormat.ENCODING_PCM_16BIT
202
+ val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
203
+
204
+ // Test very small buffer (20ms = 960 frames)
205
+ val targetDuration = 0.02 // 20ms
206
+ val targetFrames = (targetDuration * sampleRate).toInt()
207
+ val targetBytes = targetFrames * 2
208
+
209
+ audioRecord?.release()
210
+ audioRecord = AudioRecord(
211
+ MediaRecorder.AudioSource.MIC,
212
+ sampleRate,
213
+ channelConfig,
214
+ audioFormat,
215
+ minBufferSize // Use minimum buffer size
216
+ )
217
+
218
+ if (audioRecord?.state == AudioRecord.STATE_INITIALIZED) {
219
+ audioRecord?.startRecording()
220
+
221
+ // Accumulate small chunks
222
+ val accumulator = mutableListOf<ByteArray>()
223
+ var totalFrames = 0
224
+ val smallBuffer = ByteArray(targetBytes)
225
+
226
+ // Read multiple times to accumulate
227
+ repeat(5) {
228
+ val bytesRead = audioRecord?.read(smallBuffer, 0, smallBuffer.size) ?: -1
229
+ if (bytesRead > 0) {
230
+ accumulator.add(smallBuffer.copyOf(bytesRead))
231
+ totalFrames += bytesRead / 2
232
+ }
233
+ }
234
+
235
+ audioRecord?.stop()
236
+
237
+ val passed = totalFrames >= targetFrames
238
+ results.add(TestResult(
239
+ name = "Buffer Accumulation",
240
+ passed = passed,
241
+ message = "Target: $targetFrames frames, Accumulated: $totalFrames frames over ${accumulator.size} reads"
242
+ ))
243
+
244
+ println("✓ Target frames: $targetFrames")
245
+ println("✓ Accumulated: $totalFrames frames")
246
+ println("✓ Number of reads: ${accumulator.size}")
247
+ } else {
248
+ results.add(TestResult(
249
+ name = "Buffer Accumulation",
250
+ passed = false,
251
+ message = "Failed to initialize AudioRecord"
252
+ ))
253
+ }
254
+ println()
255
+ }
256
+
257
+ @Test
258
+ fun testDifferentSampleRates() {
259
+ println("Test 5: Different Sample Rates")
260
+ println("------------------------------")
261
+
262
+ val channelConfig = AudioFormat.CHANNEL_IN_MONO
263
+ val audioFormat = AudioFormat.ENCODING_PCM_16BIT
264
+ val bufferDuration = 0.1 // 100ms
265
+
266
+ val sampleRates = listOf(16000, 44100, 48000)
267
+
268
+ for (sampleRate in sampleRates) {
269
+ val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
270
+ val targetFrames = (bufferDuration * sampleRate).toInt()
271
+ val targetBytes = targetFrames * 2
272
+ val actualBufferSize = if (targetBytes < minBufferSize) minBufferSize else targetBytes
273
+
274
+ audioRecord?.release()
275
+ audioRecord = AudioRecord(
276
+ MediaRecorder.AudioSource.MIC,
277
+ sampleRate,
278
+ channelConfig,
279
+ audioFormat,
280
+ actualBufferSize
281
+ )
282
+
283
+ val state = audioRecord?.state
284
+ val passed = state == AudioRecord.STATE_INITIALIZED
285
+
286
+ results.add(TestResult(
287
+ name = "Sample Rate ${sampleRate}Hz",
288
+ passed = passed,
289
+ message = "Buffer duration: ${bufferDuration}s, Frames: ${actualBufferSize / 2}"
290
+ ))
291
+
292
+ println(" ${sampleRate}Hz: ${if (passed) "SUCCESS" else "FAILED"} - ${actualBufferSize / 2} frames")
293
+ }
294
+ println()
295
+ }
296
+
297
+ private fun printResults() {
298
+ println("📊 Test Results")
299
+ println("===============")
300
+
301
+ val passed = results.count { it.passed }
302
+ val total = results.size
303
+
304
+ for (result in results) {
305
+ val status = if (result.passed) "✅" else "❌"
306
+ println("$status ${result.name}")
307
+ println(" ${result.message}")
308
+ }
309
+
310
+ println("\nSummary: $passed/$total tests passed")
311
+
312
+ if (passed == total) {
313
+ println("🎉 All tests passed!")
314
+ } else {
315
+ println("⚠️ Some tests failed")
316
+ }
317
+
318
+ println("\n📝 Key Findings:")
319
+ println("- Android enforces minimum buffer size via getMinBufferSize()")
320
+ println("- Minimum varies by device and sample rate")
321
+ println("- Small buffers require accumulation strategy")
322
+ println("- AudioRecord handles buffer sizing more flexibly than iOS")
323
+ }
324
+ }
@@ -0,0 +1,253 @@
1
+ package net.siteed.audiostudio.integration
2
+
3
+ import android.os.Bundle
4
+ import androidx.test.ext.junit.runners.AndroidJUnit4
5
+ import androidx.test.platform.app.InstrumentationRegistry
6
+ import org.junit.Test
7
+ import org.junit.runner.RunWith
8
+ import org.junit.Assert.*
9
+ import java.io.File
10
+
11
+ /**
12
+ * Integration test for Compressed-Only Output (Issue #244)
13
+ *
14
+ * This test validates that when primary output is disabled and compressed output
15
+ * is enabled, the compressed file information is properly returned in the result.
16
+ */
17
+ @RunWith(AndroidJUnit4::class)
18
+ class CompressedOnlyOutputTest {
19
+
20
+ private val context = InstrumentationRegistry.getInstrumentation().targetContext
21
+
22
+ @Test
23
+ fun testCompressedOnlyOutput_AAC() {
24
+ println("🧪 Test: Compressed-Only Output with AAC")
25
+ println("---------------------------------------")
26
+
27
+ // Configuration with primary disabled, compressed enabled
28
+ val config = Bundle().apply {
29
+ putInt("sampleRate", 44100)
30
+ putInt("channels", 1)
31
+ putString("encoding", "pcm_16bit")
32
+
33
+ val outputBundle = Bundle().apply {
34
+ val primaryBundle = Bundle().apply {
35
+ putBoolean("enabled", false)
36
+ }
37
+ val compressedBundle = Bundle().apply {
38
+ putBoolean("enabled", true)
39
+ putString("format", "aac")
40
+ putInt("bitrate", 128000)
41
+ }
42
+ putBundle("primary", primaryBundle)
43
+ putBundle("compressed", compressedBundle)
44
+ }
45
+ putBundle("output", outputBundle)
46
+ }
47
+
48
+ // Simulate recording result
49
+ val result = simulateRecording(config)
50
+
51
+ // Verify compression info is present
52
+ val compressionBundle = result.getBundle("compression")
53
+ assertNotNull("Compression info should not be null", compressionBundle)
54
+
55
+ // Verify compressed file URI is provided
56
+ val compressedFileUri = compressionBundle?.getString("compressedFileUri")
57
+ assertNotNull("Compressed file URI should not be null", compressedFileUri)
58
+ assertNotEquals("Compressed file URI should not be empty", "", compressedFileUri)
59
+
60
+ // Verify format and bitrate
61
+ assertEquals("aac", compressionBundle?.getString("format"))
62
+ assertEquals(128000, compressionBundle?.getInt("bitrate"))
63
+
64
+ println("✅ Compression info properly returned")
65
+ println("✅ Compressed file URI: $compressedFileUri")
66
+ println("✅ Format: ${compressionBundle?.getString("format")}")
67
+ println()
68
+ }
69
+
70
+ @Test
71
+ fun testCompressedOnlyOutput_Opus() {
72
+ println("🧪 Test: Compressed-Only Output with Opus")
73
+ println("----------------------------------------")
74
+
75
+ val config = Bundle().apply {
76
+ putInt("sampleRate", 48000)
77
+ putInt("channels", 1)
78
+ putString("encoding", "pcm_16bit")
79
+
80
+ val outputBundle = Bundle().apply {
81
+ val primaryBundle = Bundle().apply {
82
+ putBoolean("enabled", false)
83
+ }
84
+ val compressedBundle = Bundle().apply {
85
+ putBoolean("enabled", true)
86
+ putString("format", "opus")
87
+ putInt("bitrate", 64000)
88
+ }
89
+ putBundle("primary", primaryBundle)
90
+ putBundle("compressed", compressedBundle)
91
+ }
92
+ putBundle("output", outputBundle)
93
+ }
94
+
95
+ val result = simulateRecording(config)
96
+ val compressionBundle = result.getBundle("compression")
97
+
98
+ assertNotNull("Compression info should not be null", compressionBundle)
99
+ assertEquals("opus", compressionBundle?.getString("format"))
100
+ assertEquals(64000, compressionBundle?.getInt("bitrate"))
101
+
102
+ println("✅ Opus compression properly configured")
103
+ println()
104
+ }
105
+
106
+ @Test
107
+ fun testFileAccessibility() {
108
+ println("🧪 Test: Verify Compressed File Accessibility")
109
+ println("--------------------------------------------")
110
+
111
+ val config = Bundle().apply {
112
+ putInt("sampleRate", 44100)
113
+ putInt("channels", 1)
114
+ putString("encoding", "pcm_16bit")
115
+
116
+ val outputBundle = Bundle().apply {
117
+ val primaryBundle = Bundle().apply {
118
+ putBoolean("enabled", false)
119
+ }
120
+ val compressedBundle = Bundle().apply {
121
+ putBoolean("enabled", true)
122
+ putString("format", "aac")
123
+ }
124
+ putBundle("primary", primaryBundle)
125
+ putBundle("compressed", compressedBundle)
126
+ }
127
+ putBundle("output", outputBundle)
128
+ }
129
+
130
+ val result = simulateRecording(config)
131
+
132
+ // Check main result structure
133
+ val fileUri = result.getString("fileUri", "")
134
+ val filename = result.getString("filename", "")
135
+
136
+ // Check compression structure
137
+ val compressionBundle = result.getBundle("compression")
138
+ val compressedUri = compressionBundle?.getString("compressedFileUri")
139
+ val compressedSize = compressionBundle?.getLong("size", 0L) ?: 0L
140
+
141
+ // When primary is disabled, we should have access to compressed file
142
+ val hasAccessToCompressed = !compressedUri.isNullOrEmpty() || fileUri.isNotEmpty()
143
+
144
+ assertTrue("Should have access to compressed file", hasAccessToCompressed)
145
+ assertTrue("Compressed file should have size > 0", compressedSize > 0)
146
+
147
+ println("✅ Compressed file is accessible")
148
+ println("✅ File size reported: $compressedSize bytes")
149
+ println()
150
+ }
151
+
152
+ @Test
153
+ fun testBugScenario() {
154
+ println("🧪 Test: Current Bug Scenario")
155
+ println("-----------------------------")
156
+
157
+ // This test demonstrates the current bug
158
+ val config = Bundle().apply {
159
+ putInt("sampleRate", 44100)
160
+ putInt("channels", 1)
161
+ putString("encoding", "pcm_16bit")
162
+
163
+ val outputBundle = Bundle().apply {
164
+ val primaryBundle = Bundle().apply {
165
+ putBoolean("enabled", false)
166
+ }
167
+ val compressedBundle = Bundle().apply {
168
+ putBoolean("enabled", true)
169
+ putString("format", "aac")
170
+ putInt("bitrate", 128000)
171
+ }
172
+ putBundle("primary", primaryBundle)
173
+ putBundle("compressed", compressedBundle)
174
+ }
175
+ putBundle("output", outputBundle)
176
+ }
177
+
178
+ // Current buggy behavior
179
+ val buggyResult = simulateBuggyRecording(config)
180
+
181
+ // This is what currently happens - compression is null
182
+ val compressionBundle = buggyResult.getBundle("compression")
183
+
184
+ if (compressionBundle == null) {
185
+ println("❌ BUG CONFIRMED: Compression info is null when primary is disabled")
186
+ println(" This prevents users from accessing the compressed file")
187
+ } else {
188
+ println("✅ Bug appears to be fixed - compression info is present")
189
+ }
190
+ println()
191
+ }
192
+
193
+ /**
194
+ * Simulates the expected correct behavior after fix
195
+ */
196
+ private fun simulateRecording(config: Bundle): Bundle {
197
+ val outputConfig = config.getBundle("output")
198
+ val primaryEnabled = outputConfig?.getBundle("primary")?.getBoolean("enabled", true) ?: true
199
+ val compressedConfig = outputConfig?.getBundle("compressed")
200
+ val compressedEnabled = compressedConfig?.getBoolean("enabled", false) ?: false
201
+
202
+ return Bundle().apply {
203
+ if (!primaryEnabled) {
204
+ // Expected behavior after fix
205
+ putString("fileUri", "")
206
+ putString("filename", "stream-only")
207
+ putLong("durationMs", 5000)
208
+ putLong("size", 0)
209
+ putString("mimeType", "audio/wav")
210
+
211
+ // FIXED: Include compression info when compressed is enabled
212
+ if (compressedEnabled) {
213
+ val compressionBundle = Bundle().apply {
214
+ putString("compressedFileUri", "file:///storage/emulated/0/Android/data/test/files/recording.aac")
215
+ putString("format", compressedConfig?.getString("format") ?: "aac")
216
+ putInt("bitrate", compressedConfig?.getInt("bitrate") ?: 128000)
217
+ putLong("size", 40000)
218
+ putString("mimeType", "audio/aac")
219
+ }
220
+ putBundle("compression", compressionBundle)
221
+ }
222
+ } else {
223
+ // Normal behavior
224
+ putString("fileUri", "file:///storage/emulated/0/Android/data/test/files/recording.wav")
225
+ putString("filename", "recording.wav")
226
+ putLong("durationMs", 5000)
227
+ putLong("size", 240000)
228
+ putString("mimeType", "audio/wav")
229
+ }
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Simulates the current buggy behavior
235
+ */
236
+ private fun simulateBuggyRecording(config: Bundle): Bundle {
237
+ val outputConfig = config.getBundle("output")
238
+ val primaryEnabled = outputConfig?.getBundle("primary")?.getBoolean("enabled", true) ?: true
239
+
240
+ return Bundle().apply {
241
+ if (!primaryEnabled) {
242
+ // Current buggy behavior
243
+ putString("fileUri", "")
244
+ putString("filename", "stream-only")
245
+ putLong("durationMs", 5000)
246
+ putLong("size", 0)
247
+ putString("mimeType", "audio/wav")
248
+ // BUG: compression is null even when compressed output is enabled
249
+ putBundle("compression", null)
250
+ }
251
+ }
252
+ }
253
+ }