@siteed/audio-studio 3.2.1-beta.0 → 3.2.1-beta.2

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 (64) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/README.md +41 -1
  3. package/android/src/main/java/net/siteed/audiostudio/AudioRecorderManager.kt +130 -0
  4. package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +1 -0
  5. package/android/src/main/java/net/siteed/audiostudio/Constants.kt +2 -1
  6. package/android/src/main/java/net/siteed/audiostudio/RecordingConfig.kt +5 -1
  7. package/build/cjs/AudioStudio.types.js.map +1 -1
  8. package/build/cjs/AudioStudio.web.js +125 -13
  9. package/build/cjs/AudioStudio.web.js.map +1 -1
  10. package/build/cjs/AudioStudioModule.js +6 -1
  11. package/build/cjs/AudioStudioModule.js.map +1 -1
  12. package/build/cjs/events.js +4 -0
  13. package/build/cjs/events.js.map +1 -1
  14. package/build/cjs/index.js +3 -1
  15. package/build/cjs/index.js.map +1 -1
  16. package/build/cjs/useAudioRecorder.js +187 -30
  17. package/build/cjs/useAudioRecorder.js.map +1 -1
  18. package/build/cjs/utils/nativeRecordingOptions.js +13 -0
  19. package/build/cjs/utils/nativeRecordingOptions.js.map +1 -0
  20. package/build/cjs/utils/nativeRecordingOptions.test.js +30 -0
  21. package/build/cjs/utils/nativeRecordingOptions.test.js.map +1 -0
  22. package/build/esm/AudioStudio.types.js.map +1 -1
  23. package/build/esm/AudioStudio.web.js +125 -13
  24. package/build/esm/AudioStudio.web.js.map +1 -1
  25. package/build/esm/AudioStudioModule.js +6 -1
  26. package/build/esm/AudioStudioModule.js.map +1 -1
  27. package/build/esm/events.js +3 -0
  28. package/build/esm/events.js.map +1 -1
  29. package/build/esm/index.js +1 -0
  30. package/build/esm/index.js.map +1 -1
  31. package/build/esm/useAudioRecorder.js +188 -31
  32. package/build/esm/useAudioRecorder.js.map +1 -1
  33. package/build/esm/utils/nativeRecordingOptions.js +10 -0
  34. package/build/esm/utils/nativeRecordingOptions.js.map +1 -0
  35. package/build/esm/utils/nativeRecordingOptions.test.js +28 -0
  36. package/build/esm/utils/nativeRecordingOptions.test.js.map +1 -0
  37. package/build/types/AudioStudio.types.d.ts +58 -1
  38. package/build/types/AudioStudio.types.d.ts.map +1 -1
  39. package/build/types/AudioStudio.web.d.ts +17 -1
  40. package/build/types/AudioStudio.web.d.ts.map +1 -1
  41. package/build/types/AudioStudioModule.d.ts.map +1 -1
  42. package/build/types/events.d.ts +2 -1
  43. package/build/types/events.d.ts.map +1 -1
  44. package/build/types/index.d.ts +1 -0
  45. package/build/types/index.d.ts.map +1 -1
  46. package/build/types/useAudioRecorder.d.ts +4 -1
  47. package/build/types/useAudioRecorder.d.ts.map +1 -1
  48. package/build/types/utils/nativeRecordingOptions.d.ts +28 -0
  49. package/build/types/utils/nativeRecordingOptions.d.ts.map +1 -0
  50. package/build/types/utils/nativeRecordingOptions.test.d.ts +2 -0
  51. package/build/types/utils/nativeRecordingOptions.test.d.ts.map +1 -0
  52. package/ios/AudioStreamManager.swift +103 -9
  53. package/ios/AudioStreamManagerDelegate.swift +1 -0
  54. package/ios/AudioStudioModule.swift +6 -0
  55. package/ios/RecordingSettings.swift +48 -43
  56. package/package.json +1 -1
  57. package/src/AudioStudio.types.ts +70 -1
  58. package/src/AudioStudio.web.ts +152 -13
  59. package/src/AudioStudioModule.ts +6 -1
  60. package/src/events.ts +13 -1
  61. package/src/index.ts +1 -0
  62. package/src/useAudioRecorder.tsx +260 -45
  63. package/src/utils/nativeRecordingOptions.test.ts +29 -0
  64. package/src/utils/nativeRecordingOptions.ts +20 -0
@@ -18,4 +18,7 @@ export function addRecordingInterruptionListener(listener) {
18
18
  });
19
19
  return subscription;
20
20
  }
