@siteed/audio-studio 3.2.0 → 3.2.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +30 -1
  2. package/android/src/main/java/net/siteed/audiostudio/AudioRecorderManager.kt +142 -12
  3. package/android/src/main/java/net/siteed/audiostudio/AudioRecordingService.kt +1 -1
  4. package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +5 -4
  5. package/android/src/main/java/net/siteed/audiostudio/Constants.kt +2 -1
  6. package/android/src/main/java/net/siteed/audiostudio/RecordingActionReceiver.kt +1 -1
  7. package/android/src/main/java/net/siteed/audiostudio/RecordingConfig.kt +5 -1
  8. package/build/cjs/AudioRecorder.provider.js +3 -37
  9. package/build/cjs/AudioRecorder.provider.js.map +1 -1
  10. package/build/cjs/AudioStudio.types.js.map +1 -1
  11. package/build/cjs/AudioStudio.web.js +125 -13
  12. package/build/cjs/AudioStudio.web.js.map +1 -1
  13. package/build/cjs/AudioStudioModule.js +6 -1
  14. package/build/cjs/AudioStudioModule.js.map +1 -1
  15. package/build/cjs/events.js +4 -0
  16. package/build/cjs/events.js.map +1 -1
  17. package/build/cjs/index.js +3 -1
  18. package/build/cjs/index.js.map +1 -1
  19. package/build/cjs/useAudioRecorder.js +139 -4
  20. package/build/cjs/useAudioRecorder.js.map +1 -1
  21. package/build/esm/AudioRecorder.provider.js +3 -4
  22. package/build/esm/AudioRecorder.provider.js.map +1 -1
  23. package/build/esm/AudioStudio.types.js.map +1 -1
  24. package/build/esm/AudioStudio.web.js +125 -13
  25. package/build/esm/AudioStudio.web.js.map +1 -1
  26. package/build/esm/AudioStudioModule.js +6 -1
  27. package/build/esm/AudioStudioModule.js.map +1 -1
  28. package/build/esm/events.js +3 -0
  29. package/build/esm/events.js.map +1 -1
  30. package/build/esm/index.js +1 -0
  31. package/build/esm/index.js.map +1 -1
  32. package/build/esm/useAudioRecorder.js +140 -5
  33. package/build/esm/useAudioRecorder.js.map +1 -1
  34. package/build/types/AudioStudio.types.d.ts +44 -1
  35. package/build/types/AudioStudio.types.d.ts.map +1 -1
  36. package/build/types/AudioStudio.web.d.ts +17 -1
  37. package/build/types/AudioStudio.web.d.ts.map +1 -1
  38. package/build/types/AudioStudioModule.d.ts.map +1 -1
  39. package/build/types/events.d.ts +2 -1
  40. package/build/types/events.d.ts.map +1 -1
  41. package/build/types/index.d.ts +1 -0
  42. package/build/types/index.d.ts.map +1 -1
  43. package/build/types/useAudioRecorder.d.ts +2 -0
  44. package/build/types/useAudioRecorder.d.ts.map +1 -1
  45. package/ios/AudioStreamManager.swift +103 -9
  46. package/ios/AudioStreamManagerDelegate.swift +1 -0
  47. package/ios/AudioStudio.podspec +1 -1
  48. package/ios/AudioStudioModule.swift +6 -0
  49. package/ios/RecordingSettings.swift +48 -43
  50. package/package.json +163 -163
  51. package/plugin/tsconfig.json +8 -2
  52. package/src/AudioStudio.types.ts +48 -1
  53. package/src/AudioStudio.web.ts +152 -13
  54. package/src/AudioStudioModule.ts +6 -1
  55. package/src/events.ts +13 -1
  56. package/src/index.ts +1 -0
  57. package/src/useAudioRecorder.tsx +182 -2
  58. package/scripts/README.md +0 -58
