@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.
- package/CHANGELOG.md +535 -0
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/android/build.gradle +143 -0
- package/android/src/androidTest/assets/chorus.wav +0 -0
- package/android/src/androidTest/assets/jfk.wav +0 -0
- package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
- package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioProcessorInstrumentedTest.kt +197 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderInstrumentedTest.kt +541 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderPerformanceInstrumentedTest.kt +234 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/AudioFocusStrategyIntegrationTest.kt +332 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/BufferDurationIntegrationTest.kt +324 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/CompressedOnlyOutputTest.kt +253 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/DeviceDisconnectionFallbackTest.kt +218 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/EventEmissionIntervalTest.kt +120 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/M4aFormatTest.kt +345 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/OutputControlIntegrationTest.kt +340 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/PcmStreamingDurationTest.kt +252 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/README.md +95 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/run_integration_tests.sh +43 -0
- package/android/src/main/AndroidManifest.xml +30 -0
- package/android/src/main/CMakeLists.txt +29 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioAnalysisData.kt +188 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioDataEncoder.kt +9 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioDeviceManager.kt +1741 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioFeaturesNative.kt +26 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioFileHandler.kt +136 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioFormatUtils.kt +354 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioNotificationsManager.kt +439 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioProcessor.kt +2237 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioRecorderManager.kt +2163 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioRecordingService.kt +167 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +1112 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioTrimmer.kt +1099 -0
- package/android/src/main/java/net/siteed/audiostudio/Constants.kt +37 -0
- package/android/src/main/java/net/siteed/audiostudio/EventSender.kt +7 -0
- package/android/src/main/java/net/siteed/audiostudio/FFT.kt +100 -0
- package/android/src/main/java/net/siteed/audiostudio/Features.kt +98 -0
- package/android/src/main/java/net/siteed/audiostudio/LogUtils.kt +93 -0
- package/android/src/main/java/net/siteed/audiostudio/MelSpectrogramNative.kt +36 -0
- package/android/src/main/java/net/siteed/audiostudio/NotificationConfig.kt +72 -0
- package/android/src/main/java/net/siteed/audiostudio/PermissionUtils.kt +68 -0
- package/android/src/main/java/net/siteed/audiostudio/RecordingActionReceiver.kt +59 -0
- package/android/src/main/java/net/siteed/audiostudio/RecordingConfig.kt +259 -0
- package/android/src/main/java/net/siteed/audiostudio/WaveformConfig.kt +19 -0
- package/android/src/main/java/net/siteed/audiostudio/WaveformRenderer.kt +159 -0
- package/android/src/main/jni/AudioFeaturesJNI.cpp +152 -0
- package/android/src/main/jni/MelSpectrogramJNI.cpp +165 -0
- package/android/src/main/res/drawable/ic_default_action_icon.xml +16 -0
- package/android/src/main/res/drawable/ic_microphone.xml +13 -0
- package/android/src/main/res/drawable/ic_pause.xml +10 -0
- package/android/src/main/res/drawable/ic_play.xml +10 -0
- package/android/src/main/res/drawable/ic_stop.xml +10 -0
- package/android/src/main/res/layout/notification_recording.xml +37 -0
- package/android/src/test/java/net/siteed/audiostudio/AudioFileHandlerTest.kt +279 -0
- package/android/src/test/java/net/siteed/audiostudio/AudioFocusStrategyTest.kt +249 -0
- package/android/src/test/java/net/siteed/audiostudio/AudioFormatTest.kt +151 -0
- package/android/src/test/java/net/siteed/audiostudio/AudioFormatUtilsTest.kt +273 -0
- package/android/src/test/java/net/siteed/audiostudio/DeviceDisconnectionFallbackUnitTest.kt +140 -0
- package/android/src/test/resources/chorus.wav +0 -0
- package/android/src/test/resources/generate_test_audio.py +94 -0
- package/android/src/test/resources/jfk.wav +0 -0
- package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
- package/android/src/test/resources/recorder_hello_world.wav +0 -0
- package/app.plugin.js +3 -0
- package/build/cjs/AudioAnalysis/AudioAnalysis.types.js +4 -0
- package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
- package/build/cjs/AudioAnalysis/audioFeaturesWasm.js +164 -0
- package/build/cjs/AudioAnalysis/audioFeaturesWasm.js.map +1 -0
- package/build/cjs/AudioAnalysis/extractAudioAnalysis.js +213 -0
- package/build/cjs/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
- package/build/cjs/AudioAnalysis/extractAudioData.js +21 -0
- package/build/cjs/AudioAnalysis/extractAudioData.js.map +1 -0
- package/build/cjs/AudioAnalysis/extractMelSpectrogram.js +90 -0
- package/build/cjs/AudioAnalysis/extractMelSpectrogram.js.map +1 -0
- package/build/cjs/AudioAnalysis/extractPreview.js +28 -0
- package/build/cjs/AudioAnalysis/extractPreview.js.map +1 -0
- package/build/cjs/AudioAnalysis/extractWaveform.js +18 -0
- package/build/cjs/AudioAnalysis/extractWaveform.js.map +1 -0
- package/build/cjs/AudioAnalysis/melSpectrogramWasm.js +149 -0
- package/build/cjs/AudioAnalysis/melSpectrogramWasm.js.map +1 -0
- package/build/cjs/AudioDeviceManager.js +688 -0
- package/build/cjs/AudioDeviceManager.js.map +1 -0
- package/build/cjs/AudioRecorder.provider.js +78 -0
- package/build/cjs/AudioRecorder.provider.js.map +1 -0
- package/build/cjs/AudioStudio.native.js +8 -0
- package/build/cjs/AudioStudio.native.js.map +1 -0
- package/build/cjs/AudioStudio.types.js +11 -0
- package/build/cjs/AudioStudio.types.js.map +1 -0
- package/build/cjs/AudioStudio.web.js +708 -0
- package/build/cjs/AudioStudio.web.js.map +1 -0
- package/build/cjs/AudioStudioModule.js +718 -0
- package/build/cjs/AudioStudioModule.js.map +1 -0
- package/build/cjs/WebRecorder.web.js +865 -0
- package/build/cjs/WebRecorder.web.js.map +1 -0
- package/build/cjs/constants/platformLimitations.js +99 -0
- package/build/cjs/constants/platformLimitations.js.map +1 -0
- package/build/cjs/constants.js +20 -0
- package/build/cjs/constants.js.map +1 -0
- package/build/cjs/events.js +29 -0
- package/build/cjs/events.js.map +1 -0
- package/build/cjs/hooks/useAudioDevices.js +179 -0
- package/build/cjs/hooks/useAudioDevices.js.map +1 -0
- package/build/cjs/index.js +64 -0
- package/build/cjs/index.js.map +1 -0
- package/build/cjs/trimAudio.js +76 -0
- package/build/cjs/trimAudio.js.map +1 -0
- package/build/cjs/useAudioRecorder.js +535 -0
- package/build/cjs/useAudioRecorder.js.map +1 -0
- package/build/cjs/utils/BlobFix.js +502 -0
- package/build/cjs/utils/BlobFix.js.map +1 -0
- package/build/cjs/utils/audioProcessing.js +136 -0
- package/build/cjs/utils/audioProcessing.js.map +1 -0
- package/build/cjs/utils/cleanNativeOptions.js +22 -0
- package/build/cjs/utils/cleanNativeOptions.js.map +1 -0
- package/build/cjs/utils/concatenateBuffers.js +25 -0
- package/build/cjs/utils/concatenateBuffers.js.map +1 -0
- package/build/cjs/utils/convertPCMToFloat32.js +124 -0
- package/build/cjs/utils/convertPCMToFloat32.js.map +1 -0
- package/build/cjs/utils/crc32.js +52 -0
- package/build/cjs/utils/crc32.js.map +1 -0
- package/build/cjs/utils/encodingToBitDepth.js +17 -0
- package/build/cjs/utils/encodingToBitDepth.js.map +1 -0
- package/build/cjs/utils/getWavFileInfo.js +96 -0
- package/build/cjs/utils/getWavFileInfo.js.map +1 -0
- package/build/cjs/utils/writeWavHeader.js +88 -0
- package/build/cjs/utils/writeWavHeader.js.map +1 -0
- package/build/cjs/workers/InlineFeaturesExtractor.web.js +294 -0
- package/build/cjs/workers/InlineFeaturesExtractor.web.js.map +1 -0
- package/build/cjs/workers/inlineAudioWebWorker.web.js +190 -0
- package/build/cjs/workers/inlineAudioWebWorker.web.js.map +1 -0
- package/build/cjs/workers/wasmGlueString.web.js +27 -0
- package/build/cjs/workers/wasmGlueString.web.js.map +1 -0
- package/build/esm/AudioAnalysis/AudioAnalysis.types.js +3 -0
- package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
- package/build/esm/AudioAnalysis/audioFeaturesWasm.js +126 -0
- package/build/esm/AudioAnalysis/audioFeaturesWasm.js.map +1 -0
- package/build/esm/AudioAnalysis/extractAudioAnalysis.js +205 -0
- package/build/esm/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
- package/build/esm/AudioAnalysis/extractAudioData.js +14 -0
- package/build/esm/AudioAnalysis/extractAudioData.js.map +1 -0
- package/build/esm/AudioAnalysis/extractMelSpectrogram.js +86 -0
- package/build/esm/AudioAnalysis/extractMelSpectrogram.js.map +1 -0
- package/build/esm/AudioAnalysis/extractPreview.js +25 -0
- package/build/esm/AudioAnalysis/extractPreview.js.map +1 -0
- package/build/esm/AudioAnalysis/extractWaveform.js +11 -0
- package/build/esm/AudioAnalysis/extractWaveform.js.map +1 -0
- package/build/esm/AudioAnalysis/melSpectrogramWasm.js +111 -0
- package/build/esm/AudioAnalysis/melSpectrogramWasm.js.map +1 -0
- package/build/esm/AudioDeviceManager.js +681 -0
- package/build/esm/AudioDeviceManager.js.map +1 -0
- package/build/esm/AudioRecorder.provider.js +40 -0
- package/build/esm/AudioRecorder.provider.js.map +1 -0
- package/build/esm/AudioStudio.native.js +6 -0
- package/build/esm/AudioStudio.native.js.map +1 -0
- package/build/esm/AudioStudio.types.js +8 -0
- package/build/esm/AudioStudio.types.js.map +1 -0
- package/build/esm/AudioStudio.web.js +704 -0
- package/build/esm/AudioStudio.web.js.map +1 -0
- package/build/esm/AudioStudioModule.js +713 -0
- package/build/esm/AudioStudioModule.js.map +1 -0
- package/build/esm/WebRecorder.web.js +861 -0
- package/build/esm/WebRecorder.web.js.map +1 -0
- package/build/esm/constants/platformLimitations.js +90 -0
- package/build/esm/constants/platformLimitations.js.map +1 -0
- package/build/esm/constants.js +17 -0
- package/build/esm/constants.js.map +1 -0
- package/build/esm/events.js +21 -0
- package/build/esm/events.js.map +1 -0
- package/build/esm/hooks/useAudioDevices.js +176 -0
- package/build/esm/hooks/useAudioDevices.js.map +1 -0
- package/build/esm/index.js +23 -0
- package/build/esm/index.js.map +1 -0
- package/build/esm/trimAudio.js +69 -0
- package/build/esm/trimAudio.js.map +1 -0
- package/build/esm/useAudioRecorder.js +529 -0
- package/build/esm/useAudioRecorder.js.map +1 -0
- package/build/esm/utils/BlobFix.js +498 -0
- package/build/esm/utils/BlobFix.js.map +1 -0
- package/build/esm/utils/audioProcessing.js +133 -0
- package/build/esm/utils/audioProcessing.js.map +1 -0
- package/build/esm/utils/cleanNativeOptions.js +19 -0
- package/build/esm/utils/cleanNativeOptions.js.map +1 -0
- package/build/esm/utils/concatenateBuffers.js +21 -0
- package/build/esm/utils/concatenateBuffers.js.map +1 -0
- package/build/esm/utils/convertPCMToFloat32.js +120 -0
- package/build/esm/utils/convertPCMToFloat32.js.map +1 -0
- package/build/esm/utils/crc32.js +50 -0
- package/build/esm/utils/crc32.js.map +1 -0
- package/build/esm/utils/encodingToBitDepth.js +13 -0
- package/build/esm/utils/encodingToBitDepth.js.map +1 -0
- package/build/esm/utils/getWavFileInfo.js +92 -0
- package/build/esm/utils/getWavFileInfo.js.map +1 -0
- package/build/esm/utils/writeWavHeader.js +84 -0
- package/build/esm/utils/writeWavHeader.js.map +1 -0
- package/build/esm/workers/InlineFeaturesExtractor.web.js +291 -0
- package/build/esm/workers/InlineFeaturesExtractor.web.js.map +1 -0
- package/build/esm/workers/inlineAudioWebWorker.web.js +187 -0
- package/build/esm/workers/inlineAudioWebWorker.web.js.map +1 -0
- package/build/esm/workers/wasmGlueString.web.js +24 -0
- package/build/esm/workers/wasmGlueString.web.js.map +1 -0
- package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts +198 -0
- package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -0
- package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts +24 -0
- package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts.map +1 -0
- package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts +74 -0
- package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -0
- package/build/types/AudioAnalysis/extractAudioData.d.ts +3 -0
- package/build/types/AudioAnalysis/extractAudioData.d.ts.map +1 -0
- package/build/types/AudioAnalysis/extractMelSpectrogram.d.ts +20 -0
- package/build/types/AudioAnalysis/extractMelSpectrogram.d.ts.map +1 -0
- package/build/types/AudioAnalysis/extractPreview.d.ts +11 -0
- package/build/types/AudioAnalysis/extractPreview.d.ts.map +1 -0
- package/build/types/AudioAnalysis/extractWaveform.d.ts +8 -0
- package/build/types/AudioAnalysis/extractWaveform.d.ts.map +1 -0
- package/build/types/AudioAnalysis/melSpectrogramWasm.d.ts +16 -0
- package/build/types/AudioAnalysis/melSpectrogramWasm.d.ts.map +1 -0
- package/build/types/AudioDeviceManager.d.ts +187 -0
- package/build/types/AudioDeviceManager.d.ts.map +1 -0
- package/build/types/AudioRecorder.provider.d.ts +11 -0
- package/build/types/AudioRecorder.provider.d.ts.map +1 -0
- package/build/types/AudioStudio.native.d.ts +3 -0
- package/build/types/AudioStudio.native.d.ts.map +1 -0
- package/build/types/AudioStudio.types.d.ts +760 -0
- package/build/types/AudioStudio.types.d.ts.map +1 -0
- package/build/types/AudioStudio.web.d.ts +96 -0
- package/build/types/AudioStudio.web.d.ts.map +1 -0
- package/build/types/AudioStudioModule.d.ts +3 -0
- package/build/types/AudioStudioModule.d.ts.map +1 -0
- package/build/types/WebRecorder.web.d.ts +208 -0
- package/build/types/WebRecorder.web.d.ts.map +1 -0
- package/build/types/constants/platformLimitations.d.ts +40 -0
- package/build/types/constants/platformLimitations.d.ts.map +1 -0
- package/build/types/constants.d.ts +14 -0
- package/build/types/constants.d.ts.map +1 -0
- package/build/types/events.d.ts +29 -0
- package/build/types/events.d.ts.map +1 -0
- package/build/types/hooks/useAudioDevices.d.ts +15 -0
- package/build/types/hooks/useAudioDevices.d.ts.map +1 -0
- package/build/types/index.d.ts +21 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/trimAudio.d.ts +25 -0
- package/build/types/trimAudio.d.ts.map +1 -0
- package/build/types/useAudioRecorder.d.ts +22 -0
- package/build/types/useAudioRecorder.d.ts.map +1 -0
- package/build/types/utils/BlobFix.d.ts +9 -0
- package/build/types/utils/BlobFix.d.ts.map +1 -0
- package/build/types/utils/audioProcessing.d.ts +24 -0
- package/build/types/utils/audioProcessing.d.ts.map +1 -0
- package/build/types/utils/cleanNativeOptions.d.ts +15 -0
- package/build/types/utils/cleanNativeOptions.d.ts.map +1 -0
- package/build/types/utils/concatenateBuffers.d.ts +8 -0
- package/build/types/utils/concatenateBuffers.d.ts.map +1 -0
- package/build/types/utils/convertPCMToFloat32.d.ts +13 -0
- package/build/types/utils/convertPCMToFloat32.d.ts.map +1 -0
- package/build/types/utils/crc32.d.ts +7 -0
- package/build/types/utils/crc32.d.ts.map +1 -0
- package/build/types/utils/encodingToBitDepth.d.ts +5 -0
- package/build/types/utils/encodingToBitDepth.d.ts.map +1 -0
- package/build/types/utils/getWavFileInfo.d.ts +26 -0
- package/build/types/utils/getWavFileInfo.d.ts.map +1 -0
- package/build/types/utils/writeWavHeader.d.ts +34 -0
- package/build/types/utils/writeWavHeader.d.ts.map +1 -0
- package/build/types/workers/InlineFeaturesExtractor.web.d.ts +2 -0
- package/build/types/workers/InlineFeaturesExtractor.web.d.ts.map +1 -0
- package/build/types/workers/inlineAudioWebWorker.web.d.ts +2 -0
- package/build/types/workers/inlineAudioWebWorker.web.d.ts.map +1 -0
- package/build/types/workers/wasmGlueString.web.d.ts +2 -0
- package/build/types/workers/wasmGlueString.web.d.ts.map +1 -0
- package/cpp/AudioFeatures.cpp +274 -0
- package/cpp/AudioFeatures.h +85 -0
- package/cpp/AudioFeaturesBridge.cpp +146 -0
- package/cpp/AudioFeaturesBridge.h +47 -0
- package/cpp/MelSpectrogram.cpp +227 -0
- package/cpp/MelSpectrogram.h +82 -0
- package/cpp/MelSpectrogramBridge.cpp +112 -0
- package/cpp/MelSpectrogramBridge.h +33 -0
- package/cpp/kiss_fft/COPYING +11 -0
- package/cpp/kiss_fft/_kiss_fft_guts.h +167 -0
- package/cpp/kiss_fft/kiss_fft.c +424 -0
- package/cpp/kiss_fft/kiss_fft.h +160 -0
- package/cpp/kiss_fft/kiss_fft_log.h +36 -0
- package/cpp/kiss_fft/kiss_fftr.c +155 -0
- package/cpp/kiss_fft/kiss_fftr.h +54 -0
- package/expo-module.config.json +10 -0
- package/ios/AudioAnalysisData.swift +74 -0
- package/ios/AudioDeviceManager.swift +670 -0
- package/ios/AudioFeaturesWrapper.h +21 -0
- package/ios/AudioFeaturesWrapper.mm +63 -0
- package/ios/AudioNotificationManager.swift +154 -0
- package/ios/AudioProcessingHelpers.swift +797 -0
- package/ios/AudioProcessor.swift +1191 -0
- package/ios/AudioStreamError.swift +7 -0
- package/ios/AudioStreamManager.swift +2369 -0
- package/ios/AudioStreamManagerDelegate.swift +16 -0
- package/ios/AudioStudio.podspec +39 -0
- package/ios/AudioStudioModule.swift +1111 -0
- package/ios/AudioStudioTests/AudioFileHandlerTests.swift +338 -0
- package/ios/AudioStudioTests/AudioFormatUtilsTests.swift +331 -0
- package/ios/AudioStudioTests/AudioTestHelpers.swift +130 -0
- package/ios/AudioStudioTests/CompressedOnlyOutputTests.swift +294 -0
- package/ios/AudioStudioTests/EventEmissionIntervalTests.swift +105 -0
- package/ios/AudioStudioTests/Info.plist +22 -0
- package/ios/AudioStudioTests/README.md +39 -0
- package/ios/AudioStudioTests/SimpleAudioTest.swift +98 -0
- package/ios/AudioStudioTests/TestAudioGenerator.swift +75 -0
- package/ios/DataPoint.swift +54 -0
- package/ios/DecodingConfig.swift +59 -0
- package/ios/FFT.swift +62 -0
- package/ios/Features.swift +95 -0
- package/ios/ISSUE_IOS.md +68 -0
- package/ios/Logger.swift +39 -0
- package/ios/MelSpectrogramWrapper.h +30 -0
- package/ios/MelSpectrogramWrapper.mm +97 -0
- package/ios/NotificationExtension.swift +15 -0
- package/ios/RecordingResult.swift +22 -0
- package/ios/RecordingSettings.swift +311 -0
- package/ios/WaveformExtractor.swift +105 -0
- package/ios/tests/README.md +41 -0
- package/ios/tests/integration/buffer_and_fallback_test.swift +178 -0
- package/ios/tests/integration/buffer_duration_test.swift +185 -0
- package/ios/tests/integration/compressed_only_output_test.swift +271 -0
- package/ios/tests/integration/output_control_test.swift +322 -0
- package/ios/tests/integration/run_integration_tests.sh +37 -0
- package/ios/tests/opus_support_test_macos.swift +154 -0
- package/ios/tests/standalone/audio_processing_test.swift +144 -0
- package/ios/tests/standalone/audio_recording_test.swift +277 -0
- package/ios/tests/standalone/audio_streaming_test.swift +249 -0
- package/ios/tests/standalone/standalone_test.swift +144 -0
- package/package.json +146 -0
- package/plugin/build/index.cjs +194 -0
- package/plugin/build/index.d.cts +22 -0
- package/plugin/build/index.js +194 -0
- package/plugin/src/index.ts +285 -0
- package/plugin/tsconfig.json +10 -0
- package/plugin/tsconfig.tsbuildinfo +1 -0
- package/prebuilt/wasm/mel-spectrogram.js +18 -0
- package/src/AudioAnalysis/AudioAnalysis.types.ts +226 -0
- package/src/AudioAnalysis/audio-features-wasm.d.ts +37 -0
- package/src/AudioAnalysis/audioFeaturesWasm.ts +200 -0
- package/src/AudioAnalysis/extractAudioAnalysis.ts +350 -0
- package/src/AudioAnalysis/extractAudioData.ts +17 -0
- package/src/AudioAnalysis/extractMelSpectrogram.ts +140 -0
- package/src/AudioAnalysis/extractPreview.ts +34 -0
- package/src/AudioAnalysis/extractWaveform.ts +22 -0
- package/src/AudioAnalysis/mel-spectrogram-wasm.d.ts +48 -0
- package/src/AudioAnalysis/melSpectrogramWasm.ts +179 -0
- package/src/AudioDeviceManager.ts +800 -0
- package/src/AudioRecorder.provider.tsx +57 -0
- package/src/AudioStudio.native.ts +6 -0
- package/src/AudioStudio.types.ts +899 -0
- package/src/AudioStudio.web.ts +911 -0
- package/src/AudioStudioModule.ts +984 -0
- package/src/WebRecorder.web.ts +1114 -0
- package/src/constants/platformLimitations.ts +118 -0
- package/src/constants.ts +21 -0
- package/src/events.ts +63 -0
- package/src/hooks/useAudioDevices.ts +213 -0
- package/src/index.ts +67 -0
- package/src/trimAudio.ts +94 -0
- package/src/types/crc-32.d.ts +9 -0
- package/src/useAudioRecorder.tsx +784 -0
- package/src/utils/BlobFix.ts +561 -0
- package/src/utils/audioProcessing.ts +205 -0
- package/src/utils/cleanNativeOptions.ts +18 -0
- package/src/utils/concatenateBuffers.ts +24 -0
- package/src/utils/convertPCMToFloat32.ts +170 -0
- package/src/utils/crc32.ts +59 -0
- package/src/utils/encodingToBitDepth.ts +18 -0
- package/src/utils/getWavFileInfo.ts +132 -0
- package/src/utils/writeWavHeader.ts +115 -0
- package/src/workers/InlineFeaturesExtractor.web.tsx +291 -0
- package/src/workers/inlineAudioWebWorker.web.tsx +186 -0
- package/src/workers/wasmGlueString.web.ts +23 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
package net.siteed.audiostudio.integration
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
5
|
+
import androidx.test.platform.app.InstrumentationRegistry
|
|
6
|
+
import net.siteed.audiostudio.OutputConfig
|
|
7
|
+
import net.siteed.audiostudio.RecordingConfig
|
|
8
|
+
import org.junit.After
|
|
9
|
+
import org.junit.Assert.*
|
|
10
|
+
import org.junit.Before
|
|
11
|
+
import org.junit.Test
|
|
12
|
+
import org.junit.runner.RunWith
|
|
13
|
+
import java.io.File
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Integration test for device disconnection fallback behavior.
|
|
17
|
+
* Tests various scenarios when audio devices are disconnected during recording.
|
|
18
|
+
*/
|
|
19
|
+
@RunWith(AndroidJUnit4::class)
|
|
20
|
+
class DeviceDisconnectionFallbackTest {
|
|
21
|
+
|
|
22
|
+
private lateinit var context: Context
|
|
23
|
+
private lateinit var testDir: File
|
|
24
|
+
|
|
25
|
+
@Before
|
|
26
|
+
fun setup() {
|
|
27
|
+
context = InstrumentationRegistry.getInstrumentation().targetContext
|
|
28
|
+
|
|
29
|
+
// Create test directory
|
|
30
|
+
testDir = File(context.getExternalFilesDir(null), "fallback_test")
|
|
31
|
+
testDir.mkdirs()
|
|
32
|
+
|
|
33
|
+
// Clear any existing test files
|
|
34
|
+
testDir.listFiles()?.forEach { it.delete() }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@After
|
|
38
|
+
fun tearDown() {
|
|
39
|
+
// Clean up test files
|
|
40
|
+
testDir.listFiles()?.forEach { it.delete() }
|
|
41
|
+
testDir.delete()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Test
|
|
45
|
+
fun testDeviceDisconnectionBehavior_Fallback() {
|
|
46
|
+
println("🧪 Test: Device Disconnection with Fallback Behavior")
|
|
47
|
+
println("--------------------------------------------------")
|
|
48
|
+
|
|
49
|
+
// Create recording config with fallback behavior
|
|
50
|
+
val config = RecordingConfig(
|
|
51
|
+
sampleRate = 44100,
|
|
52
|
+
channels = 1,
|
|
53
|
+
encoding = "pcm_16bit",
|
|
54
|
+
deviceDisconnectionBehavior = "fallback",
|
|
55
|
+
output = OutputConfig(
|
|
56
|
+
primary = OutputConfig.PrimaryOutput(
|
|
57
|
+
enabled = true,
|
|
58
|
+
format = "wav"
|
|
59
|
+
)
|
|
60
|
+
),
|
|
61
|
+
outputDirectory = testDir.absolutePath,
|
|
62
|
+
filename = "fallback_test.wav"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
// Verify device disconnection behavior is set
|
|
66
|
+
assertEquals("Device disconnection behavior should be fallback",
|
|
67
|
+
"fallback", config.deviceDisconnectionBehavior)
|
|
68
|
+
|
|
69
|
+
println("✅ RecordingConfig correctly configured with fallback behavior")
|
|
70
|
+
|
|
71
|
+
// Note: Actual device disconnection simulation would require running the full
|
|
72
|
+
// AudioStudioModule with device connection/disconnection events
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@Test
|
|
76
|
+
fun testDeviceDisconnectionBehavior_Pause() {
|
|
77
|
+
println("🧪 Test: Device Disconnection with Pause Behavior")
|
|
78
|
+
println("-----------------------------------------------")
|
|
79
|
+
|
|
80
|
+
val config = RecordingConfig(
|
|
81
|
+
sampleRate = 44100,
|
|
82
|
+
channels = 1,
|
|
83
|
+
encoding = "pcm_16bit",
|
|
84
|
+
deviceDisconnectionBehavior = "pause",
|
|
85
|
+
output = OutputConfig(
|
|
86
|
+
primary = OutputConfig.PrimaryOutput(
|
|
87
|
+
enabled = true,
|
|
88
|
+
format = "wav"
|
|
89
|
+
)
|
|
90
|
+
),
|
|
91
|
+
outputDirectory = testDir.absolutePath,
|
|
92
|
+
filename = "pause_test.wav"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
// Verify device disconnection behavior is set to pause
|
|
96
|
+
assertEquals("Device disconnection behavior should be pause",
|
|
97
|
+
"pause", config.deviceDisconnectionBehavior)
|
|
98
|
+
|
|
99
|
+
println("✅ RecordingConfig correctly configured with pause behavior")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@Test
|
|
103
|
+
fun testDefaultDeviceDisconnectionBehavior() {
|
|
104
|
+
println("🧪 Test: Default Device Disconnection Behavior")
|
|
105
|
+
println("--------------------------------------------")
|
|
106
|
+
|
|
107
|
+
// Create config without specifying deviceDisconnectionBehavior
|
|
108
|
+
val config = RecordingConfig(
|
|
109
|
+
sampleRate = 44100,
|
|
110
|
+
channels = 1,
|
|
111
|
+
encoding = "pcm_16bit",
|
|
112
|
+
output = OutputConfig(
|
|
113
|
+
primary = OutputConfig.PrimaryOutput(
|
|
114
|
+
enabled = true,
|
|
115
|
+
format = "wav"
|
|
116
|
+
)
|
|
117
|
+
),
|
|
118
|
+
outputDirectory = testDir.absolutePath,
|
|
119
|
+
filename = "default_test.wav"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
// Default behavior should be null (handled as pause in implementation)
|
|
123
|
+
assertNull("Default device disconnection behavior should be null",
|
|
124
|
+
config.deviceDisconnectionBehavior)
|
|
125
|
+
|
|
126
|
+
println("✅ Default device disconnection behavior is null (treated as 'pause')")
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@Test
|
|
130
|
+
fun testInterruptionEventReasons() {
|
|
131
|
+
println("🧪 Test: Interruption Event Reasons")
|
|
132
|
+
println("----------------------------------")
|
|
133
|
+
|
|
134
|
+
// Test that the expected event reasons are valid
|
|
135
|
+
val fallbackReasons = listOf("deviceFallback", "deviceSwitchFailed")
|
|
136
|
+
val pauseReasons = listOf("deviceDisconnected")
|
|
137
|
+
|
|
138
|
+
println("📋 Expected reasons for fallback behavior:")
|
|
139
|
+
fallbackReasons.forEach { reason ->
|
|
140
|
+
println(" - $reason")
|
|
141
|
+
assertNotNull("Reason should not be null", reason)
|
|
142
|
+
assertTrue("Reason should not be empty", reason.isNotEmpty())
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
println("📋 Expected reasons for pause behavior:")
|
|
146
|
+
pauseReasons.forEach { reason ->
|
|
147
|
+
println(" - $reason")
|
|
148
|
+
assertNotNull("Reason should not be null", reason)
|
|
149
|
+
assertTrue("Reason should not be empty", reason.isNotEmpty())
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
println("✅ All interruption event reasons are valid")
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@Test
|
|
156
|
+
fun testAllDeviceDisconnectionBehaviors() {
|
|
157
|
+
println("🧪 Test: All Device Disconnection Behaviors")
|
|
158
|
+
println("-----------------------------------------")
|
|
159
|
+
|
|
160
|
+
val behaviors = listOf("fallback", "pause")
|
|
161
|
+
|
|
162
|
+
behaviors.forEach { behavior ->
|
|
163
|
+
val config = RecordingConfig(
|
|
164
|
+
sampleRate = 44100,
|
|
165
|
+
channels = 1,
|
|
166
|
+
encoding = "pcm_16bit",
|
|
167
|
+
deviceDisconnectionBehavior = behavior,
|
|
168
|
+
output = OutputConfig(
|
|
169
|
+
primary = OutputConfig.PrimaryOutput(
|
|
170
|
+
enabled = true,
|
|
171
|
+
format = "wav"
|
|
172
|
+
)
|
|
173
|
+
),
|
|
174
|
+
outputDirectory = testDir.absolutePath,
|
|
175
|
+
filename = "${behavior}_test.wav"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
// Verify configuration
|
|
179
|
+
assertEquals("Device disconnection behavior should be $behavior",
|
|
180
|
+
behavior, config.deviceDisconnectionBehavior)
|
|
181
|
+
|
|
182
|
+
println("✅ Behavior '$behavior' correctly configured")
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@Test
|
|
187
|
+
fun testDeviceDisconnectionWithCompressedOutput() {
|
|
188
|
+
println("🧪 Test: Device Disconnection with Compressed Output")
|
|
189
|
+
println("--------------------------------------------------")
|
|
190
|
+
|
|
191
|
+
// Test fallback behavior with compressed output enabled
|
|
192
|
+
val config = RecordingConfig(
|
|
193
|
+
sampleRate = 44100,
|
|
194
|
+
channels = 1,
|
|
195
|
+
encoding = "pcm_16bit",
|
|
196
|
+
deviceDisconnectionBehavior = "fallback",
|
|
197
|
+
output = OutputConfig(
|
|
198
|
+
primary = OutputConfig.PrimaryOutput(
|
|
199
|
+
enabled = false
|
|
200
|
+
),
|
|
201
|
+
compressed = OutputConfig.CompressedOutput(
|
|
202
|
+
enabled = true,
|
|
203
|
+
format = "aac",
|
|
204
|
+
bitrate = 128000
|
|
205
|
+
)
|
|
206
|
+
),
|
|
207
|
+
outputDirectory = testDir.absolutePath,
|
|
208
|
+
filename = "compressed_fallback_test"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
assertEquals("Device disconnection behavior should be fallback",
|
|
212
|
+
"fallback", config.deviceDisconnectionBehavior)
|
|
213
|
+
assertTrue("Compressed output should be enabled", config.output.compressed.enabled)
|
|
214
|
+
assertFalse("Primary output should be disabled", config.output.primary.enabled)
|
|
215
|
+
|
|
216
|
+
println("✅ Fallback behavior configured with compressed-only output")
|
|
217
|
+
}
|
|
218
|
+
}
|
package/android/src/androidTest/java/net/siteed/audiostudio/integration/EventEmissionIntervalTest.kt
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
package net.siteed.audiostudio.integration
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
6
|
+
import androidx.test.platform.app.InstrumentationRegistry
|
|
7
|
+
import org.junit.Test
|
|
8
|
+
import org.junit.runner.RunWith
|
|
9
|
+
import org.junit.Assert.*
|
|
10
|
+
import java.io.File
|
|
11
|
+
import kotlin.math.abs
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Integration test to validate event emission interval enforcement.
|
|
15
|
+
*
|
|
16
|
+
* This test verifies that the configured intervals respect platform
|
|
17
|
+
* minimums to prevent excessive CPU usage.
|
|
18
|
+
*/
|
|
19
|
+
@RunWith(AndroidJUnit4::class)
|
|
20
|
+
class EventEmissionIntervalTest {
|
|
21
|
+
|
|
22
|
+
private val TAG = "EventEmissionIntervalTest"
|
|
23
|
+
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
|
24
|
+
|
|
25
|
+
@Test
|
|
26
|
+
fun testMinimumIntervalEnforcement() {
|
|
27
|
+
println("🧪 Test: Minimum Interval Enforcement")
|
|
28
|
+
println("------------------------------------")
|
|
29
|
+
|
|
30
|
+
// Test cases for different requested intervals
|
|
31
|
+
val testCases = listOf(
|
|
32
|
+
5 to 10, // 5ms should be clamped to MIN_INTERVAL (10ms)
|
|
33
|
+
10 to 10, // 10ms should remain 10ms
|
|
34
|
+
50 to 50, // 50ms should remain 50ms
|
|
35
|
+
100 to 100 // 100ms should remain 100ms
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
for ((requested, expected) in testCases) {
|
|
39
|
+
val config = Bundle().apply {
|
|
40
|
+
putInt("sampleRate", 48000)
|
|
41
|
+
putInt("channels", 1)
|
|
42
|
+
putLong("interval", requested.toLong())
|
|
43
|
+
putLong("intervalAnalysis", requested.toLong())
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Parse the config to validate interval enforcement
|
|
47
|
+
val configMap = bundleToMap(config)
|
|
48
|
+
val result = net.siteed.audiostudio.RecordingConfig.fromMap(configMap)
|
|
49
|
+
|
|
50
|
+
assertTrue("Config parsing should succeed", result.isSuccess)
|
|
51
|
+
val (recordingConfig, _) = result.getOrNull()!!
|
|
52
|
+
|
|
53
|
+
println("Requested interval: ${requested}ms")
|
|
54
|
+
println("Actual interval: ${recordingConfig.interval}ms")
|
|
55
|
+
println("Expected interval: ${expected}ms")
|
|
56
|
+
|
|
57
|
+
assertEquals(
|
|
58
|
+
"Interval should be clamped to minimum if below MIN_INTERVAL",
|
|
59
|
+
expected.toLong(),
|
|
60
|
+
recordingConfig.interval
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
assertEquals(
|
|
64
|
+
"Analysis interval should be clamped to minimum if below MIN_INTERVAL",
|
|
65
|
+
expected.toLong(),
|
|
66
|
+
recordingConfig.intervalAnalysis
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
println("✓ Passed\n")
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Test
|
|
74
|
+
fun testIntervalConsistencyAcrossPlatforms() {
|
|
75
|
+
println("🧪 Test: Platform Consistency Check")
|
|
76
|
+
println("----------------------------------")
|
|
77
|
+
|
|
78
|
+
// Document the expected behavior across platforms
|
|
79
|
+
println("Expected behavior after fix:")
|
|
80
|
+
println("- iOS: Minimum interval = 10ms")
|
|
81
|
+
println("- Android: Minimum interval = 10ms (enforced)")
|
|
82
|
+
println("")
|
|
83
|
+
|
|
84
|
+
// Verify Android enforces the minimum
|
|
85
|
+
val intervals = listOf(1, 5, 10, 50, 100)
|
|
86
|
+
for (interval in intervals) {
|
|
87
|
+
val config = Bundle().apply {
|
|
88
|
+
putLong("interval", interval.toLong())
|
|
89
|
+
putLong("intervalAnalysis", interval.toLong())
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
val configMap = bundleToMap(config)
|
|
93
|
+
val result = net.siteed.audiostudio.RecordingConfig.fromMap(configMap)
|
|
94
|
+
val (recordingConfig, _) = result.getOrNull()!!
|
|
95
|
+
|
|
96
|
+
val expectedInterval = maxOf(10, interval).toLong()
|
|
97
|
+
assertEquals(
|
|
98
|
+
"Android should enforce MIN_INTERVAL of 10ms",
|
|
99
|
+
expectedInterval,
|
|
100
|
+
recordingConfig.interval
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
println("✓ Interval ${interval}ms -> ${recordingConfig.interval}ms")
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Helper to convert Bundle to Map for RecordingConfig
|
|
109
|
+
*/
|
|
110
|
+
private fun bundleToMap(bundle: Bundle): Map<String, Any?> {
|
|
111
|
+
val map = mutableMapOf<String, Any?>()
|
|
112
|
+
for (key in bundle.keySet()) {
|
|
113
|
+
when (val value = bundle.get(key)) {
|
|
114
|
+
is Bundle -> map[key] = bundleToMap(value)
|
|
115
|
+
else -> map[key] = value
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return map
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
package net.siteed.audiostudio.integration
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.media.MediaExtractor
|
|
6
|
+
import android.media.MediaFormat
|
|
7
|
+
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
8
|
+
import androidx.test.platform.app.InstrumentationRegistry
|
|
9
|
+
import androidx.test.rule.GrantPermissionRule
|
|
10
|
+
import expo.modules.kotlin.Promise
|
|
11
|
+
import net.siteed.audiostudio.*
|
|
12
|
+
import org.junit.After
|
|
13
|
+
import org.junit.Assert.*
|
|
14
|
+
import org.junit.Before
|
|
15
|
+
import org.junit.Rule
|
|
16
|
+
import org.junit.Test
|
|
17
|
+
import org.junit.runner.RunWith
|
|
18
|
+
import java.io.File
|
|
19
|
+
import java.util.concurrent.CountDownLatch
|
|
20
|
+
import java.util.concurrent.TimeUnit
|
|
21
|
+
|
|
22
|
+
@RunWith(AndroidJUnit4::class)
|
|
23
|
+
class M4aFormatTest {
|
|
24
|
+
|
|
25
|
+
@get:Rule
|
|
26
|
+
val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
|
|
27
|
+
Manifest.permission.RECORD_AUDIO
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
private lateinit var context: Context
|
|
31
|
+
private lateinit var filesDir: File
|
|
32
|
+
private lateinit var audioRecorderManager: AudioRecorderManager
|
|
33
|
+
private lateinit var testEventSender: TestEventSender
|
|
34
|
+
private lateinit var permissionUtils: PermissionUtils
|
|
35
|
+
private lateinit var audioDataEncoder: AudioDataEncoder
|
|
36
|
+
|
|
37
|
+
// Test event sender to capture events
|
|
38
|
+
private class TestEventSender : EventSender {
|
|
39
|
+
override fun sendExpoEvent(eventName: String, params: android.os.Bundle) {
|
|
40
|
+
// No-op for tests
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Before
|
|
45
|
+
fun setUp() {
|
|
46
|
+
context = InstrumentationRegistry.getInstrumentation().targetContext
|
|
47
|
+
filesDir = context.filesDir
|
|
48
|
+
testEventSender = TestEventSender()
|
|
49
|
+
permissionUtils = PermissionUtils(context)
|
|
50
|
+
audioDataEncoder = AudioDataEncoder()
|
|
51
|
+
|
|
52
|
+
// Initialize AudioRecorderManager
|
|
53
|
+
audioRecorderManager = AudioRecorderManager.initialize(
|
|
54
|
+
context = context,
|
|
55
|
+
filesDir = filesDir,
|
|
56
|
+
permissionUtils = permissionUtils,
|
|
57
|
+
audioDataEncoder = audioDataEncoder,
|
|
58
|
+
eventSender = testEventSender,
|
|
59
|
+
enablePhoneStateHandling = false,
|
|
60
|
+
enableBackgroundAudio = false
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
// Clean up any existing audio files
|
|
64
|
+
cleanupAudioFiles()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@After
|
|
68
|
+
fun tearDown() {
|
|
69
|
+
// Stop any ongoing recording
|
|
70
|
+
if (audioRecorderManager.isRecording) {
|
|
71
|
+
stopRecordingSync()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Clean up
|
|
75
|
+
AudioRecorderManager.destroy()
|
|
76
|
+
cleanupAudioFiles()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private fun cleanupAudioFiles() {
|
|
80
|
+
filesDir.listFiles()?.forEach { file ->
|
|
81
|
+
if (file.name.endsWith(".wav") || file.name.endsWith(".aac") ||
|
|
82
|
+
file.name.endsWith(".m4a") || file.name.endsWith(".opus")) {
|
|
83
|
+
file.delete()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@Test
|
|
89
|
+
fun testAacFormat_producesM4aByDefault() {
|
|
90
|
+
// Skip test if API level is too low for compressed recording
|
|
91
|
+
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) {
|
|
92
|
+
println("Skipping M4A test - requires API 29+, current API: ${android.os.Build.VERSION.SDK_INT}")
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Given
|
|
97
|
+
val recordingOptions = mapOf(
|
|
98
|
+
"sampleRate" to 44100,
|
|
99
|
+
"channels" to 1,
|
|
100
|
+
"encoding" to "pcm_16bit",
|
|
101
|
+
"interval" to 100,
|
|
102
|
+
"showNotification" to false,
|
|
103
|
+
"output" to mapOf(
|
|
104
|
+
"primary" to mapOf("enabled" to false),
|
|
105
|
+
"compressed" to mapOf(
|
|
106
|
+
"enabled" to true,
|
|
107
|
+
"format" to "aac"
|
|
108
|
+
// preferRawStream not specified = defaults to false = M4A
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
// When - Record for 1 second
|
|
114
|
+
startRecordingSync(recordingOptions)
|
|
115
|
+
Thread.sleep(1000)
|
|
116
|
+
val result = stopRecordingSync()
|
|
117
|
+
|
|
118
|
+
// Then
|
|
119
|
+
val compression = when (val comp = result["compression"]) {
|
|
120
|
+
is android.os.Bundle -> bundleToMap(comp)
|
|
121
|
+
is Map<*, *> -> comp
|
|
122
|
+
else -> null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
val compressedUri = compression?.get("compressedFileUri") as? String
|
|
126
|
+
assertNotNull("Compressed file URI should not be null", compressedUri)
|
|
127
|
+
|
|
128
|
+
val file = when {
|
|
129
|
+
compressedUri!!.startsWith("file://") -> File(java.net.URI(compressedUri))
|
|
130
|
+
compressedUri.startsWith("file:") -> File(java.net.URI(compressedUri))
|
|
131
|
+
else -> File(compressedUri)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
assertTrue("File should exist", file.exists())
|
|
135
|
+
assertTrue("File should have .m4a extension", file.name.endsWith(".m4a"))
|
|
136
|
+
|
|
137
|
+
// Verify it's actually an M4A file
|
|
138
|
+
verifyM4aFormat(file)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@Test
|
|
142
|
+
fun testAacFormat_withPreferRawStream_producesAac() {
|
|
143
|
+
// Skip test if API level is too low for compressed recording
|
|
144
|
+
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) {
|
|
145
|
+
println("Skipping raw AAC test - requires API 29+, current API: ${android.os.Build.VERSION.SDK_INT}")
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Given
|
|
150
|
+
val recordingOptions = mapOf(
|
|
151
|
+
"sampleRate" to 44100,
|
|
152
|
+
"channels" to 1,
|
|
153
|
+
"encoding" to "pcm_16bit",
|
|
154
|
+
"interval" to 100,
|
|
155
|
+
"showNotification" to false,
|
|
156
|
+
"output" to mapOf(
|
|
157
|
+
"primary" to mapOf("enabled" to false),
|
|
158
|
+
"compressed" to mapOf(
|
|
159
|
+
"enabled" to true,
|
|
160
|
+
"format" to "aac",
|
|
161
|
+
"preferRawStream" to true // NEW: Request raw AAC stream
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
// When - Record for 1 second
|
|
167
|
+
startRecordingSync(recordingOptions)
|
|
168
|
+
Thread.sleep(1000)
|
|
169
|
+
val result = stopRecordingSync()
|
|
170
|
+
|
|
171
|
+
// Then
|
|
172
|
+
val compression = when (val comp = result["compression"]) {
|
|
173
|
+
is android.os.Bundle -> bundleToMap(comp)
|
|
174
|
+
is Map<*, *> -> comp
|
|
175
|
+
else -> null
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
val compressedUri = compression?.get("compressedFileUri") as? String
|
|
179
|
+
assertNotNull("Compressed file URI should not be null", compressedUri)
|
|
180
|
+
|
|
181
|
+
val file = when {
|
|
182
|
+
compressedUri!!.startsWith("file://") -> File(java.net.URI(compressedUri))
|
|
183
|
+
compressedUri.startsWith("file:") -> File(java.net.URI(compressedUri))
|
|
184
|
+
else -> File(compressedUri)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
assertTrue("File should exist", file.exists())
|
|
188
|
+
assertTrue("File should have .aac extension", file.name.endsWith(".aac"))
|
|
189
|
+
|
|
190
|
+
// Verify it's actually an AAC ADTS file
|
|
191
|
+
verifyAacAdtsFormat(file)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@Test
|
|
195
|
+
fun testOpusFormat_producesOpus() {
|
|
196
|
+
// Skip test if API level is too low for Opus recording
|
|
197
|
+
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) {
|
|
198
|
+
println("Skipping Opus test - requires API 29+, current API: ${android.os.Build.VERSION.SDK_INT}")
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Given
|
|
203
|
+
val recordingOptions = mapOf(
|
|
204
|
+
"sampleRate" to 48000,
|
|
205
|
+
"channels" to 1,
|
|
206
|
+
"encoding" to "pcm_16bit",
|
|
207
|
+
"interval" to 100,
|
|
208
|
+
"showNotification" to false,
|
|
209
|
+
"output" to mapOf(
|
|
210
|
+
"primary" to mapOf("enabled" to false),
|
|
211
|
+
"compressed" to mapOf(
|
|
212
|
+
"enabled" to true,
|
|
213
|
+
"format" to "opus"
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
// When - Record for 1 second
|
|
219
|
+
startRecordingSync(recordingOptions)
|
|
220
|
+
Thread.sleep(1000)
|
|
221
|
+
val result = stopRecordingSync()
|
|
222
|
+
|
|
223
|
+
// Then
|
|
224
|
+
val compression = when (val comp = result["compression"]) {
|
|
225
|
+
is android.os.Bundle -> bundleToMap(comp)
|
|
226
|
+
is Map<*, *> -> comp
|
|
227
|
+
else -> null
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
val compressedUri = compression?.get("compressedFileUri") as? String
|
|
231
|
+
assertNotNull("Compressed file URI should not be null", compressedUri)
|
|
232
|
+
|
|
233
|
+
val file = when {
|
|
234
|
+
compressedUri!!.startsWith("file://") -> File(java.net.URI(compressedUri))
|
|
235
|
+
compressedUri.startsWith("file:") -> File(java.net.URI(compressedUri))
|
|
236
|
+
else -> File(compressedUri)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
assertTrue("File should exist", file.exists())
|
|
240
|
+
assertTrue("File should have .opus extension", file.name.endsWith(".opus"))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Helper methods from existing tests
|
|
244
|
+
private fun startRecordingSync(recordingOptions: Map<String, Any?>): Map<String, Any?> {
|
|
245
|
+
val startLatch = CountDownLatch(1)
|
|
246
|
+
var recordingResult: Map<String, Any?>? = null
|
|
247
|
+
|
|
248
|
+
audioRecorderManager.startRecording(recordingOptions, object : Promise {
|
|
249
|
+
override fun resolve(value: Any?) {
|
|
250
|
+
when (value) {
|
|
251
|
+
is android.os.Bundle -> recordingResult = bundleToMap(value)
|
|
252
|
+
is Map<*, *> -> {
|
|
253
|
+
@Suppress("UNCHECKED_CAST")
|
|
254
|
+
recordingResult = value as? Map<String, Any>
|
|
255
|
+
}
|
|
256
|
+
else -> {
|
|
257
|
+
fail("Unexpected start result type: ${value?.javaClass?.name}")
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
startLatch.countDown()
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
override fun reject(code: String, message: String?, cause: Throwable?) {
|
|
264
|
+
fail("Recording start failed: $code - $message")
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
assertTrue("Recording should start within 2 seconds", startLatch.await(2, TimeUnit.SECONDS))
|
|
269
|
+
return recordingResult ?: throw AssertionError("Recording result should not be null")
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private fun stopRecordingSync(): Map<String, Any?> {
|
|
273
|
+
val stopLatch = CountDownLatch(1)
|
|
274
|
+
var stopResult: Map<String, Any?>? = null
|
|
275
|
+
|
|
276
|
+
audioRecorderManager.stopRecording(object : Promise {
|
|
277
|
+
override fun resolve(value: Any?) {
|
|
278
|
+
when (value) {
|
|
279
|
+
is android.os.Bundle -> stopResult = bundleToMap(value)
|
|
280
|
+
is Map<*, *> -> {
|
|
281
|
+
@Suppress("UNCHECKED_CAST")
|
|
282
|
+
stopResult = value as? Map<String, Any>
|
|
283
|
+
}
|
|
284
|
+
else -> {
|
|
285
|
+
fail("Unexpected stop result type: ${value?.javaClass?.name}")
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
stopLatch.countDown()
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
override fun reject(code: String, message: String?, cause: Throwable?) {
|
|
292
|
+
fail("Recording stop failed: $code - $message")
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
assertTrue("Recording should stop within 2 seconds", stopLatch.await(2, TimeUnit.SECONDS))
|
|
297
|
+
return stopResult ?: throw AssertionError("Stop result should not be null")
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private fun bundleToMap(bundle: android.os.Bundle): Map<String, Any?> {
|
|
301
|
+
val map = mutableMapOf<String, Any?>()
|
|
302
|
+
for (key in bundle.keySet()) {
|
|
303
|
+
map[key] = bundle.get(key)
|
|
304
|
+
}
|
|
305
|
+
return map
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private fun verifyM4aFormat(file: File) {
|
|
309
|
+
val extractor = MediaExtractor()
|
|
310
|
+
try {
|
|
311
|
+
extractor.setDataSource(file.absolutePath)
|
|
312
|
+
assertTrue("Should have at least one track", extractor.trackCount > 0)
|
|
313
|
+
|
|
314
|
+
val format = extractor.getTrackFormat(0)
|
|
315
|
+
val mimeType = format.getString(MediaFormat.KEY_MIME)
|
|
316
|
+
|
|
317
|
+
// Debug output
|
|
318
|
+
println("Detected MIME type: $mimeType")
|
|
319
|
+
|
|
320
|
+
// For M4A files, the MIME type should be audio/mp4 or contain aac
|
|
321
|
+
val isValidM4aMimeType = mimeType?.let { mime ->
|
|
322
|
+
mime.contains("mp4", ignoreCase = true) ||
|
|
323
|
+
mime.contains("aac", ignoreCase = true) ||
|
|
324
|
+
mime.contains("audio/", ignoreCase = true)
|
|
325
|
+
} ?: false
|
|
326
|
+
|
|
327
|
+
assertTrue("MIME type should be valid for M4A format, got: $mimeType", isValidM4aMimeType)
|
|
328
|
+
|
|
329
|
+
// Read file header to verify MP4 container
|
|
330
|
+
val header = file.inputStream().use { it.readNBytes(20) }
|
|
331
|
+
val headerString = String(header, Charsets.ISO_8859_1)
|
|
332
|
+
val hasFtyp = headerString.contains("ftyp")
|
|
333
|
+
assertTrue("File should contain ftyp box (MP4 container)", hasFtyp)
|
|
334
|
+
} finally {
|
|
335
|
+
extractor.release()
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private fun verifyAacAdtsFormat(file: File) {
|
|
340
|
+
// ADTS header starts with 0xFFF
|
|
341
|
+
val header = file.inputStream().use { it.readNBytes(2) }
|
|
342
|
+
val syncWord = ((header[0].toInt() and 0xFF) shl 4) or ((header[1].toInt() and 0xF0) shr 4)
|
|
343
|
+
assertEquals("ADTS sync word should be 0xFFF", 0xFFF, syncWord)
|
|
344
|
+
}
|
|
345
|
+
}
|