21
+ export function addMaxDurationReachedListener(listener) {
22
+ return emitter.addListener('MaxDurationReached', listener);
23
+ }
21
24
  //# sourceMappingURL=events.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/events.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC,OAAO,EAAE,kBAAkB,EAA0B,MAAM,mBAAmB,CAAA;AAI9E,OAAO,iBAAiB,MAAM,qBAAqB,CAAA;AAEnD,MAAM,OAAO,GAAG,IAAI,kBAAkB,CAAC,iBAAiB,CAAC,CAAA;AAwBzD,MAAM,UAAU,qBAAqB,CACjC,QAAqD;IAErD,OAAO,OAAO,CAAC,WAAW,CAAoB,WAAW,EAAE,QAAQ,CAAC,CAAA;AACxE,CAAC;AAKD,MAAM,UAAU,wBAAwB,CACpC,QAAsD;IAEtD,OAAO,OAAO,CAAC,WAAW,CAAqB,eAAe,EAAE,QAAQ,CAAC,CAAA;AAC7E,CAAC;AAED,MAAM,UAAU,gCAAgC,CAC5C,QAAqD;IAErD,oBAAoB;IACpB,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAA;IAEvD,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,CACpC,wBAAwB,EAAE,+CAA+C;IACzE,CAAC,KAAK,EAAE,EAAE;QACN,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAA;QAC9D,QAAQ,CAAC,KAAK,CAAC,CAAA;IACnB,CAAC,CACJ,CAAA;IAED,OAAO,YAAY,CAAA;AACvB,CAAC","sourcesContent":["// packages/audio-studio/src/events.ts\n\nimport { LegacyEventEmitter, type EventSubscription } from 'expo-modules-core'\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport { RecordingInterruptionEvent } from './AudioStudio.types'\nimport AudioStudioModule from './AudioStudioModule'\n\nconst emitter = new LegacyEventEmitter(AudioStudioModule)\n\n// Internal event payload from native module\nexport interface AudioEventPayload {\n encoded?: string\n /** Float32 samples in [-1, 1] — sent by native when streamFormat='float32'.\n * Android new arch delivers Float32Array; iOS delivers number[]. */\n pcmFloat32?: Float32Array | number[]\n buffer?: Float32Array\n fileUri: string\n lastEmittedSize: number\n position: number\n deltaSize: number\n totalSize: number\n mimeType: string\n streamUuid: string\n compression?: {\n data?: string | Blob // Base64 (native) or Float32Array (web) encoded compressed data chunk\n position: number\n eventDataSize: number\n totalSize: number\n }\n}\n\nexport function addAudioEventListener(\n listener: (event: AudioEventPayload) => Promise<void>\n): EventSubscription {\n return emitter.addListener<AudioEventPayload>('AudioData', listener)\n}\n\n// Only aliasing the AudioAnalysis type for the event payload\nexport interface AudioAnalysisEvent extends AudioAnalysis {}\n\nexport function addAudioAnalysisListener(\n listener: (event: AudioAnalysisEvent) => Promise<void>\n): EventSubscription {\n return emitter.addListener<AudioAnalysisEvent>('AudioAnalysis', listener)\n}\n\nexport function addRecordingInterruptionListener(\n listener: (event: RecordingInterruptionEvent) => void\n): EventSubscription {\n // Add debug logging\n console.debug('Adding recording interruption listener')\n\n const subscription = emitter.addListener<RecordingInterruptionEvent>(\n 'onRecordingInterrupted', // Make sure this matches the native event name\n (event) => {\n console.debug('Recording interruption event received:', event)\n listener(event)\n }\n )\n\n return subscription\n}\n"]}
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/events.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC,OAAO,EAAE,kBAAkB,EAA0B,MAAM,mBAAmB,CAAA;AAO9E,OAAO,iBAAiB,MAAM,qBAAqB,CAAA;AAEnD,MAAM,OAAO,GAAG,IAAI,kBAAkB,CAAC,iBAAiB,CAAC,CAAA;AAwBzD,MAAM,UAAU,qBAAqB,CACjC,QAAqD;IAErD,OAAO,OAAO,CAAC,WAAW,CAAoB,WAAW,EAAE,QAAQ,CAAC,CAAA;AACxE,CAAC;AAKD,MAAM,UAAU,wBAAwB,CACpC,QAAsD;IAEtD,OAAO,OAAO,CAAC,WAAW,CAAqB,eAAe,EAAE,QAAQ,CAAC,CAAA;AAC7E,CAAC;AAED,MAAM,UAAU,gCAAgC,CAC5C,QAAqD;IAErD,oBAAoB;IACpB,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAA;IAEvD,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,CACpC,wBAAwB,EAAE,+CAA+C;IACzE,CAAC,KAAK,EAAE,EAAE;QACN,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAA;QAC9D,QAAQ,CAAC,KAAK,CAAC,CAAA;IACnB,CAAC,CACJ,CAAA;IAED,OAAO,YAAY,CAAA;AACvB,CAAC;AAED,MAAM,UAAU,6BAA6B,CACzC,QAAkD;IAElD,OAAO,OAAO,CAAC,WAAW,CACtB,oBAAoB,EACpB,QAAQ,CACX,CAAA;AACL,CAAC","sourcesContent":["// packages/audio-studio/src/events.ts\n\nimport { LegacyEventEmitter, type EventSubscription } from 'expo-modules-core'\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport type {\n MaxDurationReachedEvent,\n RecordingInterruptionEvent,\n} from './AudioStudio.types'\nimport AudioStudioModule from './AudioStudioModule'\n\nconst emitter = new LegacyEventEmitter(AudioStudioModule)\n\n// Internal event payload from native module\nexport interface AudioEventPayload {\n encoded?: string\n /** Float32 samples in [-1, 1] — sent by native when streamFormat='float32'.\n * Android new arch delivers Float32Array; iOS delivers number[]. */\n pcmFloat32?: Float32Array | number[]\n buffer?: Float32Array\n fileUri: string\n lastEmittedSize: number\n position: number\n deltaSize: number\n totalSize: number\n mimeType: string\n streamUuid: string\n compression?: {\n data?: string | Blob // Base64 (native) or Float32Array (web) encoded compressed data chunk\n position: number\n eventDataSize: number\n totalSize: number\n }\n}\n\nexport function addAudioEventListener(\n listener: (event: AudioEventPayload) => Promise<void>\n): EventSubscription {\n return emitter.addListener<AudioEventPayload>('AudioData', listener)\n}\n\n// Only aliasing the AudioAnalysis type for the event payload\nexport interface AudioAnalysisEvent extends AudioAnalysis {}\n\nexport function addAudioAnalysisListener(\n listener: (event: AudioAnalysisEvent) => Promise<void>\n): EventSubscription {\n return emitter.addListener<AudioAnalysisEvent>('AudioAnalysis', listener)\n}\n\nexport function addRecordingInterruptionListener(\n listener: (event: RecordingInterruptionEvent) => void\n): EventSubscription {\n // Add debug logging\n console.debug('Adding recording interruption listener')\n\n const subscription = emitter.addListener<RecordingInterruptionEvent>(\n 'onRecordingInterrupted', // Make sure this matches the native event name\n (event) => {\n console.debug('Recording interruption event received:', event)\n listener(event)\n }\n )\n\n return subscription\n}\n\nexport function addMaxDurationReachedListener(\n listener: (event: MaxDurationReachedEvent) => void\n): EventSubscription {\n return emitter.addListener<MaxDurationReachedEvent>(\n 'MaxDurationReached',\n listener\n )\n}\n"]}
@@ -9,6 +9,7 @@ import AudioStudioModule from './AudioStudioModule';
9
9
  import { getAudioDecodeCapabilities, streamAudioData, } from './streamAudioData';