@@ -1 +1 @@
1
- {"version":3,"file":"AudioStudio.types.js","sourceRoot":"","sources":["../../src/AudioStudio.types.ts"],"names":[],"mappings":";;;AA8UA,4EAA4E;AAC/D,QAAA,2BAA2B,GAAG;IACvC,8CAA8C;IAC9C,KAAK,EAAE,OAAO;IACd,sDAAsD;IACtD,QAAQ,EAAE,UAAU;CACd,CAAA","sourcesContent":["// packages/audio-studio/src/AudioStudio.types.ts\nimport {\n AudioAnalysis,\n AudioFeaturesOptions,\n DecodingConfig,\n} from './AudioAnalysis/AudioAnalysis.types'\nimport { AudioAnalysisEvent } from './events'\n\nexport interface CompressionInfo {\n /** Size of the compressed audio data in bytes */\n size: number\n /** MIME type of the compressed audio (e.g., 'audio/aac', 'audio/opus') */\n mimeType: string\n /** Bitrate of the compressed audio in bits per second */\n bitrate: number\n /** Format of the compression (e.g., 'aac', 'opus') */\n format: string\n /** URI to the compressed audio file if available */\n compressedFileUri?: string\n}\n\nexport interface AudioStreamStatus {\n /** Indicates whether audio recording is currently active */\n isRecording: boolean\n /** Indicates whether recording is in a paused state */\n isPaused: boolean\n /** Duration of the current recording in milliseconds */\n durationMs: number\n /** Size of the recorded audio data in bytes */\n size: number\n /** Interval in milliseconds at which recording data is emitted */\n interval: number\n /** Interval in milliseconds at which analysis data is emitted */\n intervalAnalysis: number\n /** MIME type of the recorded audio (e.g., 'audio/wav') */\n mimeType: string\n /** Information about audio compression if enabled */\n compression?: CompressionInfo\n}\n\ninterface AudioDataEventBase {\n /** Current position in the audio stream in bytes */\n position: number\n /** URI to the file being recorded */\n fileUri: string\n /** Size of the current data chunk in bytes */\n eventDataSize: number\n /** Total size of the recording so far in bytes */\n totalSize: number\n /** Information about compression if enabled, including the compressed data chunk */\n compression?: CompressionInfo & {\n /** Base64 (native) or Blob (web) encoded compressed data chunk */\n data?: string | Blob\n }\n}\n\nexport interface AudioDataEventRaw extends AudioDataEventBase {\n /** Audio data as base64 string (native), Float32Array (web), or Int16Array (web) */\n data: string | Float32Array | Int16Array\n streamFormat?: undefined | 'raw'\n}\n\nexport interface AudioDataEventFloat32 extends AudioDataEventBase {\n /** Audio data as Float32Array with samples in [-1, 1] range */\n data: Float32Array\n streamFormat: 'float32'\n}\n\nexport type AudioDataEvent = AudioDataEventRaw | AudioDataEventFloat32\n\n/**\n * Audio encoding types supported by the library.\n *\n * Platform support:\n * - `pcm_8bit`: Android only (iOS/Web will fallback to 16-bit)\n * - `pcm_16bit`: All platforms\n * - `pcm_32bit`: All platforms\n *\n * @see {@link https://github.com/deeeed/audiolab/blob/main/packages/audio-studio/docs/PLATFORM_LIMITATIONS.md | Platform Limitations}\n */\nexport type EncodingType = 'pcm_32bit' | 'pcm_16bit' | 'pcm_8bit'\n\n/**\n * Supported audio sample rates in Hz.\n * All platforms support these standard rates.\n */\nexport type SampleRate = 16000 | 44100 | 48000\n\n/**\n * Audio bit depth (bits per sample).\n *\n * Platform support:\n * - `8`: Android only (iOS/Web will fallback to 16)\n * - `16`: All platforms (recommended for compatibility)\n * - `32`: All platforms\n *\n * @see {@link https://github.com/deeeed/audiolab/blob/main/packages/audio-studio/docs/PLATFORM_LIMITATIONS.md | Platform Limitations}\n */\nexport type BitDepth = 8 | 16 | 32\n\n/**\n * PCM format string representation.\n * @deprecated Use `EncodingType` directly\n */\nexport type PCMFormat = `pcm_${BitDepth}bit`\n\nexport type ConsoleLike = {\n /** Logs a message with optional arguments */\n log: (message: string, ...args: unknown[]) => void\n /** Logs a debug message with optional arguments */\n debug: (message: string, ...args: unknown[]) => void\n /** Logs an info message with optional arguments */\n info: (message: string, ...args: unknown[]) => void\n /** Logs a warning message with optional arguments */\n warn: (message: string, ...args: unknown[]) => void\n /** Logs an error message with optional arguments */\n error: (message: string, ...args: unknown[]) => void\n}\n\nexport interface Chunk {\n /** Transcribed text content */\n text: string\n /** Start and end timestamp in seconds [start, end] where end can be null if ongoing */\n timestamp: [number, number | null]\n}\n\nexport interface TranscriberData {\n /** Unique identifier for the transcription */\n id: string\n /** Indicates if the transcriber is currently processing */\n isBusy: boolean\n /** Complete transcribed text */\n text: string\n /** Start time of the transcription in milliseconds */\n startTime: number\n /** End time of the transcription in milliseconds */\n endTime: number\n /** Array of transcribed text chunks with timestamps */\n chunks: Chunk[]\n}\n\nexport interface AudioRecording {\n /** URI to the recorded audio file */\n fileUri: string\n /** Filename of the recorded audio */\n filename: string\n /** Duration of the recording in milliseconds */\n durationMs: number\n /** Size of the recording in bytes */\n size: number\n /** MIME type of the recorded audio */\n mimeType: string\n /** Number of audio channels (1 for mono, 2 for stereo) */\n channels: number\n /** Bit depth of the audio (8, 16, or 32 bits) */\n bitDepth: BitDepth\n /** Sample rate of the audio in Hz */\n sampleRate: SampleRate\n /** Timestamp when the recording was created */\n createdAt?: number\n /** Array of transcription data if available */\n transcripts?: TranscriberData[]\n /**\n * Full analysis data for the recording if processing was enabled and\n * `keepFullAnalysis` was not set to `false`.\n */\n analysisData?: AudioAnalysis\n /** Information about compression if enabled, including the URI to the compressed file */\n compression?: CompressionInfo & {\n /** URI to the compressed audio file */\n compressedFileUri: string\n }\n}\n\nexport interface StartRecordingResult {\n /** URI to the file being recorded */\n fileUri: string\n /** MIME type of the recording */\n mimeType: string\n /** Number of audio channels (1 for mono, 2 for stereo) */\n channels?: number\n /** Bit depth of the audio (8, 16, or 32 bits) */\n bitDepth?: BitDepth\n /** Sample rate of the audio in Hz */\n sampleRate?: SampleRate\n /** Information about compression if enabled, including the URI to the compressed file */\n compression?: CompressionInfo & {\n /** URI to the compressed audio file */\n compressedFileUri: string\n }\n}\n\nexport interface AudioSessionConfig {\n /**\n * Audio session category that defines the audio behavior\n * - 'Ambient': Audio continues with silent switch, mixes with other audio\n * - 'SoloAmbient': Audio continues with silent switch, interrupts other audio\n * - 'Playback': Audio continues in background, interrupts other audio\n * - 'Record': Optimized for recording, interrupts other audio\n * - 'PlayAndRecord': Allows simultaneous playback and recording\n * - 'MultiRoute': Routes audio to multiple outputs simultaneously\n */\n category?:\n | 'Ambient'\n | 'SoloAmbient'\n | 'Playback'\n | 'Record'\n | 'PlayAndRecord'\n | 'MultiRoute'\n /**\n * Audio session mode that defines the behavior for specific use cases\n * - 'Default': Standard audio behavior\n * - 'VoiceChat': Optimized for voice chat applications\n * - 'VideoChat': Optimized for video chat applications\n * - 'GameChat': Optimized for in-game chat\n * - 'VideoRecording': Optimized for video recording\n * - 'Measurement': Optimized for audio measurement\n * - 'MoviePlayback': Optimized for movie playback\n * - 'SpokenAudio': Optimized for spoken audio content\n */\n mode?:\n | 'Default'\n | 'VoiceChat'\n | 'VideoChat'\n | 'GameChat'\n | 'VideoRecording'\n | 'Measurement'\n | 'MoviePlayback'\n | 'SpokenAudio'\n /**\n * Options that modify the behavior of the audio session category\n * - 'MixWithOthers': Allows mixing with other active audio sessions\n * - 'DuckOthers': Reduces the volume of other audio sessions\n * - 'InterruptSpokenAudioAndMixWithOthers': Interrupts spoken audio and mixes with others\n * - 'AllowBluetooth': Allows audio routing to Bluetooth devices\n * - 'AllowBluetoothA2DP': Allows audio routing to Bluetooth A2DP devices\n * - 'AllowAirPlay': Allows audio routing to AirPlay devices\n * - 'DefaultToSpeaker': Routes audio to the speaker by default\n */\n categoryOptions?: (\n | 'MixWithOthers'\n | 'DuckOthers'\n | 'InterruptSpokenAudioAndMixWithOthers'\n | 'AllowBluetooth'\n | 'AllowBluetoothA2DP'\n | 'AllowAirPlay'\n | 'DefaultToSpeaker'\n )[]\n}\n\nexport interface IOSConfig {\n /** Configuration for the iOS audio session */\n audioSession?: AudioSessionConfig\n}\n\n/** Android platform specific configuration options */\nexport interface AndroidConfig {\n /**\n * Audio focus strategy for handling interruptions and background behavior\n *\n * - `'background'`: Continue recording when app loses focus (voice recorders, transcription apps)\n * - `'interactive'`: Pause when losing focus, resume when gaining (music apps, games)\n * - `'communication'`: Maintain priority for real-time communication (video calls, voice chat)\n * - `'none'`: No automatic audio focus management (custom handling)\n *\n * @default 'background' when keepAwake=true, 'interactive' otherwise\n */\n audioFocusStrategy?: 'background' | 'interactive' | 'communication' | 'none'\n}\n\n/** Web platform specific configuration options */\nexport interface WebConfig {\n // Reserved for future web-specific options\n}\n\n// Add new type for interruption reasons\nexport type RecordingInterruptionReason =\n /** Audio focus was lost to another app */\n | 'audioFocusLoss'\n /** Audio focus was regained */\n | 'audioFocusGain'\n /** Recording was interrupted by a phone call */\n | 'phoneCall'\n /** Phone call that interrupted recording has ended */\n | 'phoneCallEnded'\n /** Recording was stopped by the system or another app */\n | 'recordingStopped'\n /** Recording device was disconnected */\n | 'deviceDisconnected'\n /** Recording switched to default device after disconnection */\n | 'deviceFallback'\n /** A new audio device was connected */\n | 'deviceConnected'\n /** Device switching failed */\n | 'deviceSwitchFailed'\n\n// Add new interface for interruption events\nexport interface RecordingInterruptionEvent {\n /** The reason for the recording interruption */\n reason: RecordingInterruptionReason\n /** Indicates whether the recording is paused due to the interruption */\n isPaused: boolean\n}\n\nexport interface AudioDeviceCapabilities {\n /** Supported sample rates for the device */\n sampleRates: number[]\n /** Supported channel counts for the device */\n channelCounts: number[]\n /** Supported bit depths for the device */\n bitDepths: number[]\n /** Whether the device supports echo cancellation */\n hasEchoCancellation?: boolean\n /** Whether the device supports noise suppression */\n hasNoiseSuppression?: boolean\n /** Whether the device supports automatic gain control */\n hasAutomaticGainControl?: boolean\n}\n\nexport interface AudioDevice {\n /** Unique identifier for the device */\n id: string\n /** Human-readable name of the device */\n name: string\n /** Device type (builtin_mic, bluetooth, etc.) */\n type: string\n /** Whether this is the system default device */\n isDefault: boolean\n /** Audio capabilities for the device */\n capabilities: AudioDeviceCapabilities\n /** Whether the device is currently available */\n isAvailable: boolean\n}\n\n/** Defines how recording should behave when a device becomes unavailable */\nexport const DeviceDisconnectionBehavior = {\n /** Pause recording when device disconnects */\n PAUSE: 'pause',\n /** Switch to default device and continue recording */\n FALLBACK: 'fallback',\n} as const\n\n/** Type for DeviceDisconnectionBehavior values */\nexport type DeviceDisconnectionBehaviorType =\n (typeof DeviceDisconnectionBehavior)[keyof typeof DeviceDisconnectionBehavior]\n\n/**\n * Configuration for audio output files during recording\n */\nexport interface OutputConfig {\n /**\n * Configuration for the primary (uncompressed) output file\n */\n primary?: {\n /** Whether to create the primary output file (default: true) */\n enabled?: boolean\n /** Format for the primary output (currently only 'wav' is supported) */\n format?: 'wav'\n }\n\n /**\n * Configuration for the compressed output file\n */\n compressed?: {\n /** Whether to create a compressed output file (default: false) */\n enabled?: boolean\n /**\n * Format for compression\n * - 'aac': Advanced Audio Coding - supported on all platforms\n * - 'opus': Opus encoding - supported on Android and Web; on iOS will automatically fall back to AAC\n */\n format?: 'aac' | 'opus'\n /** Bitrate for compression in bits per second (default: 128000) */\n bitrate?: number\n /**\n * Prefer raw stream over container format (Android only)\n * - true: Use raw AAC stream (.aac files) like in v2.10.6\n * - false/undefined: Use M4A container (.m4a files) for better seeking support\n * Note: iOS always produces M4A containers and ignores this flag\n */\n preferRawStream?: boolean\n }\n\n // Future enhancement: Post-processing pipeline\n // postProcessing?: {\n // normalize?: boolean\n // trimSilence?: boolean\n // noiseReduction?: boolean\n // customProcessors?: AudioProcessor[]\n // }\n}\n\nexport interface RecordingConfig {\n /** Sample rate for recording in Hz (16000, 44100, or 48000) */\n sampleRate?: SampleRate\n\n /** Number of audio channels (1 for mono, 2 for stereo) */\n channels?: 1 | 2\n\n /**\n * Encoding type for the recording.\n *\n * Platform limitations:\n * - `pcm_8bit`: Android only (iOS/Web will fallback to `pcm_16bit` with warning)\n * - `pcm_16bit`: All platforms (recommended for cross-platform compatibility)\n * - `pcm_32bit`: All platforms\n *\n * The library will automatically validate and adjust the encoding based on\n * platform capabilities. A warning will be logged if fallback is required.\n *\n * @default 'pcm_16bit'\n * @see {@link EncodingType}\n * @see {@link https://github.com/deeeed/audiolab/blob/main/packages/audio-studio/docs/PLATFORM_LIMITATIONS.md | Platform Limitations}\n */\n encoding?: EncodingType\n\n /** Interval in milliseconds at which to emit recording data (minimum: 10ms) */\n interval?: number\n\n /** Interval in milliseconds at which to emit analysis data (minimum: 10ms) */\n intervalAnalysis?: number\n\n /** Keep the device awake while recording (default is false) */\n keepAwake?: boolean\n\n /** Show a notification during recording (default is false) */\n showNotification?: boolean\n\n /** Show waveform in the notification (Android only, when showNotification is true) */\n showWaveformInNotification?: boolean\n\n /** Configuration for the notification */\n notification?: NotificationConfig\n\n /** Enable audio processing (default is false) */\n enableProcessing?: boolean\n\n /**\n * Whether `useAudioRecorder` should retain every audio-analysis data point\n * and attach the full history to `stopRecording().analysisData`.\n *\n * Defaults to `true` for backwards compatibility. Set to `false` for\n * long-running recordings when you only need live `analysisData` state or\n * per-callback `onAudioAnalysis` chunks; this avoids unbounded JS memory\n * growth in the hook without disabling native analysis processing.\n */\n keepFullAnalysis?: boolean\n\n /** iOS-specific configuration */\n ios?: IOSConfig\n\n /** Android-specific configuration */\n android?: AndroidConfig\n\n /** Web-specific configuration options */\n web?: WebConfig\n\n /** Duration of each segment in milliseconds for analysis (default: 100) */\n segmentDurationMs?: number\n\n /** Feature options to extract during audio processing */\n features?: AudioFeaturesOptions\n\n /** Callback function to handle audio stream data */\n onAudioStream?: (_: AudioDataEvent) => Promise<void>\n\n /** Callback function to handle audio features extraction results */\n onAudioAnalysis?: (_: AudioAnalysisEvent) => Promise<void>\n\n /**\n * Configuration for audio output files\n *\n * Examples:\n * - Primary only (default): `{ primary: { enabled: true } }`\n * - Compressed only: `{ primary: { enabled: false }, compressed: { enabled: true, format: 'aac' } }`\n * - Both outputs: `{ compressed: { enabled: true } }`\n * - Streaming only: `{ primary: { enabled: false } }`\n */\n output?: OutputConfig\n\n /** Whether to automatically resume recording after an interruption (default is false) */\n autoResumeAfterInterruption?: boolean\n\n /** Optional callback to handle recording interruptions */\n onRecordingInterrupted?: (_: RecordingInterruptionEvent) => void\n\n /** Optional directory path where output files will be saved */\n outputDirectory?: string // If not provided, uses default app directory\n /** Optional filename for the recording (uses UUID if not provided) */\n filename?: string // If not provided, uses UUID\n\n /** ID of the device to use for recording (if not specified, uses default) */\n deviceId?: string\n\n /** How to handle device disconnection during recording */\n deviceDisconnectionBehavior?: DeviceDisconnectionBehaviorType\n\n /**\n * Buffer duration in seconds. Controls the size of audio buffers\n * used during recording. Smaller values reduce latency but increase\n * CPU usage. Larger values improve efficiency but increase latency.\n *\n * Platform Notes:\n * - iOS/macOS: Minimum effective 0.1s, uses accumulation below\n * - Android: Respects all sizes within hardware limits\n * - Web: Fully configurable\n *\n * Default: undefined (uses platform default ~23ms at 44.1kHz)\n * Recommended: 0.01 - 0.5 seconds\n * Optimal iOS: >= 0.1 seconds\n */\n bufferDurationSeconds?: number\n\n /**\n * Format for the audio stream data delivered to `onAudioStream`.\n *\n * - `'raw'` (default): base64-encoded PCM bytes on native, Float32Array on web\n * - `'float32'`: Float32Array with samples in [-1, 1] on all platforms.\n * Eliminates base64 encode/decode overhead on the native bridge.\n * Android (new arch): delivered as Float32Array via JSI.\n * iOS: delivered as regular Array<number>, normalized to Float32Array in JS.\n *\n * @default 'raw'\n */\n streamFormat?: 'float32' | 'raw'\n}\n\nexport interface NotificationConfig {\n /** Title of the notification */\n title?: string\n\n /** Main text content of the notification */\n text?: string\n\n /** Icon to be displayed in the notification (resource name or URI) */\n icon?: string\n\n /** Android-specific notification configuration */\n android?: {\n /** Unique identifier for the notification channel */\n channelId?: string\n\n /** User-visible name of the notification channel */\n channelName?: string\n\n /** User-visible description of the notification channel */\n channelDescription?: string\n\n /** Unique identifier for this notification */\n notificationId?: number\n\n /** List of actions that can be performed from the notification */\n actions?: NotificationAction[]\n\n /** Configuration for the waveform visualization in the notification */\n waveform?: WaveformConfig\n\n /** Color of the notification LED (if device supports it) */\n lightColor?: string\n\n /** Priority of the notification (affects how it's displayed) */\n priority?: 'min' | 'low' | 'default' | 'high' | 'max'\n\n /** Accent color for the notification (used for the app icon and buttons) */\n accentColor?: string\n\n /** Whether to show pause/resume actions in the notification (default: true) */\n showPauseResumeActions?: boolean\n }\n\n /** iOS-specific notification configuration */\n ios?: {\n /** Identifier for the notification category (used for grouping similar notifications) */\n categoryIdentifier?: string\n }\n}\n\nexport interface NotificationAction {\n /** Display title for the action */\n title: string\n\n /** Unique identifier for the action */\n identifier: string\n\n /** Icon to be displayed for the action (Android only) */\n icon?: string\n}\n\nexport interface WaveformConfig {\n /** The color of the waveform (e.g., \"#FFFFFF\" for white) */\n color?: string // The color of the waveform (e.g., \"#FFFFFF\" for white)\n /** Opacity of the waveform (0.0 - 1.0) */\n opacity?: number // Opacity of the waveform (0.0 - 1.0)\n /** Width of the waveform line (default: 1.5) */\n strokeWidth?: number // Width of the waveform line (default: 1.5)\n /** Drawing style: \"stroke\" for outline, \"fill\" for solid */\n style?: 'stroke' | 'fill' // Drawing style: \"stroke\" for outline, \"fill\" for solid\n /** Whether to mirror the waveform (symmetrical display) */\n mirror?: boolean // Whether to mirror the waveform (symmetrical display)\n /** Height of the waveform view in dp (default: 64) */\n height?: number // Height of the waveform view in dp (default: 64)\n}\n\nexport interface ExtractAudioDataOptions {\n /** URI of the audio file to extract data from */\n fileUri: string\n /** Start time in milliseconds (for time-based range) */\n startTimeMs?: number\n /** End time in milliseconds (for time-based range) */\n endTimeMs?: number\n /** Start position in bytes (for byte-based range) */\n position?: number\n /** Length in bytes to extract (for byte-based range) */\n length?: number\n /** Include normalized audio data in [-1, 1] range */\n includeNormalizedData?: boolean\n /** Include base64 encoded string representation of the audio data */\n includeBase64Data?: boolean\n /** Include WAV header in the PCM data (makes it a valid WAV file) */\n includeWavHeader?: boolean\n /** Logger for debugging - can pass console directly. */\n logger?: ConsoleLike\n /** Compute the checksum of the PCM data */\n computeChecksum?: boolean\n /** Target config for the normalized audio (Android and Web) */\n decodingOptions?: DecodingConfig\n}\n\nexport interface ExtractedAudioData {\n /** Raw PCM audio data */\n pcmData: Uint8Array\n /** Normalized audio data in [-1, 1] range (when includeNormalizedData is true) */\n normalizedData?: Float32Array\n /** Base64 encoded string representation of the audio data (when includeBase64Data is true) */\n base64Data?: string\n /** Sample rate in Hz (e.g., 44100, 48000) */\n sampleRate: number\n /** Number of audio channels (1 for mono, 2 for stereo) */\n channels: number\n /** Bits per sample (8, 16, or 32) */\n bitDepth: BitDepth\n /** Duration of the audio in milliseconds */\n durationMs: number\n /** PCM format identifier (e.g., \"pcm_16bit\") */\n format: PCMFormat\n /** Total number of audio samples per channel */\n samples: number\n /** Whether the pcmData includes a WAV header */\n hasWavHeader?: boolean\n /** CRC32 Checksum of PCM data */\n checksum?: number\n}\n\nexport interface UseAudioRecorderState {\n /**\n * Prepares recording with the specified configuration without starting it.\n *\n * This method eliminates the latency between calling startRecording and the actual recording beginning.\n * It pre-initializes all audio resources, requests permissions, and sets up audio sessions in advance,\n * allowing for true zero-latency recording start when startRecording is called later.\n *\n * Technical benefits:\n * - Eliminates audio pipeline initialization delay (50-300ms depending on platform)\n * - Pre-allocates audio buffers to avoid memory allocation during recording start\n * - Initializes audio hardware in advance (particularly important on iOS)\n * - Requests and verifies permissions before the critical recording moment\n *\n * Use this method when:\n * - You need zero-latency recording start (e.g., voice commands, musical applications)\n * - You're building time-sensitive applications where missing initial audio would be problematic\n * - You want to prepare resources during app initialization, screen loading, or preceding user interaction\n * - You need to ensure recording starts reliably and instantly on all platforms\n *\n * @param config - The recording configuration, identical to what you would pass to startRecording\n * @returns A promise that resolves when preparation is complete\n *\n * @example\n * // Prepare during component mounting\n * useEffect(() => {\n * prepareRecording({\n * sampleRate: 44100,\n * channels: 1,\n * encoding: 'pcm_16bit',\n * });\n * }, []);\n *\n * // Later when user taps record button, it starts with zero latency\n * const handleRecordPress = () => startRecording({\n * sampleRate: 44100,\n * channels: 1,\n * encoding: 'pcm_16bit',\n * });\n */\n prepareRecording: (_: RecordingConfig) => Promise<void>\n /** Starts recording with the specified configuration */\n startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>\n /** Stops the current recording and returns the recording data */\n stopRecording: () => Promise<AudioRecording | null>\n /** Pauses the current recording */\n pauseRecording: () => Promise<void>\n /** Resumes a paused recording */\n resumeRecording: () => Promise<void>\n /** Indicates whether recording is currently active */\n isRecording: boolean\n /** Indicates whether recording is in a paused state */\n isPaused: boolean\n /** Duration of the current recording in milliseconds */\n durationMs: number // Duration of the recording\n /** Size of the recorded audio in bytes */\n size: number // Size in bytes of the recorded audio\n /** Information about compression if enabled */\n compression?: CompressionInfo\n /** Analysis data for the recording if processing was enabled */\n analysisData?: AudioAnalysis // Analysis data for the recording depending on enableProcessing flag\n /** Optional callback to handle recording interruptions */\n onRecordingInterrupted?: (_: RecordingInterruptionEvent) => void\n}\n\n/**\n * Represents an event emitted during the trimming process to report progress.\n */\nexport interface TrimProgressEvent {\n /**\n * The percentage of the trimming process that has been completed, ranging from 0 to 100.\n */\n progress: number\n\n /**\n * The number of bytes that have been processed so far. This is optional and may not be provided in all implementations.\n */\n bytesProcessed?: number\n\n /**\n * The total number of bytes to process. This is optional and may not be provided in all implementations.\n */\n totalBytes?: number\n}\n\n/**\n * Defines a time range in milliseconds for trimming operations.\n */\nexport interface TimeRange {\n /**\n * The start time of the range in milliseconds.\n */\n startTimeMs: number\n\n /**\n * The end time of the range in milliseconds.\n */\n endTimeMs: number\n}\n\n/**\n * Options for configuring the audio trimming operation.\n */\nexport interface TrimAudioOptions {\n /**\n * The URI of the audio file to trim.\n */\n fileUri: string\n\n /**\n * The mode of trimming to apply.\n * - `'single'`: Trims the audio to a single range defined by `startTimeMs` and `endTimeMs`.\n * - `'keep'`: Keeps the specified `ranges` and removes all other portions of the audio.\n * - `'remove'`: Removes the specified `ranges` and keeps the remaining portions of the audio.\n * @default 'single'\n */\n mode?: 'single' | 'keep' | 'remove'\n\n /**\n * An array of time ranges to keep or remove, depending on the `mode`.\n * - Required for `'keep'` and `'remove'` modes.\n * - Ignored when `mode` is `'single'`.\n */\n ranges?: TimeRange[]\n\n /**\n * The start time in milliseconds for the `'single'` mode.\n * - If not provided, trimming starts from the beginning of the audio (0 ms).\n */\n startTimeMs?: number\n\n /**\n * The end time in milliseconds for the `'single'` mode.\n * - If not provided, trimming extends to the end of the audio.\n */\n endTimeMs?: number\n\n /**\n * The name of the output file. If not provided, a default name will be generated.\n */\n outputFileName?: string\n\n /**\n * Configuration for the output audio format.\n */\n outputFormat?: {\n /**\n * The format of the output audio file.\n * - `'wav'`: Waveform Audio File Format (uncompressed).\n * - `'aac'`: Advanced Audio Coding (compressed). Not supported on web platforms.\n * - `'opus'`: Opus Interactive Audio Codec (compressed).\n */\n format: 'wav' | 'aac' | 'opus'\n\n /**\n * The sample rate of the output audio in Hertz (Hz).\n * - If not provided, the input audio's sample rate is used.\n */\n sampleRate?: number\n\n /**\n * The number of channels in the output audio (e.g., 1 for mono, 2 for stereo).\n * - If not provided, the input audio's channel count is used.\n */\n channels?: number\n\n /**\n * The bit depth of the output audio, applicable to PCM formats like `'wav'`.\n * - If not provided, the input audio's bit depth is used.\n */\n bitDepth?: number\n\n /**\n * The bitrate of the output audio in bits per second, applicable to compressed formats like `'aac'`.\n * - If not provided, a default bitrate is used based on the format.\n */\n bitrate?: number\n }\n\n /**\n * Options for decoding the input audio file.\n * - See `DecodingConfig` for details.\n */\n decodingOptions?: DecodingConfig\n}\n\n/**\n * Result of the audio trimming operation.\n */\nexport interface TrimAudioResult {\n /**\n * The URI of the trimmed audio file.\n */\n uri: string\n\n /**\n * The filename of the trimmed audio file.\n */\n filename: string\n\n /**\n * The duration of the trimmed audio in milliseconds.\n */\n durationMs: number\n\n /**\n * The size of the trimmed audio file in bytes.\n */\n size: number\n\n /**\n * The sample rate of the trimmed audio in Hertz (Hz).\n */\n sampleRate: number\n\n /**\n * The number of channels in the trimmed audio (e.g., 1 for mono, 2 for stereo).\n */\n channels: number\n\n /**\n * The bit depth of the trimmed audio, applicable to PCM formats like `'wav'`.\n */\n bitDepth: number\n\n /**\n * The MIME type of the trimmed audio file (e.g., `'audio/wav'`, `'audio/mpeg'`).\n */\n mimeType: string\n\n /**\n * Information about compression if the output format is compressed.\n */\n compression?: {\n /**\n * The format of the compression (e.g., `'aac'`, `'mp3'`, `'opus'`).\n */\n format: string\n\n /**\n * The bitrate of the compressed audio in bits per second.\n */\n bitrate: number\n\n /**\n * The size of the compressed audio file in bytes.\n */\n size: number\n }\n\n /**\n * Information about the processing time.\n */\n processingInfo?: {\n /**\n * The time it took to process the audio in milliseconds.\n */\n durationMs: number\n }\n}\n"]}
1
+ {"version":3,"file":"AudioStudio.types.js","sourceRoot":"","sources":["../../src/AudioStudio.types.ts"],"names":[],"mappings":";;;AA+VA,4EAA4E;AAC/D,QAAA,2BAA2B,GAAG;IACvC,8CAA8C;IAC9C,KAAK,EAAE,OAAO;IACd,sDAAsD;IACtD,QAAQ,EAAE,UAAU;CACd,CAAA","sourcesContent":["// packages/audio-studio/src/AudioStudio.types.ts\nimport {\n AudioAnalysis,\n AudioFeaturesOptions,\n DecodingConfig,\n} from './AudioAnalysis/AudioAnalysis.types'\nimport type { AudioAnalysisEvent } from './events'\n\nexport interface CompressionInfo {\n /** Size of the compressed audio data in bytes */\n size: number\n /** MIME type of the compressed audio (e.g., 'audio/aac', 'audio/opus') */\n mimeType: string\n /** Bitrate of the compressed audio in bits per second */\n bitrate: number\n /** Format of the compression (e.g., 'aac', 'opus') */\n format: string\n /** URI to the compressed audio file if available */\n compressedFileUri?: string\n}\n\nexport interface AudioStreamStatus {\n /** Indicates whether audio recording is currently active */\n isRecording: boolean\n /** Indicates whether recording is in a paused state */\n isPaused: boolean\n /** Duration of the current recording in milliseconds */\n durationMs: number\n /** Size of the recorded audio data in bytes */\n size: number\n /** Interval in milliseconds at which recording data is emitted */\n interval: number\n /** Interval in milliseconds at which analysis data is emitted */\n intervalAnalysis: number\n /** MIME type of the recorded audio (e.g., 'audio/wav') */\n mimeType: string\n /** Information about audio compression if enabled */\n compression?: CompressionInfo\n /** Configured maximum active recording duration in milliseconds, if enabled */\n maxDurationMs?: number\n /** Whether the current recording session has reached the configured maximum duration */\n maxDurationReached?: boolean\n}\n\ninterface AudioDataEventBase {\n /** Current position in the audio stream in bytes */\n position: number\n /** URI to the file being recorded */\n fileUri: string\n /** Size of the current data chunk in bytes */\n eventDataSize: number\n /** Total size of the recording so far in bytes */\n totalSize: number\n /** Information about compression if enabled, including the compressed data chunk */\n compression?: CompressionInfo & {\n /** Base64 (native) or Blob (web) encoded compressed data chunk */\n data?: string | Blob\n }\n}\n\nexport interface AudioDataEventRaw extends AudioDataEventBase {\n /** Audio data as base64 string (native), Float32Array (web), or Int16Array (web) */\n data: string | Float32Array | Int16Array\n streamFormat?: undefined | 'raw'\n}\n\nexport interface AudioDataEventFloat32 extends AudioDataEventBase {\n /** Audio data as Float32Array with samples in [-1, 1] range */\n data: Float32Array\n streamFormat: 'float32'\n}\n\nexport type AudioDataEvent = AudioDataEventRaw | AudioDataEventFloat32\n\n/**\n * Audio encoding types supported by the library.\n *\n * Platform support:\n * - `pcm_8bit`: Android only (iOS/Web will fallback to 16-bit)\n * - `pcm_16bit`: All platforms\n * - `pcm_32bit`: All platforms\n *\n * @see {@link https://github.com/deeeed/audiolab/blob/main/packages/audio-studio/docs/PLATFORM_LIMITATIONS.md | Platform Limitations}\n */\nexport type EncodingType = 'pcm_32bit' | 'pcm_16bit' | 'pcm_8bit'\n\n/**\n * Supported audio sample rates in Hz.\n * All platforms support these standard rates.\n */\nexport type SampleRate = 16000 | 44100 | 48000\n\n/**\n * Audio bit depth (bits per sample).\n *\n * Platform support:\n * - `8`: Android only (iOS/Web will fallback to 16)\n * - `16`: All platforms (recommended for compatibility)\n * - `32`: All platforms\n *\n * @see {@link https://github.com/deeeed/audiolab/blob/main/packages/audio-studio/docs/PLATFORM_LIMITATIONS.md | Platform Limitations}\n */\nexport type BitDepth = 8 | 16 | 32\n\n/**\n * PCM format string representation.\n * @deprecated Use `EncodingType` directly\n */\nexport type PCMFormat = `pcm_${BitDepth}bit`\n\nexport type ConsoleLike = {\n /** Logs a message with optional arguments */\n log: (message: string, ...args: unknown[]) => void\n /** Logs a debug message with optional arguments */\n debug: (message: string, ...args: unknown[]) => void\n /** Logs an info message with optional arguments */\n info: (message: string, ...args: unknown[]) => void\n /** Logs a warning message with optional arguments */\n warn: (message: string, ...args: unknown[]) => void\n /** Logs an error message with optional arguments */\n error: (message: string, ...args: unknown[]) => void\n}\n\nexport interface Chunk {\n /** Transcribed text content */\n text: string\n /** Start and end timestamp in seconds [start, end] where end can be null if ongoing */\n timestamp: [number, number | null]\n}\n\nexport interface TranscriberData {\n /** Unique identifier for the transcription */\n id: string\n /** Indicates if the transcriber is currently processing */\n isBusy: boolean\n /** Complete transcribed text */\n text: string\n /** Start time of the transcription in milliseconds */\n startTime: number\n /** End time of the transcription in milliseconds */\n endTime: number\n /** Array of transcribed text chunks with timestamps */\n chunks: Chunk[]\n}\n\nexport interface AudioRecording {\n /** URI to the recorded audio file */\n fileUri: string\n /** Filename of the recorded audio */\n filename: string\n /** Duration of the recording in milliseconds */\n durationMs: number\n /** Size of the recording in bytes */\n size: number\n /** MIME type of the recorded audio */\n mimeType: string\n /** Number of audio channels (1 for mono, 2 for stereo) */\n channels: number\n /** Bit depth of the audio (8, 16, or 32 bits) */\n bitDepth: BitDepth\n /** Sample rate of the audio in Hz */\n sampleRate: SampleRate\n /** Timestamp when the recording was created */\n createdAt?: number\n /** Array of transcription data if available */\n transcripts?: TranscriberData[]\n /**\n * Full analysis data for the recording if processing was enabled and\n * `keepFullAnalysis` was not set to `false`.\n */\n analysisData?: AudioAnalysis\n /** Information about compression if enabled, including the URI to the compressed file */\n compression?: CompressionInfo & {\n /** URI to the compressed audio file */\n compressedFileUri: string\n }\n}\n\nexport interface StartRecordingResult {\n /** URI to the file being recorded */\n fileUri: string\n /** MIME type of the recording */\n mimeType: string\n /** Number of audio channels (1 for mono, 2 for stereo) */\n channels?: number\n /** Bit depth of the audio (8, 16, or 32 bits) */\n bitDepth?: BitDepth\n /** Sample rate of the audio in Hz */\n sampleRate?: SampleRate\n /** Information about compression if enabled, including the URI to the compressed file */\n compression?: CompressionInfo & {\n /** URI to the compressed audio file */\n compressedFileUri: string\n }\n}\n\nexport interface MaxDurationReachedEvent {\n /** Active recording duration that triggered the event, in milliseconds */\n durationMs: number\n /** Configured active recording duration limit, in milliseconds */\n maxDurationMs: number\n /** Amount by which timer delivery exceeded the limit, in milliseconds */\n overrunMs: number\n /** Active stream identifier when available */\n streamUuid?: string\n /** Whether the recorder was configured to stop automatically after this event */\n autoStopped: boolean\n}\n\nexport interface AudioSessionConfig {\n /**\n * Audio session category that defines the audio behavior\n * - 'Ambient': Audio continues with silent switch, mixes with other audio\n * - 'SoloAmbient': Audio continues with silent switch, interrupts other audio\n * - 'Playback': Audio continues in background, interrupts other audio\n * - 'Record': Optimized for recording, interrupts other audio\n * - 'PlayAndRecord': Allows simultaneous playback and recording\n * - 'MultiRoute': Routes audio to multiple outputs simultaneously\n */\n category?:\n | 'Ambient'\n | 'SoloAmbient'\n | 'Playback'\n | 'Record'\n | 'PlayAndRecord'\n | 'MultiRoute'\n /**\n * Audio session mode that defines the behavior for specific use cases\n * - 'Default': Standard audio behavior\n * - 'VoiceChat': Optimized for voice chat applications\n * - 'VideoChat': Optimized for video chat applications\n * - 'GameChat': Optimized for in-game chat\n * - 'VideoRecording': Optimized for video recording\n * - 'Measurement': Optimized for audio measurement\n * - 'MoviePlayback': Optimized for movie playback\n * - 'SpokenAudio': Optimized for spoken audio content\n */\n mode?:\n | 'Default'\n | 'VoiceChat'\n | 'VideoChat'\n | 'GameChat'\n | 'VideoRecording'\n | 'Measurement'\n | 'MoviePlayback'\n | 'SpokenAudio'\n /**\n * Options that modify the behavior of the audio session category\n * - 'MixWithOthers': Allows mixing with other active audio sessions\n * - 'DuckOthers': Reduces the volume of other audio sessions\n * - 'InterruptSpokenAudioAndMixWithOthers': Interrupts spoken audio and mixes with others\n * - 'AllowBluetooth': Allows audio routing to Bluetooth devices\n * - 'AllowBluetoothA2DP': Allows audio routing to Bluetooth A2DP devices\n * - 'AllowAirPlay': Allows audio routing to AirPlay devices\n * - 'DefaultToSpeaker': Routes audio to the speaker by default\n */\n categoryOptions?: (\n | 'MixWithOthers'\n | 'DuckOthers'\n | 'InterruptSpokenAudioAndMixWithOthers'\n | 'AllowBluetooth'\n | 'AllowBluetoothA2DP'\n | 'AllowAirPlay'\n | 'DefaultToSpeaker'\n )[]\n}\n\nexport interface IOSConfig {\n /** Configuration for the iOS audio session */\n audioSession?: AudioSessionConfig\n}\n\n/** Android platform specific configuration options */\nexport interface AndroidConfig {\n /**\n * Audio focus strategy for handling interruptions and background behavior\n *\n * - `'background'`: Continue recording when app loses focus (voice recorders, transcription apps)\n * - `'interactive'`: Pause when losing focus, resume when gaining (music apps, games)\n * - `'communication'`: Maintain priority for real-time communication (video calls, voice chat)\n * - `'none'`: No automatic audio focus management (custom handling)\n *\n * @default 'background' when keepAwake=true, 'interactive' otherwise\n */\n audioFocusStrategy?: 'background' | 'interactive' | 'communication' | 'none'\n}\n\n/** Web platform specific configuration options */\nexport interface WebConfig {\n // Reserved for future web-specific options\n}\n\n// Add new type for interruption reasons\nexport type RecordingInterruptionReason =\n /** Audio focus was lost to another app */\n | 'audioFocusLoss'\n /** Audio focus was regained */\n | 'audioFocusGain'\n /** Recording was interrupted by a phone call */\n | 'phoneCall'\n /** Phone call that interrupted recording has ended */\n | 'phoneCallEnded'\n /** Recording was stopped by the system or another app */\n | 'recordingStopped'\n /** Recording device was disconnected */\n | 'deviceDisconnected'\n /** Recording switched to default device after disconnection */\n | 'deviceFallback'\n /** A new audio device was connected */\n | 'deviceConnected'\n /** Device switching failed */\n | 'deviceSwitchFailed'\n\n// Add new interface for interruption events\nexport interface RecordingInterruptionEvent {\n /** The reason for the recording interruption */\n reason: RecordingInterruptionReason\n /** Indicates whether the recording is paused due to the interruption */\n isPaused: boolean\n}\n\nexport interface AudioDeviceCapabilities {\n /** Supported sample rates for the device */\n sampleRates: number[]\n /** Supported channel counts for the device */\n channelCounts: number[]\n /** Supported bit depths for the device */\n bitDepths: number[]\n /** Whether the device supports echo cancellation */\n hasEchoCancellation?: boolean\n /** Whether the device supports noise suppression */\n hasNoiseSuppression?: boolean\n /** Whether the device supports automatic gain control */\n hasAutomaticGainControl?: boolean\n}\n\nexport interface AudioDevice {\n /** Unique identifier for the device */\n id: string\n /** Human-readable name of the device */\n name: string\n /** Device type (builtin_mic, bluetooth, etc.) */\n type: string\n /** Whether this is the system default device */\n isDefault: boolean\n /** Audio capabilities for the device */\n capabilities: AudioDeviceCapabilities\n /** Whether the device is currently available */\n isAvailable: boolean\n}\n\n/** Defines how recording should behave when a device becomes unavailable */\nexport const DeviceDisconnectionBehavior = {\n /** Pause recording when device disconnects */\n PAUSE: 'pause',\n /** Switch to default device and continue recording */\n FALLBACK: 'fallback',\n} as const\n\n/** Type for DeviceDisconnectionBehavior values */\nexport type DeviceDisconnectionBehaviorType =\n (typeof DeviceDisconnectionBehavior)[keyof typeof DeviceDisconnectionBehavior]\n\n/**\n * Configuration for audio output files during recording\n */\nexport interface OutputConfig {\n /**\n * Configuration for the primary (uncompressed) output file\n */\n primary?: {\n /** Whether to create the primary output file (default: true) */\n enabled?: boolean\n /** Format for the primary output (currently only 'wav' is supported) */\n format?: 'wav'\n }\n\n /**\n * Configuration for the compressed output file\n */\n compressed?: {\n /** Whether to create a compressed output file (default: false) */\n enabled?: boolean\n /**\n * Format for compression\n * - 'aac': Advanced Audio Coding - supported on all platforms\n * - 'opus': Opus encoding - supported on Android and Web; on iOS will automatically fall back to AAC\n */\n format?: 'aac' | 'opus'\n /** Bitrate for compression in bits per second (default: 128000) */\n bitrate?: number\n /**\n * Prefer raw stream over container format (Android only)\n * - true: Use raw AAC stream (.aac files) like in v2.10.6\n * - false/undefined: Use M4A container (.m4a files) for better seeking support\n * Note: iOS always produces M4A containers and ignores this flag\n */\n preferRawStream?: boolean\n }\n\n // Future enhancement: Post-processing pipeline\n // postProcessing?: {\n // normalize?: boolean\n // trimSilence?: boolean\n // noiseReduction?: boolean\n // customProcessors?: AudioProcessor[]\n // }\n}\n\nexport interface RecordingConfig {\n /** Sample rate for recording in Hz (16000, 44100, or 48000) */\n sampleRate?: SampleRate\n\n /** Number of audio channels (1 for mono, 2 for stereo) */\n channels?: 1 | 2\n\n /**\n * Encoding type for the recording.\n *\n * Platform limitations:\n * - `pcm_8bit`: Android only (iOS/Web will fallback to `pcm_16bit` with warning)\n * - `pcm_16bit`: All platforms (recommended for cross-platform compatibility)\n * - `pcm_32bit`: All platforms\n *\n * The library will automatically validate and adjust the encoding based on\n * platform capabilities. A warning will be logged if fallback is required.\n *\n * @default 'pcm_16bit'\n * @see {@link EncodingType}\n * @see {@link https://github.com/deeeed/audiolab/blob/main/packages/audio-studio/docs/PLATFORM_LIMITATIONS.md | Platform Limitations}\n */\n encoding?: EncodingType\n\n /** Interval in milliseconds at which to emit recording data (minimum: 10ms) */\n interval?: number\n\n /** Interval in milliseconds at which to emit analysis data (minimum: 10ms) */\n intervalAnalysis?: number\n\n /** Keep the device awake while recording (default is false) */\n keepAwake?: boolean\n\n /** Show a notification during recording (default is false) */\n showNotification?: boolean\n\n /** Show waveform in the notification (Android only, when showNotification is true) */\n showWaveformInNotification?: boolean\n\n /** Configuration for the notification */\n notification?: NotificationConfig\n\n /** Enable audio processing (default is false) */\n enableProcessing?: boolean\n\n /**\n * Whether `useAudioRecorder` should retain every audio-analysis data point\n * and attach the full history to `stopRecording().analysisData`.\n *\n * Defaults to `true` for backwards compatibility. Set to `false` for\n * long-running recordings when you only need live `analysisData` state or\n * per-callback `onAudioAnalysis` chunks; this avoids unbounded JS memory\n * growth in the hook without disabling native analysis processing.\n */\n keepFullAnalysis?: boolean\n\n /** iOS-specific configuration */\n ios?: IOSConfig\n\n /** Android-specific configuration */\n android?: AndroidConfig\n\n /** Web-specific configuration options */\n web?: WebConfig\n\n /** Duration of each segment in milliseconds for analysis (default: 100) */\n segmentDurationMs?: number\n\n /** Feature options to extract during audio processing */\n features?: AudioFeaturesOptions\n\n /** Callback function to handle audio stream data */\n onAudioStream?: (_: AudioDataEvent) => Promise<void>\n\n /** Callback function to handle audio features extraction results */\n onAudioAnalysis?: (_: AudioAnalysisEvent) => Promise<void>\n\n /**\n * Configuration for audio output files\n *\n * Examples:\n * - Primary only (default): `{ primary: { enabled: true } }`\n * - Compressed only: `{ primary: { enabled: false }, compressed: { enabled: true, format: 'aac' } }`\n * - Both outputs: `{ compressed: { enabled: true } }`\n * - Streaming only: `{ primary: { enabled: false } }`\n */\n output?: OutputConfig\n\n /** Whether to automatically resume recording after an interruption (default is false) */\n autoResumeAfterInterruption?: boolean\n\n /** Optional callback to handle recording interruptions */\n onRecordingInterrupted?: (_: RecordingInterruptionEvent) => void\n\n /**\n * Maximum cumulative active recording duration, in milliseconds.\n *\n * Paused time does not count. Set to undefined, 0, or a negative value to disable.\n */\n maxDurationMs?: number\n\n /**\n * Stop recording automatically when maxDurationMs is reached.\n *\n * Defaults to false. The MaxDurationReached event is emitted before the stop request.\n * The automatic stop result is not returned to onMaxDurationReached; use the\n * event and stream callbacks for immediate UI updates.\n */\n autoStopOnMaxDuration?: boolean\n\n /**\n * Optional callback invoked when maxDurationMs is reached.\n *\n * If autoStopOnMaxDuration is true, this callback is invoked before the\n * recorder finishes stopping. The final stop result is not passed here.\n */\n onMaxDurationReached?: (_: MaxDurationReachedEvent) => void\n\n /** Optional directory path where output files will be saved */\n outputDirectory?: string // If not provided, uses default app directory\n /** Optional filename for the recording (uses UUID if not provided) */\n filename?: string // If not provided, uses UUID\n\n /** ID of the device to use for recording (if not specified, uses default) */\n deviceId?: string\n\n /** How to handle device disconnection during recording */\n deviceDisconnectionBehavior?: DeviceDisconnectionBehaviorType\n\n /**\n * Buffer duration in seconds. Controls the size of audio buffers\n * used during recording. Smaller values reduce latency but increase\n * CPU usage. Larger values improve efficiency but increase latency.\n *\n * Platform Notes:\n * - iOS/macOS: Minimum effective 0.1s, uses accumulation below\n * - Android: Respects all sizes within hardware limits\n * - Web: Fully configurable\n *\n * Default: undefined (uses platform default ~23ms at 44.1kHz)\n * Recommended: 0.01 - 0.5 seconds\n * Optimal iOS: >= 0.1 seconds\n */\n bufferDurationSeconds?: number\n\n /**\n * Format for the audio stream data delivered to `onAudioStream`.\n *\n * - `'raw'` (default): base64-encoded PCM bytes on native, Float32Array on web\n * - `'float32'`: Float32Array with samples in [-1, 1] on all platforms.\n * Eliminates base64 encode/decode overhead on the native bridge.\n * Android (new arch): delivered as Float32Array via JSI.\n * iOS: delivered as regular Array<number>, normalized to Float32Array in JS.\n *\n * @default 'raw'\n */\n streamFormat?: 'float32' | 'raw'\n}\n\nexport interface NotificationConfig {\n /** Title of the notification */\n title?: string\n\n /** Main text content of the notification */\n text?: string\n\n /** Icon to be displayed in the notification (resource name or URI) */\n icon?: string\n\n /** Android-specific notification configuration */\n android?: {\n /** Unique identifier for the notification channel */\n channelId?: string\n\n /** User-visible name of the notification channel */\n channelName?: string\n\n /** User-visible description of the notification channel */\n channelDescription?: string\n\n /** Unique identifier for this notification */\n notificationId?: number\n\n /** List of actions that can be performed from the notification */\n actions?: NotificationAction[]\n\n /** Configuration for the waveform visualization in the notification */\n waveform?: WaveformConfig\n\n /** Color of the notification LED (if device supports it) */\n lightColor?: string\n\n /** Priority of the notification (affects how it's displayed) */\n priority?: 'min' | 'low' | 'default' | 'high' | 'max'\n\n /** Accent color for the notification (used for the app icon and buttons) */\n accentColor?: string\n\n /** Whether to show pause/resume actions in the notification (default: true) */\n showPauseResumeActions?: boolean\n }\n\n /** iOS-specific notification configuration */\n ios?: {\n /** Identifier for the notification category (used for grouping similar notifications) */\n categoryIdentifier?: string\n }\n}\n\nexport interface NotificationAction {\n /** Display title for the action */\n title: string\n\n /** Unique identifier for the action */\n identifier: string\n\n /** Icon to be displayed for the action (Android only) */\n icon?: string\n}\n\nexport interface WaveformConfig {\n /** The color of the waveform (e.g., \"#FFFFFF\" for white) */\n color?: string // The color of the waveform (e.g., \"#FFFFFF\" for white)\n /** Opacity of the waveform (0.0 - 1.0) */\n opacity?: number // Opacity of the waveform (0.0 - 1.0)\n /** Width of the waveform line (default: 1.5) */\n strokeWidth?: number // Width of the waveform line (default: 1.5)\n /** Drawing style: \"stroke\" for outline, \"fill\" for solid */\n style?: 'stroke' | 'fill' // Drawing style: \"stroke\" for outline, \"fill\" for solid\n /** Whether to mirror the waveform (symmetrical display) */\n mirror?: boolean // Whether to mirror the waveform (symmetrical display)\n /** Height of the waveform view in dp (default: 64) */\n height?: number // Height of the waveform view in dp (default: 64)\n}\n\nexport interface ExtractAudioDataOptions {\n /** URI of the audio file to extract data from */\n fileUri: string\n /** Start time in milliseconds (for time-based range) */\n startTimeMs?: number\n /** End time in milliseconds (for time-based range) */\n endTimeMs?: number\n /** Start position in bytes (for byte-based range) */\n position?: number\n /** Length in bytes to extract (for byte-based range) */\n length?: number\n /** Include normalized audio data in [-1, 1] range */\n includeNormalizedData?: boolean\n /** Include base64 encoded string representation of the audio data */\n includeBase64Data?: boolean\n /** Include WAV header in the PCM data (makes it a valid WAV file) */\n includeWavHeader?: boolean\n /** Logger for debugging - can pass console directly. */\n logger?: ConsoleLike\n /** Compute the checksum of the PCM data */\n computeChecksum?: boolean\n /** Target config for the normalized audio (Android and Web) */\n decodingOptions?: DecodingConfig\n}\n\nexport interface ExtractedAudioData {\n /** Raw PCM audio data */\n pcmData: Uint8Array\n /** Normalized audio data in [-1, 1] range (when includeNormalizedData is true) */\n normalizedData?: Float32Array\n /** Base64 encoded string representation of the audio data (when includeBase64Data is true) */\n base64Data?: string\n /** Sample rate in Hz (e.g., 44100, 48000) */\n sampleRate: number\n /** Number of audio channels (1 for mono, 2 for stereo) */\n channels: number\n /** Bits per sample (8, 16, or 32) */\n bitDepth: BitDepth\n /** Duration of the audio in milliseconds */\n durationMs: number\n /** PCM format identifier (e.g., \"pcm_16bit\") */\n format: PCMFormat\n /** Total number of audio samples per channel */\n samples: number\n /** Whether the pcmData includes a WAV header */\n hasWavHeader?: boolean\n /** CRC32 Checksum of PCM data */\n checksum?: number\n}\n\nexport interface UseAudioRecorderState {\n /**\n * Prepares recording with the specified configuration without starting it.\n *\n * This method eliminates the latency between calling startRecording and the actual recording beginning.\n * It pre-initializes all audio resources, requests permissions, and sets up audio sessions in advance,\n * allowing for true zero-latency recording start when startRecording is called later.\n *\n * Technical benefits:\n * - Eliminates audio pipeline initialization delay (50-300ms depending on platform)\n * - Pre-allocates audio buffers to avoid memory allocation during recording start\n * - Initializes audio hardware in advance (particularly important on iOS)\n * - Requests and verifies permissions before the critical recording moment\n *\n * Use this method when:\n * - You need zero-latency recording start (e.g., voice commands, musical applications)\n * - You're building time-sensitive applications where missing initial audio would be problematic\n * - You want to prepare resources during app initialization, screen loading, or preceding user interaction\n * - You need to ensure recording starts reliably and instantly on all platforms\n *\n * @param config - The recording configuration, identical to what you would pass to startRecording\n * @returns A promise that resolves when preparation is complete\n *\n * @example\n * // Prepare during component mounting\n * useEffect(() => {\n * prepareRecording({\n * sampleRate: 44100,\n * channels: 1,\n * encoding: 'pcm_16bit',\n * });\n * }, []);\n *\n * // Later when user taps record button, it starts with zero latency\n * const handleRecordPress = () => startRecording({\n * sampleRate: 44100,\n * channels: 1,\n * encoding: 'pcm_16bit',\n * });\n */\n prepareRecording: (_: RecordingConfig) => Promise<void>\n /** Starts recording with the specified configuration */\n startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>\n /** Stops the current recording and returns the recording data */\n stopRecording: () => Promise<AudioRecording | null>\n /** Pauses the current recording */\n pauseRecording: () => Promise<void>\n /** Resumes a paused recording */\n resumeRecording: () => Promise<void>\n /** Indicates whether recording is currently active */\n isRecording: boolean\n /** Indicates whether recording is in a paused state */\n isPaused: boolean\n /** Duration of the current recording in milliseconds */\n durationMs: number // Duration of the recording\n /** Size of the recorded audio in bytes */\n size: number // Size in bytes of the recorded audio\n /** Information about compression if enabled */\n compression?: CompressionInfo\n /** Configured maximum active recording duration in milliseconds, if enabled */\n maxDurationMs?: number\n /** Whether the current recording session has reached the configured maximum duration */\n maxDurationReached?: boolean\n /** Analysis data for the recording if processing was enabled */\n analysisData?: AudioAnalysis // Analysis data for the recording depending on enableProcessing flag\n /** Optional callback to handle recording interruptions */\n onRecordingInterrupted?: (_: RecordingInterruptionEvent) => void\n /** Optional callback invoked when maxDurationMs is reached */\n onMaxDurationReached?: (_: MaxDurationReachedEvent) => void\n}\n\n/**\n * Represents an event emitted during the trimming process to report progress.\n */\nexport interface TrimProgressEvent {\n /**\n * The percentage of the trimming process that has been completed, ranging from 0 to 100.\n */\n progress: number\n\n /**\n * The number of bytes that have been processed so far. This is optional and may not be provided in all implementations.\n */\n bytesProcessed?: number\n\n /**\n * The total number of bytes to process. This is optional and may not be provided in all implementations.\n */\n totalBytes?: number\n}\n\n/**\n * Defines a time range in milliseconds for trimming operations.\n */\nexport interface TimeRange {\n /**\n * The start time of the range in milliseconds.\n */\n startTimeMs: number\n\n /**\n * The end time of the range in milliseconds.\n */\n endTimeMs: number\n}\n\n/**\n * Options for configuring the audio trimming operation.\n */\nexport interface TrimAudioOptions {\n /**\n * The URI of the audio file to trim.\n */\n fileUri: string\n\n /**\n * The mode of trimming to apply.\n * - `'single'`: Trims the audio to a single range defined by `startTimeMs` and `endTimeMs`.\n * - `'keep'`: Keeps the specified `ranges` and removes all other portions of the audio.\n * - `'remove'`: Removes the specified `ranges` and keeps the remaining portions of the audio.\n * @default 'single'\n */\n mode?: 'single' | 'keep' | 'remove'\n\n /**\n * An array of time ranges to keep or remove, depending on the `mode`.\n * - Required for `'keep'` and `'remove'` modes.\n * - Ignored when `mode` is `'single'`.\n */\n ranges?: TimeRange[]\n\n /**\n * The start time in milliseconds for the `'single'` mode.\n * - If not provided, trimming starts from the beginning of the audio (0 ms).\n */\n startTimeMs?: number\n\n /**\n * The end time in milliseconds for the `'single'` mode.\n * - If not provided, trimming extends to the end of the audio.\n */\n endTimeMs?: number\n\n /**\n * The name of the output file. If not provided, a default name will be generated.\n */\n outputFileName?: string\n\n /**\n * Configuration for the output audio format.\n */\n outputFormat?: {\n /**\n * The format of the output audio file.\n * - `'wav'`: Waveform Audio File Format (uncompressed).\n * - `'aac'`: Advanced Audio Coding (compressed). Not supported on web platforms.\n * - `'opus'`: Opus Interactive Audio Codec (compressed).\n */\n format: 'wav' | 'aac' | 'opus'\n\n /**\n * The sample rate of the output audio in Hertz (Hz).\n * - If not provided, the input audio's sample rate is used.\n */\n sampleRate?: number\n\n /**\n * The number of channels in the output audio (e.g., 1 for mono, 2 for stereo).\n * - If not provided, the input audio's channel count is used.\n */\n channels?: number\n\n /**\n * The bit depth of the output audio, applicable to PCM formats like `'wav'`.\n * - If not provided, the input audio's bit depth is used.\n */\n bitDepth?: number\n\n /**\n * The bitrate of the output audio in bits per second, applicable to compressed formats like `'aac'`.\n * - If not provided, a default bitrate is used based on the format.\n */\n bitrate?: number\n }\n\n /**\n * Options for decoding the input audio file.\n * - See `DecodingConfig` for details.\n */\n decodingOptions?: DecodingConfig\n}\n\n/**\n * Result of the audio trimming operation.\n */\nexport interface TrimAudioResult {\n /**\n * The URI of the trimmed audio file.\n */\n uri: string\n\n /**\n * The filename of the trimmed audio file.\n */\n filename: string\n\n /**\n * The duration of the trimmed audio in milliseconds.\n */\n durationMs: number\n\n /**\n * The size of the trimmed audio file in bytes.\n */\n size: number\n\n /**\n * The sample rate of the trimmed audio in Hertz (Hz).\n */\n sampleRate: number\n\n /**\n * The number of channels in the trimmed audio (e.g., 1 for mono, 2 for stereo).\n */\n channels: number\n\n /**\n * The bit depth of the trimmed audio, applicable to PCM formats like `'wav'`.\n */\n bitDepth: number\n\n /**\n * The MIME type of the trimmed audio file (e.g., `'audio/wav'`, `'audio/mpeg'`).\n */\n mimeType: string\n\n /**\n * Information about compression if the output format is compressed.\n */\n compression?: {\n /**\n * The format of the compression (e.g., `'aac'`, `'mp3'`, `'opus'`).\n */\n format: string\n\n /**\n * The bitrate of the compressed audio in bits per second.\n */\n bitrate: number\n\n /**\n * The size of the compressed audio file in bytes.\n */\n size: number\n }\n\n /**\n * Information about the processing time.\n */\n processingInfo?: {\n /**\n * The time it took to process the audio in milliseconds.\n */\n durationMs: number\n }\n}\n"]}
@@ -32,7 +32,13 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
32
32
  totalCompressedSize = 0;