10
10
  import { trimAudio } from './trimAudio';
11
11
  import { useAudioRecorder } from './useAudioRecorder';
12
+ export { addMaxDurationReachedListener } from './events';
12
13
  export * from './utils/convertPCMToFloat32';
13
14
  export * from './utils/getWavFileInfo';
14
15
  export * from './utils/writeWavHeader';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,eAAe;AAEf,OAAO,EACH,qBAAqB,EACrB,oBAAoB,GACvB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,EACH,qBAAqB,EACrB,eAAe,GAClB,MAAM,uCAAuC,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAC/D,OAAO,EACH,oBAAoB,EACpB,mBAAmB,GACtB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EACH,qBAAqB,EACrB,sBAAsB,GACzB,MAAM,0BAA0B,CAAA;AACjC,OAAO,iBAAiB,MAAM,qBAAqB,CAAA;AACnD,OAAO,EACH,0BAA0B,EAC1B,eAAe,GAClB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAErD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAEtC,+BAA+B;AAC/B,OAAO,EACH,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,GAE1B,MAAM,iCAAiC,CAAA;AAExC,4BAA4B;AAC5B,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE7E,8BAA8B;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAEzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAEvE,OAAO,EACH,qBAAqB,EACrB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,eAAe,EACf,0BAA0B,EAC1B,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,sBAAsB,GACzB,CAAA;AAED,OAAO,EACH,oBAAoB,EACpB,kBAAkB,GACrB,MAAM,+BAA+B,CAAA;AAMtC,OAAO,EACH,gBAAgB,EAChB,cAAc,GACjB,MAAM,2BAA2B,CAAA;AAmBlC,gDAAgD;AAChD,MAAM,CAAC,MAAM,qBAAqB,GAAG,iBAAiB,CAAA","sourcesContent":["// src/index.ts\n\nimport {\n extractRawWavAnalysis,\n extractAudioAnalysis,\n} from './AudioAnalysis/extractAudioAnalysis'\nimport { extractAudioData } from './AudioAnalysis/extractAudioData'\nimport {\n extractMelSpectrogram,\n MAX_DURATION_MS,\n} from './AudioAnalysis/extractMelSpectrogram'\nimport { extractPreview } from './AudioAnalysis/extractPreview'\nimport {\n initMelStreamingWasm,\n computeMelFrameWasm,\n} from './AudioAnalysis/melSpectrogramWasm'\nimport {\n AudioRecorderProvider,\n useSharedAudioRecorder,\n} from './AudioRecorder.provider'\nimport AudioStudioModule from './AudioStudioModule'\nimport {\n getAudioDecodeCapabilities,\n streamAudioData,\n} from './streamAudioData'\nimport { trimAudio } from './trimAudio'\nimport { useAudioRecorder } from './useAudioRecorder'\n\nexport * from './utils/convertPCMToFloat32'\nexport * from './utils/getWavFileInfo'\nexport * from './utils/writeWavHeader'\n\n// Export platform capabilities\nexport {\n getPlatformCapabilities,\n isEncodingSupported,\n isBitDepthSupported,\n getFallbackEncoding,\n getFallbackBitDepth,\n validateRecordingConfig,\n type PlatformCapabilities,\n} from './constants/platformLimitations'\n\n// Export AudioDeviceManager\nexport { AudioDeviceManager, audioDeviceManager } from './AudioDeviceManager'\n\n// Export useAudioDevices hook\nexport { useAudioDevices } from './hooks/useAudioDevices'\n\nexport { setMelSpectrogramWasmUrl } from './AudioAnalysis/wasmConfig'\nexport { extractPreviewBars } from './AudioAnalysis/extractPreviewBars'\n\nexport {\n AudioRecorderProvider,\n AudioStudioModule,\n extractRawWavAnalysis,\n extractAudioAnalysis,\n extractPreview,\n trimAudio,\n extractAudioData,\n streamAudioData,\n getAudioDecodeCapabilities,\n extractMelSpectrogram,\n initMelStreamingWasm,\n computeMelFrameWasm,\n MAX_DURATION_MS,\n useAudioRecorder,\n useSharedAudioRecorder,\n}\n\nexport {\n AudioExtractionError,\n mapExtractionError,\n} from './errors/AudioExtractionError'\nexport type {\n AudioExtractionErrorCode,\n AudioExtractionErrorPayload,\n} from './errors/AudioExtractionError'\n\nexport {\n AudioStreamError,\n mapStreamError,\n} from './errors/AudioStreamError'\nexport type {\n AudioStreamErrorCode,\n AudioStreamErrorPayload,\n} from './errors/AudioStreamError'\n\nexport type {\n StreamAudioDataOptions,\n StreamAudioDataChunk,\n StreamAudioDataProgress,\n StreamAudioDataResult,\n StreamAudioDataCallbacks,\n AudioDecodeCapabilities,\n} from './streamAudioData'\n\n// Export all types\nexport type * from './AudioAnalysis/AudioAnalysis.types'\nexport type * from './AudioStudio.types'\n\n/** @deprecated Use AudioStudioModule instead */\nexport const ExpoAudioStreamModule = AudioStudioModule\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,eAAe;AAEf,OAAO,EACH,qBAAqB,EACrB,oBAAoB,GACvB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,EACH,qBAAqB,EACrB,eAAe,GAClB,MAAM,uCAAuC,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAC/D,OAAO,EACH,oBAAoB,EACpB,mBAAmB,GACtB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EACH,qBAAqB,EACrB,sBAAsB,GACzB,MAAM,0BAA0B,CAAA;AACjC,OAAO,iBAAiB,MAAM,qBAAqB,CAAA;AACnD,OAAO,EACH,0BAA0B,EAC1B,eAAe,GAClB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAExD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAEtC,+BAA+B;AAC/B,OAAO,EACH,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,GAE1B,MAAM,iCAAiC,CAAA;AAExC,4BAA4B;AAC5B,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE7E,8BAA8B;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAEzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAEvE,OAAO,EACH,qBAAqB,EACrB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,eAAe,EACf,0BAA0B,EAC1B,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,sBAAsB,GACzB,CAAA;AAED,OAAO,EACH,oBAAoB,EACpB,kBAAkB,GACrB,MAAM,+BAA+B,CAAA;AAMtC,OAAO,EACH,gBAAgB,EAChB,cAAc,GACjB,MAAM,2BAA2B,CAAA;AAmBlC,gDAAgD;AAChD,MAAM,CAAC,MAAM,qBAAqB,GAAG,iBAAiB,CAAA","sourcesContent":["// src/index.ts\n\nimport {\n extractRawWavAnalysis,\n extractAudioAnalysis,\n} from './AudioAnalysis/extractAudioAnalysis'\nimport { extractAudioData } from './AudioAnalysis/extractAudioData'\nimport {\n extractMelSpectrogram,\n MAX_DURATION_MS,\n} from './AudioAnalysis/extractMelSpectrogram'\nimport { extractPreview } from './AudioAnalysis/extractPreview'\nimport {\n initMelStreamingWasm,\n computeMelFrameWasm,\n} from './AudioAnalysis/melSpectrogramWasm'\nimport {\n AudioRecorderProvider,\n useSharedAudioRecorder,\n} from './AudioRecorder.provider'\nimport AudioStudioModule from './AudioStudioModule'\nimport {\n getAudioDecodeCapabilities,\n streamAudioData,\n} from './streamAudioData'\nimport { trimAudio } from './trimAudio'\nimport { useAudioRecorder } from './useAudioRecorder'\nexport { addMaxDurationReachedListener } from './events'\n\nexport * from './utils/convertPCMToFloat32'\nexport * from './utils/getWavFileInfo'\nexport * from './utils/writeWavHeader'\n\n// Export platform capabilities\nexport {\n getPlatformCapabilities,\n isEncodingSupported,\n isBitDepthSupported,\n getFallbackEncoding,\n getFallbackBitDepth,\n validateRecordingConfig,\n type PlatformCapabilities,\n} from './constants/platformLimitations'\n\n// Export AudioDeviceManager\nexport { AudioDeviceManager, audioDeviceManager } from './AudioDeviceManager'\n\n// Export useAudioDevices hook\nexport { useAudioDevices } from './hooks/useAudioDevices'\n\nexport { setMelSpectrogramWasmUrl } from './AudioAnalysis/wasmConfig'\nexport { extractPreviewBars } from './AudioAnalysis/extractPreviewBars'\n\nexport {\n AudioRecorderProvider,\n AudioStudioModule,\n extractRawWavAnalysis,\n extractAudioAnalysis,\n extractPreview,\n trimAudio,\n extractAudioData,\n streamAudioData,\n getAudioDecodeCapabilities,\n extractMelSpectrogram,\n initMelStreamingWasm,\n computeMelFrameWasm,\n MAX_DURATION_MS,\n useAudioRecorder,\n useSharedAudioRecorder,\n}\n\nexport {\n AudioExtractionError,\n mapExtractionError,\n} from './errors/AudioExtractionError'\nexport type {\n AudioExtractionErrorCode,\n AudioExtractionErrorPayload,\n} from './errors/AudioExtractionError'\n\nexport {\n AudioStreamError,\n mapStreamError,\n} from './errors/AudioStreamError'\nexport type {\n AudioStreamErrorCode,\n AudioStreamErrorPayload,\n} from './errors/AudioStreamError'\n\nexport type {\n StreamAudioDataOptions,\n StreamAudioDataChunk,\n StreamAudioDataProgress,\n StreamAudioDataResult,\n StreamAudioDataCallbacks,\n AudioDecodeCapabilities,\n} from './streamAudioData'\n\n// Export all types\nexport type * from './AudioAnalysis/AudioAnalysis.types'\nexport type * from './AudioStudio.types'\n\n/** @deprecated Use AudioStudioModule instead */\nexport const ExpoAudioStreamModule = AudioStudioModule\n"]}
@@ -4,8 +4,8 @@ import { useCallback, useEffect, useReducer, useRef, useId } from 'react';
4
4
  import { audioDeviceManager } from './AudioDeviceManager';
5
5
  import AudioStudioModule from './AudioStudioModule';
6
6
  import { validateRecordingConfig } from './constants/platformLimitations';
7
- import { addAudioAnalysisListener, addAudioEventListener, addRecordingInterruptionListener, } from './events';
8
- import { cleanNativeOptions } from './utils/cleanNativeOptions';
7
+ import { addAudioAnalysisListener, addAudioEventListener, addMaxDurationReachedListener, addRecordingInterruptionListener, } from './events';
8
+ import { createNativeRecordingOptions } from './utils/nativeRecordingOptions';
9
9
  const defaultAnalysis = {
10
10
  segmentDurationMs: 100,
11
11
  bitDepth: 32,
@@ -24,6 +24,31 @@ const defaultAnalysis = {
24
24
  },
25
25
  extractionTimeMs: 0,
26
26
  };
27
+ function finiteOrZero(value) {
28
+ return Number.isFinite(value) ? value : 0;
29
+ }
30
+ function sanitizeSerializableValue(value) {
31
+ if (typeof value === 'number') {
32
+ return finiteOrZero(value);
33
+ }
34
+ if (Array.isArray(value)) {
35
+ return value.map((item) => sanitizeSerializableValue(item));
36
+ }
37
+ if (value && typeof value === 'object') {
38
+ const sanitized = {};
39
+ for (const [key, nestedValue] of Object.entries(value)) {
40
+ sanitized[key] = sanitizeSerializableValue(nestedValue);
41
+ }
42
+ return sanitized;
43
+ }
44
+ return value;
45
+ }
46
+ function createSerializableAnalysis(analysis) {
47
+ return sanitizeSerializableValue(analysis);
48
+ }
49
+ function createRecordingSnapshot(recording) {
50
+ return sanitizeSerializableValue(recording);
51
+ }
27
52
  function audioRecorderReducer(state, action) {
28
53
  switch (action.type) {
29
54
  case 'START':
@@ -35,6 +60,9 @@ function audioRecorderReducer(state, action) {
35
60
  size: 0,
36
61
  compression: undefined,
37
62
  analysisData: defaultAnalysis,
63
+ maxDurationMs: undefined,
64
+ maxDurationReached: false,
65
+ lastRecordingReason: undefined,
38
66
  };
39
67
  case 'STOP':
40
68
  return {
@@ -45,6 +73,9 @@ function audioRecorderReducer(state, action) {
45
73
  size: 0,
46
74
  compression: undefined,
47
75
  analysisData: undefined,
76
+ lastRecordingReason: action.payload.reason,
77
+ // Preserve max-duration state after stop so UI and agentic
78
+ // validation can explain why recording ended. START resets it.
48
79
  };
49
80
  case 'PAUSE':
50
81
  return { ...state, isPaused: true, isRecording: false };
@@ -69,9 +100,17 @@ function audioRecorderReducer(state, action) {
69
100
  format: action.payload.compression.format,
70
101
  }
71
102
  : undefined,
103
+ maxDurationMs: action.payload.maxDurationMs,
104
+ maxDurationReached: action.payload.maxDurationReached,
72
105
  };
73
106
  return newState;
74
107
  }
108
+ case 'MAX_DURATION_REACHED':
109
+ return {
110
+ ...state,
111
+ maxDurationMs: action.payload.maxDurationMs,
112
+ maxDurationReached: true,
113
+ };
75
114
  case 'UPDATE_ANALYSIS':
76
115
  return {
77
116
  ...state,
@@ -96,6 +135,9 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
96
135
  size: 0,
97
136
  compression: undefined,
98
137
  analysisData: undefined,
138
+ maxDurationMs: undefined,
139
+ maxDurationReached: false,
140
+ lastRecordingReason: undefined,
99
141
  });