33
33
  maxBufferSize;
34
34
  eventCallback;
35
- constructor({ audioWorkletUrl, featuresExtratorUrl, logger, maxBufferSize = constants_1.DEFAULT_MAX_BUFFER_SIZE, }) {
35
+ moduleEventEmitter;
36
+ maxDurationTimer;
37
+ maxDurationTargetMs = 0;
38
+ maxDurationAccumulatedActiveMs = 0;
39
+ maxDurationSegmentStartMs = 0;
40
+ maxDurationReached = false;
41
+ constructor({ audioWorkletUrl, featuresExtratorUrl, logger, maxBufferSize = constants_1.DEFAULT_MAX_BUFFER_SIZE, emitEvent, }) {
36
42
  const mockNativeModule = {
37
43
  addListener: () => { },
38
44
  removeListeners: () => { },
@@ -59,6 +65,100 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
59
65
  this.audioWorkletUrl = audioWorkletUrl;
60
66
  this.featuresExtratorUrl = featuresExtratorUrl;
61
67
  this.maxBufferSize = maxBufferSize;
68
+ this.moduleEventEmitter = emitEvent;
69
+ }
70
+ emitModuleEvent(eventName, params) {
71
+ this.emit(eventName, params);
72
+ this.moduleEventEmitter?.(eventName, params);
73
+ }
74
+ resetMaxDurationState(preserveReached = false) {
75
+ if (this.maxDurationTimer) {
76
+ clearTimeout(this.maxDurationTimer);
77
+ this.maxDurationTimer = undefined;
78
+ }
79
+ this.maxDurationSegmentStartMs = 0;
80
+ if (!preserveReached || !this.maxDurationReached) {
81
+ this.maxDurationTargetMs = 0;
82
+ this.maxDurationAccumulatedActiveMs = 0;
83
+ this.maxDurationReached = false;
84
+ }
85
+ }
86
+ getMaxDurationActiveMs(now = performance.now()) {
87
+ if (this.maxDurationSegmentStartMs <= 0) {
88
+ return this.maxDurationAccumulatedActiveMs;
89
+ }
90
+ return (this.maxDurationAccumulatedActiveMs +
91
+ (now - this.maxDurationSegmentStartMs));
92
+ }
93
+ scheduleMaxDurationTimer() {
94
+ if (this.maxDurationTargetMs <= 0 ||
95
+ this.maxDurationReached ||
96
+ !this.isRecording ||
97
+ this.isPaused) {
98
+ return;
99
+ }
100
+ if (this.maxDurationTimer) {
101
+ clearTimeout(this.maxDurationTimer);
102
+ }
103
+ const remainingMs = Math.max(0, this.maxDurationTargetMs - this.getMaxDurationActiveMs());
104
+ this.maxDurationTimer = setTimeout(() => {
105
+ this.maxDurationTimer = undefined;
106
+ this.emitMaxDurationReached();
107
+ }, remainingMs);
108
+ }
109
+ startMaxDurationTimer(recordingConfig) {
110
+ this.resetMaxDurationState();
111
+ const targetMs = Number(recordingConfig.maxDurationMs ?? 0);
112
+ if (!Number.isFinite(targetMs) || targetMs <= 0) {
113
+ return;
114
+ }
115
+ this.maxDurationTargetMs = targetMs;
116
+ this.maxDurationSegmentStartMs = performance.now();
117
+ this.scheduleMaxDurationTimer();
118
+ }
119
+ pauseMaxDurationTimer() {
120
+ if (this.maxDurationTimer) {
121
+ clearTimeout(this.maxDurationTimer);
122
+ this.maxDurationTimer = undefined;
123
+ }
124
+ if (this.maxDurationSegmentStartMs > 0) {
125
+ this.maxDurationAccumulatedActiveMs = this.getMaxDurationActiveMs();
126
+ this.maxDurationSegmentStartMs = 0;
127
+ }
128
+ }
129
+ resumeMaxDurationTimer() {
130
+ if (this.maxDurationTargetMs <= 0 || this.maxDurationReached) {
131
+ return;
132
+ }
133
+ this.maxDurationSegmentStartMs = performance.now();
134
+ this.scheduleMaxDurationTimer();
135
+ }
136
+ emitMaxDurationReached() {
137
+ if (this.maxDurationTargetMs <= 0 || this.maxDurationReached) {
138
+ return;
139
+ }
140
+ const durationMs = Math.round(this.getMaxDurationActiveMs());
141
+ this.maxDurationReached = true;
142
+ const autoStopped = !!this.recordingConfig?.autoStopOnMaxDuration;
143
+ this.emitModuleEvent('MaxDurationReached', {
144
+ durationMs,
145
+ maxDurationMs: this.maxDurationTargetMs,
146
+ overrunMs: Math.max(0, durationMs - this.maxDurationTargetMs),
147
+ streamUuid: this.streamUuid ?? undefined,
148
+ autoStopped,
149
+ });
150
+ if (autoStopped) {
151
+ this.stopRecording().catch((error) => {
152
+ this.logger?.error('Error auto-stopping on max duration:', error);
153
+ });
154
+ }
155
+ }
156
+ flushExpiredMaxDuration() {
157
+ if (this.maxDurationTargetMs > 0 &&
158
+ !this.maxDurationReached &&
159
+ this.getMaxDurationActiveMs() >= this.maxDurationTargetMs) {
160
+ this.emitMaxDurationReached();
161
+ }
62
162
  }
63
163
  // Utility to handle user media stream
64
164
  async getMediaStream() {
@@ -188,6 +288,7 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
188
288
  this.currentInterval = recordingConfig.interval ?? 1000;
189
289
  this.currentIntervalAnalysis = recordingConfig.intervalAnalysis ?? 500;
190
290
  this.lastEmittedAnalysisTime = Date.now();
291
+ this.startMaxDurationTimer(recordingConfig);
191
292
  // Use custom filename if provided, otherwise fallback to timestamp
192
293
  if (recordingConfig.filename) {
193
294
  // Remove any existing extension from the filename
@@ -224,9 +325,10 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
224
325
  // Update local state if the interruption should pause recording
225
326
  if (event.isPaused) {
226
327
  this.isPaused = true;
328
+ this.pausedTime = Date.now();
329
+ this.pauseMaxDurationTimer();
227
330
  // If this is a device disconnection, handle according to behavior setting
228
331
  if (event.reason === 'deviceDisconnected') {
229
- this.pausedTime = Date.now();
230
332
  // Check if we should try fallback to another device
231
333
  if (this.recordingConfig?.deviceDisconnectionBehavior ===
232
334
  'fallback') {
@@ -235,7 +337,7 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
235
337
  this.handleDeviceFallback().catch((error) => {
236
338
  // If fallback fails, emit warning
237
339
  this.logger?.error('Device fallback failed:', error);
238
- this.emit('onRecordingInterrupted', {
340
+ this.emitModuleEvent('onRecordingInterrupted', {
239
341
  reason: 'deviceSwitchFailed',
240
342
  isPaused: true,
241
343
  timestamp: Date.now(),
@@ -246,17 +348,17 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
246
348
  else {
247
349
  // Just warn about disconnection if fallback not enabled
248
350
  this.logger?.warn('Device disconnected - recording paused automatically');
249
- this.emit('onRecordingInterrupted', event);
351
+ this.emitModuleEvent('onRecordingInterrupted', event);
250
352
  }
251
353
  }
252
354
  else {
253
355
  // For other interruption types, just emit the event
254
- this.emit('onRecordingInterrupted', event);
356
+ this.emitModuleEvent('onRecordingInterrupted', event);
255
357
  }
256
358
  }
257
359
  else {
258
360
  // If not causing a pause, just forward the event
259
- this.emit('onRecordingInterrupted', event);
361
+ this.emitModuleEvent('onRecordingInterrupted', event);
260
362
  }
261
363
  }
262
364
  /**
@@ -278,7 +380,7 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
278
380
  * Handler for audio analysis events from the WebRecorder
279
381
  */
280
382
  customRecorderAnalysisCallback(audioAnalysisData) {
281
- this.emit('AudioAnalysis', audioAnalysisData);
383
+ this.emitModuleEvent('AudioAnalysis', audioAnalysisData);
282
384
  }
283
385
  // Get recording duration
284
386
  getRecordingDuration() {
@@ -306,6 +408,7 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
306
408
  else {
307
409
  this.currentDurationMs += chunkDurationMs;
308
410
  }
411
+ this.flushExpiredMaxDuration();
309
412
  const audioEventPayload = {
310
413
  fileUri,
311
414
  mimeType: `audio/${this.extension}`,
@@ -324,7 +427,7 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
324
427
  }
325
428
  : undefined,
326
429
  };
327
- this.emit('AudioData', audioEventPayload);
430
+ this.emitModuleEvent('AudioData', audioEventPayload);
328
431
  }
329
432
  // Stop recording
330
433
  async stopRecording() {
@@ -333,6 +436,7 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
333
436
  }
334
437
  this.logger?.debug('Starting stop process');
335
438
  try {
439
+ this.pauseMaxDurationTimer();
336
440
  const { compressedBlob, uncompressedBlob } = await this.customRecorder.stop();
337
441
  this.isRecording = false;
338
442
  this.isPaused = false;
@@ -400,6 +504,7 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
400
504
  this.totalCompressedSize = 0;
401
505
  this.lastEmittedCompressionSize = 0;
402
506
  this.audioChunks = [];
507
+ this.resetMaxDurationState(true);
403
508
  return result;
404
509
  }
405
510
  catch (error) {
@@ -422,12 +527,14 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
422
527
  }
423
528
  this.isPaused = true;
424
529
  this.pausedTime = Date.now();
530
+ this.pauseMaxDurationTimer();
425
531
  }
426
532
  catch (error) {
427
533
  this.logger?.error('Error in pauseRecording', error);
428
534
  // Even if the pause operation failed, make sure our state is consistent
429
535
  this.isPaused = true;
430
536
  this.pausedTime = Date.now();
537
+ this.pauseMaxDurationTimer();
431
538
  }
432
539
  }
433
540
  // Resume recording
@@ -455,7 +562,8 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
455
562
  const pauseDuration = Date.now() - this.pausedTime;
456
563
  this.recordingStartTime += pauseDuration;
457
564
  this.pausedTime = 0;
458
- this.emit('onRecordingInterrupted', {
565
+ this.resumeMaxDurationTimer();
566
+ this.emitModuleEvent('onRecordingInterrupted', {
459
567
  reason: 'userResumed',
460
568
  isPaused: false,
461
569
  timestamp: Date.now(),
@@ -464,7 +572,7 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
464
572
  catch (error) {
465
573
  this.logger?.error('Resume failed:', error);
466
574
  // Fallback to emitting a general failure if resume fails unexpectedly
467
- this.emit('onRecordingInterrupted', {
575
+ this.emitModuleEvent('onRecordingInterrupted', {
468
576
  reason: 'resumeFailed', // Use a more specific reason
469
577
  isPaused: true, // Remain paused if resume fails
470
578
  timestamp: Date.now(),
@@ -474,6 +582,7 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
474
582
  }
475
583
  // Get current status
476
584
  status() {
585
+ this.flushExpiredMaxDuration();
477
586
  const durationMs = this.getRecordingDuration();
478
587
  const status = {
479
588
  isRecording: this.isRecording,
@@ -494,6 +603,8 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
494
603
  compressedFileUri: `${this.streamUuid}.webm`,
495
604
  }
496
605
  : undefined,
606
+ maxDurationMs: this.maxDurationTargetMs > 0 ? this.maxDurationTargetMs : undefined,
607
+ maxDurationReached: this.maxDurationReached,
497
608
  };
498
609
  return status;
499
610
  }
@@ -544,7 +655,7 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
544
655
  // Try to get a fallback device
545
656
  const fallbackDeviceInfo = await this.getFallbackDevice();
546
657
  if (!fallbackDeviceInfo) {
547
- this.emit('onRecordingInterrupted', {
658
+ this.emitModuleEvent('onRecordingInterrupted', {
548
659
  reason: 'deviceSwitchFailed',
549
660
  isPaused: true,
550
661
  timestamp: Date.now(),
@@ -592,6 +703,7 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
592
703
  // Update recording state
593
704
  this.isPaused = false;
594
705
  this.recordingStartTime = Date.now();
706
+ this.resumeMaxDurationTimer();
595
707
  // Restore size counters to maintain continuity
596
708
  this.currentSize = previousTotalSize;
597
709
  this.lastEmittedSize = previousLastEmittedSize;
@@ -609,7 +721,7 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
609
721
  catch (error) {
610
722
  this.logger?.error('Failed to start recording with fallback device', error);
611
723
  this.isPaused = true;
612
- this.emit('onRecordingInterrupted', {
724
+ this.emitModuleEvent('onRecordingInterrupted', {
613
725
  reason: 'deviceSwitchFailed',
614
726
  isPaused: true,
615
727
  timestamp: Date.now(),
@@ -621,7 +733,7 @@ class AudioStudioWeb extends expo_modules_core_1.LegacyEventEmitter {
621
733
  catch (error) {
622
734
  this.logger?.error('Failed to use fallback device', error);
623
735
  this.isPaused = true;
624
- this.emit('onRecordingInterrupted', {
736
+ this.emitModuleEvent('onRecordingInterrupted', {
625
737
  reason: 'deviceSwitchFailed',
626
738
  isPaused: true,
627
739
  timestamp: Date.now(),