100
142
  const startResultRef = useRef(null);
101
143
  const analysisListenerRef = useRef(null);
@@ -120,8 +162,12 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
120
162
  durationMs: 0,
121
163
  size: 0,
122
164
  compression: undefined,
165
+ maxDurationMs: undefined,
166
+ maxDurationReached: false,
123
167
  });
124
168
  const recordingConfigRef = useRef(null);
169
+ const maxDurationHandledRef = useRef(false);
170
+ const stopFinalizationRef = useRef(null);
125
171
  // Generate unique instance ID for debugging
126
172
  const instanceId = useId().replace(/:/g, '').slice(0, 5);
127
173
  const handleAudioAnalysis = useCallback(async ({ analysis, visualizationDuration, }) => {
@@ -287,10 +333,105 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
287
333
  logger?.error(`Error processing audio event:`, error);
288
334
  }
289
335
  }, []);
336
+ const finalizeRecordingStop = useCallback(async (reason) => {
337
+ if (stopFinalizationRef.current) {
338
+ return stopFinalizationRef.current;
339
+ }
340
+ const finalizePromise = (async () => {
341
+ const nativeStopResult = await audioStudio.stopRecording();
342
+ if (!nativeStopResult) {
343
+ throw new Error('Failed to stop recording');
344
+ }
345
+ const stopResult = createRecordingSnapshot(nativeStopResult);
346
+ if (shouldKeepFullAnalysis(recordingConfigRef.current)) {
347
+ stopResult.analysisData = createSerializableAnalysis(fullAnalysisRef.current);
348
+ }
349
+ else {
350
+ // `keepFullAnalysis` is a hook-level retention policy. If a platform
351
+ // starts returning native analysisData in the future, keep opt-out
352
+ // semantics explicit and avoid leaking a full history here.
353
+ delete stopResult.analysisData;
354
+ }
355
+ if (analysisListenerRef.current) {
356
+ analysisListenerRef.current.remove();
357
+ analysisListenerRef.current = null;
358
+ }
359
+ onAudioStreamRef.current = null;
360
+ stateRef.current.isRecording = false;
361
+ stateRef.current.isPaused = false;
362
+ // Note: We deliberately DON'T clear recordingConfigRef here to preserve callbacks.
363
+ logger?.debug(`recording stopped`, stopResult);
364
+ maxDurationHandledRef.current = false;
365
+ dispatch({
366
+ type: 'STOP',
367
+ payload: { reason },
368
+ });
369
+ const stoppedCallback = recordingConfigRef.current?.onRecordingStopped;
370
+ if (stoppedCallback) {
371
+ try {
372
+ void Promise.resolve(stoppedCallback(stopResult, reason)).catch((error) => {
373
+ logger?.error(`Error in recording stopped callback:`, error);
374
+ });
375
+ }
376
+ catch (error) {
377
+ logger?.error(`Error in recording stopped callback:`, error);
378
+ }
379
+ }
380
+ return stopResult;
381
+ })();
382
+ stopFinalizationRef.current = finalizePromise;
383
+ try {
384
+ return await finalizePromise;
385
+ }
386
+ finally {
387
+ stopFinalizationRef.current = null;
388
+ }
389
+ }, [audioStudio, dispatch, logger]);
390
+ const handleMaxDurationReached = useCallback(async (event) => {
391
+ if (maxDurationHandledRef.current) {
392
+ return;
393
+ }
394
+ maxDurationHandledRef.current = true;
395
+ const config = recordingConfigRef.current;
396
+ const callbackEvent = {
397
+ ...event,
398
+ autoStopped: event.autoStopped || !!config?.autoStopOnMaxDuration,
399
+ };
400
+ stateRef.current.maxDurationMs = callbackEvent.maxDurationMs;
401
+ stateRef.current.maxDurationReached = true;
402
+ dispatch({
403
+ type: 'MAX_DURATION_REACHED',
404
+ payload: callbackEvent,
405
+ });
406
+ try {
407
+ config?.onMaxDurationReached?.(callbackEvent);
408
+ }
409
+ catch (error) {
410
+ logger?.error(`Error in max duration callback:`, error);
411
+ }
412
+ if (config?.autoStopOnMaxDuration && stateRef.current.isRecording) {
413
+ try {
414
+ await finalizeRecordingStop('maxDuration');
415
+ }
416
+ catch (error) {
417
+ logger?.error(`Error auto-stopping on max duration:`, error);
418
+ }
419
+ }
420
+ }, [dispatch, finalizeRecordingStop, logger]);
290
421
  const checkStatus = useCallback(async () => {
291
422
  try {
292
423
  const status = audioStudio.status();
293
424
  logger?.debug(`Status: paused: ${status.isPaused} isRecording: ${status.isRecording} durationMs: ${status.durationMs} size: ${status.size}`, status.compression);
425
+ if (status.maxDurationReached === true &&
426
+ status.maxDurationMs != null &&
427
+ !stateRef.current.maxDurationReached) {
428
+ await handleMaxDurationReached({
429
+ durationMs: status.durationMs,
430
+ maxDurationMs: status.maxDurationMs,
431
+ overrunMs: Math.max(0, status.durationMs - status.maxDurationMs),
432
+ autoStopped: false,
433
+ });
434
+ }
294
435
  // Only dispatch if values actually changed
295
436
  if (status.isRecording !== stateRef.current.isRecording ||
296
437
  status.isPaused !== stateRef.current.isPaused) {
@@ -304,17 +445,34 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
304
445
  },
305
446
  });
306
447
  }
448
+ const statusMaxDurationReached = status.maxDurationReached ?? false;
449
+ const preserveStoppedMaxDuration = !status.isRecording &&
450
+ !status.isPaused &&
451
+ stateRef.current.maxDurationReached &&
452
+ !statusMaxDurationReached;
453
+ const nextMaxDurationMs = preserveStoppedMaxDuration
454
+ ? stateRef.current.maxDurationMs
455
+ : status.maxDurationMs;
456
+ const nextMaxDurationReached = preserveStoppedMaxDuration
457
+ ? true
458
+ : statusMaxDurationReached;
307
459
  if (status.durationMs !== stateRef.current.durationMs ||
308
- status.size !== stateRef.current.size) {
460
+ status.size !== stateRef.current.size ||
461
+ nextMaxDurationMs !== stateRef.current.maxDurationMs ||
462
+ nextMaxDurationReached !== stateRef.current.maxDurationReached) {
309
463
  stateRef.current.durationMs = status.durationMs;
310
464
  stateRef.current.size = status.size;
311
465
  stateRef.current.compression = status.compression;
466
+ stateRef.current.maxDurationMs = nextMaxDurationMs;
467
+ stateRef.current.maxDurationReached = nextMaxDurationReached;
312
468
  dispatch({
313
469
  type: 'UPDATE_STATUS',
314
470
  payload: {
315
471
  durationMs: status.durationMs,
316
472
  size: status.size,
317
473
  compression: status.compression,
474
+ maxDurationMs: nextMaxDurationMs,
475
+ maxDurationReached: nextMaxDurationReached,
318
476
  },
319
477
  });
320
478
  }
@@ -322,7 +480,7 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
322
480
  catch (error) {
323
481
  logger?.error(`Error getting status:`, error);
324
482
  }
325
- }, [audioStudio, logger]); // Only depend on audioStudio and logger
483
+ }, [audioStudio, handleMaxDurationReached, logger]);
326
484
  // Update ref when state changes
327
485
  useEffect(() => {
328
486
  stateRef.current = {
@@ -331,6 +489,8 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
331
489
  durationMs: state.durationMs,
332
490
  size: state.size,
333
491
  compression: state.compression,
492
+ maxDurationMs: state.maxDurationMs,
493
+ maxDurationReached: state.maxDurationReached ?? false,
334
494
  };
335
495
  }, [
336
496
  state.isRecording,
@@ -338,6 +498,8 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
338
498
  state.durationMs,
339
499
  state.size,
340
500
  state.compression,
501
+ state.maxDurationMs,
502
+ state.maxDurationReached,
341
503
  ]);
342
504
  const startRecording = useCallback(async (recordingOptions) => {
343
505
  // Validate the encoding configuration
@@ -356,11 +518,11 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
356
518
  encoding: validationResult.encoding,
357
519
  };
358
520
  recordingConfigRef.current = validatedOptions;
521
+ maxDurationHandledRef.current = false;
359
522
  logger?.debug(`start recording with validated config`, validatedOptions);
360
523
  analysisRef.current = { ...defaultAnalysis }; // Reset analysis data
361
524
  fullAnalysisRef.current = { ...defaultAnalysis };
362
- const { onAudioStream, onRecordingInterrupted, onAudioAnalysis, keepFullAnalysis: _keepFullAnalysis, ...options } = validatedOptions;
363
- const { enableProcessing } = options;
525
+ const { onAudioStream, enableProcessing } = validatedOptions;
364
526
  const maxRecentDataDuration = 10000; // TODO compute maxRecentDataDuration based on screen dimensions
365
527
  if (typeof onAudioStream === 'function') {
366
528
  onAudioStreamRef.current = onAudioStream;
@@ -369,8 +531,10 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
369
531
  logger?.warn(`onAudioStream is not a function`, onAudioStream);
370
532
  onAudioStreamRef.current = null;
371
533
  }
372
- // Strip undefined values and functions that can't cross the native bridge
373
- const cleanOptions = cleanNativeOptions(options);
534
+ // Strip hook-only values and undefineds that can't cross the native bridge.
535
+ // autoStopOnMaxDuration stays hook-owned so finalization can expose
536
+ // the same AudioRecording result as a manual stop.
537
+ const cleanOptions = createNativeRecordingOptions(validatedOptions);
374
538
  const startResult = await audioStudio.startRecording(cleanOptions);
375
539
  dispatch({ type: 'START' });
376
540
  startResultRef.current = startResult;
@@ -396,7 +560,7 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
396
560
  logger?.debug(`preparing recording`, recordingOptions);
397
561
  analysisRef.current = { ...defaultAnalysis }; // Reset analysis data
398
562
  fullAnalysisRef.current = { ...defaultAnalysis };
399
- const { onAudioStream, onRecordingInterrupted, onAudioAnalysis, keepFullAnalysis: _keepFullAnalysis, ...options } = recordingOptions;
563
+ const { onAudioStream } = recordingOptions;
400
564
  // Store onAudioStream for later use when recording starts
401
565
  if (typeof onAudioStream === 'function') {
402
566
  onAudioStreamRef.current = onAudioStream;
@@ -405,34 +569,16 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
405
569
  logger?.warn(`onAudioStream is not a function`, onAudioStream);
406
570
  onAudioStreamRef.current = null;
407
571
  }
408
- // Strip undefined values and functions that can't cross the native bridge
409
- const cleanOptions = cleanNativeOptions(options);
572
+ // Strip hook-only values and undefineds that can't cross the native bridge.
573
+ const cleanOptions = createNativeRecordingOptions(recordingOptions);
410
574
  // Call the native prepareRecording method
411
575
  await audioStudio.prepareRecording(cleanOptions);
412
576
  logger?.debug(`recording prepared successfully`);
413
577
  }, []);
414
578
  const stopRecording = useCallback(async () => {
415
579
  logger?.debug(`stoping recording`);
416
- const stopResult = await audioStudio.stopRecording();
417
- if (shouldKeepFullAnalysis(recordingConfigRef.current)) {
418
- stopResult.analysisData = fullAnalysisRef.current;
419
- }
420
- else {
421
- // `keepFullAnalysis` is a hook-level retention policy. If a platform
422
- // starts returning native analysisData in the future, keep opt-out
423
- // semantics explicit and avoid leaking a full history here.
424
- delete stopResult.analysisData;
425
- }
426
- if (analysisListenerRef.current) {
427
- analysisListenerRef.current.remove();
428
- analysisListenerRef.current = null;
429
- }
430
- onAudioStreamRef.current = null;
431
- // Note: We deliberately DON'T clear recordingConfigRef here to preserve interruption callback
432
- logger?.debug(`recording stopped`, stopResult);
433
- dispatch({ type: 'STOP' });
434
- return stopResult;
435
- }, [dispatch]);
580
+ return finalizeRecordingStop('manual');
581
+ }, [finalizeRecordingStop, logger]);
436
582
  const pauseRecording = useCallback(async () => {
437
583
  logger?.debug(`pause recording`);
438
584
  const pauseResult = await audioStudio.pauseRecording();
@@ -445,6 +591,14 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
445
591
  dispatch({ type: 'RESUME' });
446
592
  return resumeResult;
447
593
  }, [dispatch]);
594
+ useEffect(() => {
595
+ const subscription = addMaxDurationReachedListener(async (event) => {
596
+ await handleMaxDurationReached(event);
597
+ });
598
+ return () => {
599
+ subscription.remove();
600
+ };
601
+ }, [handleMaxDurationReached]);
448
602
  useEffect(() => {
449
603
  let intervalId;
450
604
  if (state.isRecording || state.isPaused) {
@@ -542,6 +696,9 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
542
696
  size: state.size,
543
697
  compression: state.compression,
544
698
  analysisData: state.analysisData,
699
+ maxDurationMs: state.maxDurationMs,
700
+ maxDurationReached: state.maxDurationReached,
701
+ lastRecordingReason: state.lastRecordingReason,
545
702
  };
546
703
  }
547
704
  //# sourceMappingURL=useAudioRecorder.js.map