@siteed/audio-studio 3.0.5 → 3.1.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.
- package/CHANGELOG.md +19 -1
- package/README.md +108 -41
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioFinalMetadataContractInstrumentedTest.kt +190 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderInstrumentedTest.kt +29 -83
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderPerformanceInstrumentedTest.kt +17 -1
- package/android/src/androidTest/java/net/siteed/audiostudio/OpusRangeDecodeRegressionInstrumentedTest.kt +186 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioProcessor.kt +473 -380
- package/android/src/main/java/net/siteed/audiostudio/AudioRecorderManager.kt +74 -22
- package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +86 -19
- package/android/src/main/java/net/siteed/audiostudio/AudioTrimmer.kt +174 -212
- package/android/src/main/java/net/siteed/audiostudio/EventSender.kt +6 -0
- package/android/src/test/java/net/siteed/audiostudio/AndroidCallStateTest.kt +37 -0
- package/android/src/test/java/net/siteed/audiostudio/AndroidEventEmitterTest.kt +28 -0
- package/android/src/test/java/net/siteed/audiostudio/InterruptionAutoResumePolicyTest.kt +49 -0
- package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
- package/build/cjs/AudioAnalysis/extractPreview.js +92 -15
- package/build/cjs/AudioAnalysis/extractPreview.js.map +1 -1
- package/build/cjs/AudioAnalysis/extractPreviewBars.js +134 -0
- package/build/cjs/AudioAnalysis/extractPreviewBars.js.map +1 -0
- package/build/cjs/AudioStudio.types.js.map +1 -1
- package/build/cjs/errors/AudioExtractionError.js +127 -0
- package/build/cjs/errors/AudioExtractionError.js.map +1 -0
- package/build/cjs/index.js +6 -1
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/useAudioRecorder.js +36 -18
- package/build/cjs/useAudioRecorder.js.map +1 -1
- package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
- package/build/esm/AudioAnalysis/extractPreview.js +92 -15
- package/build/esm/AudioAnalysis/extractPreview.js.map +1 -1
- package/build/esm/AudioAnalysis/extractPreviewBars.js +128 -0
- package/build/esm/AudioAnalysis/extractPreviewBars.js.map +1 -0
- package/build/esm/AudioStudio.types.js.map +1 -1
- package/build/esm/errors/AudioExtractionError.js +122 -0
- package/build/esm/errors/AudioExtractionError.js.map +1 -0
- package/build/esm/index.js +2 -0
- package/build/esm/index.js.map +1 -1
- package/build/esm/useAudioRecorder.js +36 -18
- package/build/esm/useAudioRecorder.js.map +1 -1
- package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts +79 -0
- package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -1
- package/build/types/AudioAnalysis/extractPreview.d.ts +2 -2
- package/build/types/AudioAnalysis/extractPreview.d.ts.map +1 -1
- package/build/types/AudioAnalysis/extractPreviewBars.d.ts +12 -0
- package/build/types/AudioAnalysis/extractPreviewBars.d.ts.map +1 -0
- package/build/types/AudioStudio.types.d.ts +14 -1
- package/build/types/AudioStudio.types.d.ts.map +1 -1
- package/build/types/errors/AudioExtractionError.d.ts +24 -0
- package/build/types/errors/AudioExtractionError.d.ts.map +1 -0
- package/build/types/index.d.ts +3 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/useAudioRecorder.d.ts.map +1 -1
- package/ios/AudioProcessor.swift +99 -0
- package/ios/AudioStreamManager.swift +79 -15
- package/ios/AudioStudioModule.swift +63 -0
- package/ios/AudioStudioTests/CompressedOnlyOutputTests.swift +41 -1
- package/package.json +7 -7
- package/src/AudioAnalysis/AudioAnalysis.types.ts +82 -0
- package/src/AudioAnalysis/extractPreview.ts +118 -17
- package/src/AudioAnalysis/extractPreviewBars.ts +193 -0
- package/src/AudioStudio.types.ts +15 -1
- package/src/errors/AudioExtractionError.ts +167 -0
- package/src/index.ts +10 -0
- package/src/useAudioRecorder.tsx +36 -14
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioStudio.types.js","sourceRoot":"","sources":["../../src/AudioStudio.types.ts"],"names":[],"mappings":";;;AA2UA,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 /** Analysis data for the recording if processing was enabled */\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 /** 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":";;;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"]}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AudioExtractionError = void 0;
|
|
4
|
+
exports.mapExtractionError = mapExtractionError;
|
|
5
|
+
class AudioExtractionError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
nativeMessage;
|
|
8
|
+
fileUri;
|
|
9
|
+
constructor(payload) {
|
|
10
|
+
super(payload.message);
|
|
11
|
+
this.name = 'AudioExtractionError';
|
|
12
|
+
this.code = payload.code;
|
|
13
|
+
this.nativeMessage = payload.nativeMessage;
|
|
14
|
+
this.fileUri = payload.fileUri;
|
|
15
|
+
}
|
|
16
|
+
toJSON() {
|
|
17
|
+
return {
|
|
18
|
+
code: this.code,
|
|
19
|
+
message: this.message,
|
|
20
|
+
nativeMessage: this.nativeMessage,
|
|
21
|
+
fileUri: this.fileUri,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.AudioExtractionError = AudioExtractionError;
|
|
26
|
+
function getNativeMessage(err) {
|
|
27
|
+
if (err instanceof Error)
|
|
28
|
+
return err.message;
|
|
29
|
+
if (typeof err === 'string')
|
|
30
|
+
return err;
|
|
31
|
+
try {
|
|
32
|
+
return JSON.stringify(err) ?? String(err);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return String(err);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function getNativeCode(err) {
|
|
39
|
+
if (err && typeof err === 'object' && 'code' in err) {
|
|
40
|
+
const code = err.code;
|
|
41
|
+
if (typeof code === 'string')
|
|
42
|
+
return code;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
function mapNativeCode(code) {
|
|
47
|
+
if (!code)
|
|
48
|
+
return null;
|
|
49
|
+
const normalized = code.toUpperCase();
|
|
50
|
+
if (normalized.includes('FILE_NOT_FOUND') ||
|
|
51
|
+
normalized === 'ENOENT' ||
|
|
52
|
+
normalized.includes('NO_SUCH_FILE')) {
|
|
53
|
+
return 'file_not_found';
|
|
54
|
+
}
|
|
55
|
+
if (normalized.includes('PERMISSION') ||
|
|
56
|
+
normalized === 'EACCES' ||
|
|
57
|
+
normalized.includes('NOT_AUTHORIZED')) {
|
|
58
|
+
return 'permission_denied';
|
|
59
|
+
}
|
|
60
|
+
if (normalized.includes('UNSUPPORTED') ||
|
|
61
|
+
normalized.includes('NO_SUITABLE_CODEC')) {
|
|
62
|
+
return 'unsupported_codec';
|
|
63
|
+
}
|
|
64
|
+
if (normalized.includes('INVALID_RANGE') ||
|
|
65
|
+
normalized.includes('INVALID_HEADER') ||
|
|
66
|
+
normalized.includes('MALFORMED') ||
|
|
67
|
+
normalized.includes('CORRUPT')) {
|
|
68
|
+
return 'malformed_file';
|
|
69
|
+
}
|
|
70
|
+
if (normalized.includes('PROCESSING_ERROR') ||
|
|
71
|
+
normalized.includes('AUDIO_READ_ERROR') ||
|
|
72
|
+
normalized.includes('DECODE')) {
|
|
73
|
+
return 'decode_failed';
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Map a thrown native/JS value into an AudioExtractionError with a stable code.
|
|
79
|
+
* Heuristics inspect message text and known native error codes.
|
|
80
|
+
*/
|
|
81
|
+
function mapExtractionError(err, fileUri) {
|
|
82
|
+
if (err instanceof AudioExtractionError)
|
|
83
|
+
return err;
|
|
84
|
+
const nativeMessage = getNativeMessage(err);
|
|
85
|
+
const lower = nativeMessage.toLowerCase();
|
|
86
|
+
let code = mapNativeCode(getNativeCode(err)) ?? 'unknown';
|
|
87
|
+
if (code === 'unknown' &&
|
|
88
|
+
(lower.includes('unsupported') ||
|
|
89
|
+
lower.includes('not supported') ||
|
|
90
|
+
lower.includes('no suitable codec') ||
|
|
91
|
+
lower.includes('no track'))) {
|
|
92
|
+
code = 'unsupported_codec';
|
|
93
|
+
}
|
|
94
|
+
else if (code === 'unknown' &&
|
|
95
|
+
(lower.includes('not found') ||
|
|
96
|
+
lower.includes('no such file') ||
|
|
97
|
+
lower.includes('does not exist'))) {
|
|
98
|
+
code = 'file_not_found';
|
|
99
|
+
}
|
|
100
|
+
else if (code === 'unknown' &&
|
|
101
|
+
(lower.includes('permission') ||
|
|
102
|
+
lower.includes('denied') ||
|
|
103
|
+
lower.includes('not authorized'))) {
|
|
104
|
+
code = 'permission_denied';
|
|
105
|
+
}
|
|
106
|
+
else if (code === 'unknown' &&
|
|
107
|
+
(lower.includes('malformed') ||
|
|
108
|
+
lower.includes('corrupt') ||
|
|
109
|
+
lower.includes('invalid header') ||
|
|
110
|
+
lower.includes('invalid wav'))) {
|
|
111
|
+
code = 'malformed_file';
|
|
112
|
+
}
|
|
113
|
+
else if (code === 'unknown' &&
|
|
114
|
+
(lower.includes('decode') ||
|
|
115
|
+
lower.includes('codec') ||
|
|
116
|
+
lower.includes('mediaextractor') ||
|
|
117
|
+
lower.includes('avaudio'))) {
|
|
118
|
+
code = 'decode_failed';
|
|
119
|
+
}
|
|
120
|
+
return new AudioExtractionError({
|
|
121
|
+
code,
|
|
122
|
+
message: `Audio extraction failed (${code}): ${nativeMessage}`,
|
|
123
|
+
nativeMessage,
|
|
124
|
+
fileUri,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=AudioExtractionError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioExtractionError.js","sourceRoot":"","sources":["../../../src/errors/AudioExtractionError.ts"],"names":[],"mappings":";;;AA8GA,gDAwDC;AAnJD,MAAa,oBAAqB,SAAQ,KAAK;IAClC,IAAI,CAA0B;IAC9B,aAAa,CAAS;IACtB,OAAO,CAAS;IAEzB,YAAY,OAAoC;QAC5C,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QACtB,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAA;QAClC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;QACxB,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAA;QAC1C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAA;IAClC,CAAC;IAED,MAAM;QACF,OAAO;YACH,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,OAAO,EAAE,IAAI,CAAC,OAAO;SACxB,CAAA;IACL,CAAC;CACJ;AArBD,oDAqBC;AAED,SAAS,gBAAgB,CAAC,GAAY;IAClC,IAAI,GAAG,YAAY,KAAK;QAAE,OAAO,GAAG,CAAC,OAAO,CAAA;IAC5C,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAA;IAEvC,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAA;IAC7C,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,MAAM,CAAC,GAAG,CAAC,CAAA;IACtB,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CAAC,GAAY;IAC/B,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QAClD,MAAM,IAAI,GAAI,GAA0B,CAAC,IAAI,CAAA;QAC7C,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAA;IAC7C,CAAC;IACD,OAAO,SAAS,CAAA;AACpB,CAAC;AAED,SAAS,aAAa,CAClB,IAAwB;IAExB,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAEtB,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;IACrC,IACI,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QACrC,UAAU,KAAK,QAAQ;QACvB,UAAU,CAAC,QAAQ,CAAC,cAAc,CAAC,EACrC,CAAC;QACC,OAAO,gBAAgB,CAAA;IAC3B,CAAC;IACD,IACI,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC;QACjC,UAAU,KAAK,QAAQ;QACvB,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EACvC,CAAC;QACC,OAAO,mBAAmB,CAAA;IAC9B,CAAC;IACD,IACI,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC;QAClC,UAAU,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAC1C,CAAC;QACC,OAAO,mBAAmB,CAAA;IAC9B,CAAC;IACD,IACI,UAAU,CAAC,QAAQ,CAAC,eAAe,CAAC;QACpC,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QACrC,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC;QAChC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,EAChC,CAAC;QACC,OAAO,gBAAgB,CAAA;IAC3B,CAAC;IACD,IACI,UAAU,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QACvC,UAAU,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QACvC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAC/B,CAAC;QACC,OAAO,eAAe,CAAA;IAC1B,CAAC;IAED,OAAO,IAAI,CAAA;AACf,CAAC;AAED;;;GAGG;AACH,SAAgB,kBAAkB,CAC9B,GAAY,EACZ,OAAgB;IAEhB,IAAI,GAAG,YAAY,oBAAoB;QAAE,OAAO,GAAG,CAAA;IAEnD,MAAM,aAAa,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAA;IAC3C,MAAM,KAAK,GAAG,aAAa,CAAC,WAAW,EAAE,CAAA;IAEzC,IAAI,IAAI,GAAG,aAAa,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,SAAS,CAAA;IACzD,IACI,IAAI,KAAK,SAAS;QAClB,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC;YAC1B,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC;YAC/B,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC;YACnC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,EACjC,CAAC;QACC,IAAI,GAAG,mBAAmB,CAAA;IAC9B,CAAC;SAAM,IACH,IAAI,KAAK,SAAS;QAClB,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC;YACxB,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC;YAC9B,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,EACvC,CAAC;QACC,IAAI,GAAG,gBAAgB,CAAA;IAC3B,CAAC;SAAM,IACH,IAAI,KAAK,SAAS;QAClB,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC;YACzB,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACxB,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,EACvC,CAAC;QACC,IAAI,GAAG,mBAAmB,CAAA;IAC9B,CAAC;SAAM,IACH,IAAI,KAAK,SAAS;QAClB,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC;YACxB,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC;YACzB,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAChC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,EACpC,CAAC;QACC,IAAI,GAAG,gBAAgB,CAAA;IAC3B,CAAC;SAAM,IACH,IAAI,KAAK,SAAS;QAClB,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACrB,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC;YACvB,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAChC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAChC,CAAC;QACC,IAAI,GAAG,eAAe,CAAA;IAC1B,CAAC;IAED,OAAO,IAAI,oBAAoB,CAAC;QAC5B,IAAI;QACJ,OAAO,EAAE,4BAA4B,IAAI,MAAM,aAAa,EAAE;QAC9D,aAAa;QACb,OAAO;KACV,CAAC,CAAA;AACN,CAAC","sourcesContent":["/**\n * Typed error class for audio extraction failures.\n * Wraps native module errors with stable codes consumers can switch on.\n */\nexport type AudioExtractionErrorCode =\n | 'unsupported_codec'\n | 'malformed_file'\n | 'decode_failed'\n | 'permission_denied'\n | 'file_not_found'\n | 'unknown'\n\nexport interface AudioExtractionErrorPayload {\n code: AudioExtractionErrorCode\n message: string\n nativeMessage?: string\n fileUri?: string\n}\n\nexport class AudioExtractionError extends Error {\n readonly code: AudioExtractionErrorCode\n readonly nativeMessage?: string\n readonly fileUri?: string\n\n constructor(payload: AudioExtractionErrorPayload) {\n super(payload.message)\n this.name = 'AudioExtractionError'\n this.code = payload.code\n this.nativeMessage = payload.nativeMessage\n this.fileUri = payload.fileUri\n }\n\n toJSON(): AudioExtractionErrorPayload {\n return {\n code: this.code,\n message: this.message,\n nativeMessage: this.nativeMessage,\n fileUri: this.fileUri,\n }\n }\n}\n\nfunction getNativeMessage(err: unknown): string {\n if (err instanceof Error) return err.message\n if (typeof err === 'string') return err\n\n try {\n return JSON.stringify(err) ?? String(err)\n } catch {\n return String(err)\n }\n}\n\nfunction getNativeCode(err: unknown): string | undefined {\n if (err && typeof err === 'object' && 'code' in err) {\n const code = (err as { code?: unknown }).code\n if (typeof code === 'string') return code\n }\n return undefined\n}\n\nfunction mapNativeCode(\n code: string | undefined\n): AudioExtractionErrorCode | null {\n if (!code) return null\n\n const normalized = code.toUpperCase()\n if (\n normalized.includes('FILE_NOT_FOUND') ||\n normalized === 'ENOENT' ||\n normalized.includes('NO_SUCH_FILE')\n ) {\n return 'file_not_found'\n }\n if (\n normalized.includes('PERMISSION') ||\n normalized === 'EACCES' ||\n normalized.includes('NOT_AUTHORIZED')\n ) {\n return 'permission_denied'\n }\n if (\n normalized.includes('UNSUPPORTED') ||\n normalized.includes('NO_SUITABLE_CODEC')\n ) {\n return 'unsupported_codec'\n }\n if (\n normalized.includes('INVALID_RANGE') ||\n normalized.includes('INVALID_HEADER') ||\n normalized.includes('MALFORMED') ||\n normalized.includes('CORRUPT')\n ) {\n return 'malformed_file'\n }\n if (\n normalized.includes('PROCESSING_ERROR') ||\n normalized.includes('AUDIO_READ_ERROR') ||\n normalized.includes('DECODE')\n ) {\n return 'decode_failed'\n }\n\n return null\n}\n\n/**\n * Map a thrown native/JS value into an AudioExtractionError with a stable code.\n * Heuristics inspect message text and known native error codes.\n */\nexport function mapExtractionError(\n err: unknown,\n fileUri?: string\n): AudioExtractionError {\n if (err instanceof AudioExtractionError) return err\n\n const nativeMessage = getNativeMessage(err)\n const lower = nativeMessage.toLowerCase()\n\n let code = mapNativeCode(getNativeCode(err)) ?? 'unknown'\n if (\n code === 'unknown' &&\n (lower.includes('unsupported') ||\n lower.includes('not supported') ||\n lower.includes('no suitable codec') ||\n lower.includes('no track'))\n ) {\n code = 'unsupported_codec'\n } else if (\n code === 'unknown' &&\n (lower.includes('not found') ||\n lower.includes('no such file') ||\n lower.includes('does not exist'))\n ) {\n code = 'file_not_found'\n } else if (\n code === 'unknown' &&\n (lower.includes('permission') ||\n lower.includes('denied') ||\n lower.includes('not authorized'))\n ) {\n code = 'permission_denied'\n } else if (\n code === 'unknown' &&\n (lower.includes('malformed') ||\n lower.includes('corrupt') ||\n lower.includes('invalid header') ||\n lower.includes('invalid wav'))\n ) {\n code = 'malformed_file'\n } else if (\n code === 'unknown' &&\n (lower.includes('decode') ||\n lower.includes('codec') ||\n lower.includes('mediaextractor') ||\n lower.includes('avaudio'))\n ) {\n code = 'decode_failed'\n }\n\n return new AudioExtractionError({\n code,\n message: `Audio extraction failed (${code}): ${nativeMessage}`,\n nativeMessage,\n fileUri,\n })\n}\n"]}
|
package/build/cjs/index.js
CHANGED
|
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.ExpoAudioStreamModule = exports.useSharedAudioRecorder = exports.useAudioRecorder = exports.MAX_DURATION_MS = exports.computeMelFrameWasm = exports.initMelStreamingWasm = exports.extractMelSpectrogram = exports.extractAudioData = exports.trimAudio = exports.extractPreview = exports.extractAudioAnalysis = exports.extractRawWavAnalysis = exports.AudioStudioModule = exports.AudioRecorderProvider = exports.setMelSpectrogramWasmUrl = exports.useAudioDevices = exports.audioDeviceManager = exports.AudioDeviceManager = exports.validateRecordingConfig = exports.getFallbackBitDepth = exports.getFallbackEncoding = exports.isBitDepthSupported = exports.isEncodingSupported = exports.getPlatformCapabilities = void 0;
|
|
21
|
+
exports.ExpoAudioStreamModule = exports.mapExtractionError = exports.AudioExtractionError = exports.useSharedAudioRecorder = exports.useAudioRecorder = exports.MAX_DURATION_MS = exports.computeMelFrameWasm = exports.initMelStreamingWasm = exports.extractMelSpectrogram = exports.extractAudioData = exports.trimAudio = exports.extractPreview = exports.extractAudioAnalysis = exports.extractRawWavAnalysis = exports.AudioStudioModule = exports.AudioRecorderProvider = exports.extractPreviewBars = exports.setMelSpectrogramWasmUrl = exports.useAudioDevices = exports.audioDeviceManager = exports.AudioDeviceManager = exports.validateRecordingConfig = exports.getFallbackBitDepth = exports.getFallbackEncoding = exports.isBitDepthSupported = exports.isEncodingSupported = exports.getPlatformCapabilities = void 0;
|
|
22
22
|
const extractAudioAnalysis_1 = require("./AudioAnalysis/extractAudioAnalysis");
|
|
23
23
|
Object.defineProperty(exports, "extractRawWavAnalysis", { enumerable: true, get: function () { return extractAudioAnalysis_1.extractRawWavAnalysis; } });
|
|
24
24
|
Object.defineProperty(exports, "extractAudioAnalysis", { enumerable: true, get: function () { return extractAudioAnalysis_1.extractAudioAnalysis; } });
|
|
@@ -61,6 +61,11 @@ var useAudioDevices_1 = require("./hooks/useAudioDevices");
|
|
|
61
61
|
Object.defineProperty(exports, "useAudioDevices", { enumerable: true, get: function () { return useAudioDevices_1.useAudioDevices; } });
|
|
62
62
|
var wasmConfig_1 = require("./AudioAnalysis/wasmConfig");
|
|
63
63
|
Object.defineProperty(exports, "setMelSpectrogramWasmUrl", { enumerable: true, get: function () { return wasmConfig_1.setMelSpectrogramWasmUrl; } });
|
|
64
|
+
var extractPreviewBars_1 = require("./AudioAnalysis/extractPreviewBars");
|
|
65
|
+
Object.defineProperty(exports, "extractPreviewBars", { enumerable: true, get: function () { return extractPreviewBars_1.extractPreviewBars; } });
|
|
66
|
+
var AudioExtractionError_1 = require("./errors/AudioExtractionError");
|
|
67
|
+
Object.defineProperty(exports, "AudioExtractionError", { enumerable: true, get: function () { return AudioExtractionError_1.AudioExtractionError; } });
|
|
68
|
+
Object.defineProperty(exports, "mapExtractionError", { enumerable: true, get: function () { return AudioExtractionError_1.mapExtractionError; } });
|
|
64
69
|
/** @deprecated Use AudioStudioModule instead */
|
|
65
70
|
exports.ExpoAudioStreamModule = AudioStudioModule_1.default;
|
|
66
71
|
//# sourceMappingURL=index.js.map
|
package/build/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,eAAe;;;;;;;;;;;;;;;;;;;;AAEf,+EAG6C;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,eAAe;;;;;;;;;;;;;;;;;;;;AAEf,+EAG6C;AA8CzC,sGAhDA,4CAAqB,OAgDA;AACrB,qGAhDA,2CAAoB,OAgDA;AA9CxB,uEAAmE;AAiD/D,iGAjDK,mCAAgB,OAiDL;AAhDpB,iFAG8C;AA8C1C,sGAhDA,6CAAqB,OAgDA;AAGrB,gGAlDA,uCAAe,OAkDA;AAhDnB,mEAA+D;AA0C3D,+FA1CK,+BAAc,OA0CL;AAzClB,2EAG2C;AA0CvC,qGA5CA,yCAAoB,OA4CA;AACpB,oGA5CA,wCAAmB,OA4CA;AA1CvB,qEAGiC;AA8B7B,sGAhCA,8CAAqB,OAgCA;AAYrB,uGA3CA,+CAAsB,OA2CA;AAzC1B,4EAAmD;AA8B/C,4BA9BG,2BAAiB,CA8BH;AA7BrB,2CAAuC;AAiCnC,0FAjCK,qBAAS,OAiCL;AAhCb,yDAAqD;AAsCjD,iGAtCK,mCAAgB,OAsCL;AApCpB,8DAA2C;AAC3C,yDAAsC;AACtC,yDAAsC;AAEtC,+BAA+B;AAC/B,uEAQwC;AAPpC,8HAAA,uBAAuB,OAAA;AACvB,0HAAA,mBAAmB,OAAA;AACnB,0HAAA,mBAAmB,OAAA;AACnB,0HAAA,mBAAmB,OAAA;AACnB,0HAAA,mBAAmB,OAAA;AACnB,8HAAA,uBAAuB,OAAA;AAI3B,4BAA4B;AAC5B,2DAA6E;AAApE,wHAAA,kBAAkB,OAAA;AAAE,wHAAA,kBAAkB,OAAA;AAE/C,8BAA8B;AAC9B,2DAAyD;AAAhD,kHAAA,eAAe,OAAA;AAExB,yDAAqE;AAA5D,sHAAA,wBAAwB,OAAA;AACjC,yEAAuE;AAA9D,wHAAA,kBAAkB,OAAA;AAkB3B,sEAGsC;AAFlC,4HAAA,oBAAoB,OAAA;AACpB,0HAAA,kBAAkB,OAAA;AAWtB,gDAAgD;AACnC,QAAA,qBAAqB,GAAG,2BAAiB,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 { 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 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\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"]}
|
|
@@ -87,6 +87,9 @@ function audioRecorderReducer(state, action) {
|
|
|
87
87
|
return state;
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
function shouldKeepFullAnalysis(config) {
|
|
91
|
+
return config?.keepFullAnalysis !== false;
|
|
92
|
+
}
|
|
90
93
|
function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl, } = {}) {
|
|
91
94
|
// Initialize AudioDeviceManager with logger (once)
|
|
92
95
|
if (logger) {
|
|
@@ -138,10 +141,13 @@ function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl, } = {}
|
|
|
138
141
|
...savedAnalysisData.dataPoints,
|
|
139
142
|
...analysis.dataPoints,
|
|
140
143
|
];
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
const keepFullAnalysis = shouldKeepFullAnalysis(recordingConfigRef.current);
|
|
145
|
+
const fullCombinedDataPoints = keepFullAnalysis
|
|
146
|
+
? [
|
|
147
|
+
...(fullAnalysisRef.current?.dataPoints ?? []),
|
|
148
|
+
...analysis.dataPoints,
|
|
149
|
+
]
|
|
150
|
+
: undefined;
|
|
145
151
|
// Calculate the new duration
|
|
146
152
|
// The number of segments is based on how many segments of segmentDurationMs can fit in visualizationDuration
|
|
147
153
|
const numberOfSegments = Math.ceil(visualizationDuration / analysis.segmentDurationMs);
|
|
@@ -152,13 +158,15 @@ function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl, } = {}
|
|
|
152
158
|
if (combinedDataPoints.length > maxDataPoints) {
|
|
153
159
|
combinedDataPoints.splice(0, combinedDataPoints.length - maxDataPoints);
|
|
154
160
|
}
|
|
155
|
-
// Keep the full data points
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
161
|
+
// Keep the full data points when requested for stopRecording().analysisData.
|
|
162
|
+
if (keepFullAnalysis && fullCombinedDataPoints) {
|
|
163
|
+
fullAnalysisRef.current = {
|
|
164
|
+
...fullAnalysisRef.current,
|
|
165
|
+
dataPoints: fullCombinedDataPoints,
|
|
166
|
+
};
|
|
167
|
+
fullAnalysisRef.current.durationMs =
|
|
168
|
+
fullCombinedDataPoints.length * analysis.segmentDurationMs;
|
|
169
|
+
}
|
|
162
170
|
savedAnalysisData.dataPoints = combinedDataPoints;
|
|
163
171
|
savedAnalysisData.bitDepth =
|
|
164
172
|
analysis.bitDepth || savedAnalysisData.bitDepth;
|
|
@@ -171,10 +179,12 @@ function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl, } = {}
|
|
|
171
179
|
min: newMin,
|
|
172
180
|
max: newMax,
|
|
173
181
|
};
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
182
|
+
if (keepFullAnalysis) {
|
|
183
|
+
fullAnalysisRef.current.amplitudeRange = {
|
|
184
|
+
min: newMin,
|
|
185
|
+
max: newMax,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
178
188
|
logger?.debug(`[handleAudioAnalysis] Updated analysis data: durationMs=${savedAnalysisData.durationMs}`, { dataPoints: savedAnalysisData.dataPoints.length });
|
|
179
189
|
// Call the onAudioAnalysis callback if it exists in the recording config
|
|
180
190
|
if (recordingConfigRef.current?.onAudioAnalysis) {
|
|
@@ -355,7 +365,7 @@ function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl, } = {}
|
|
|
355
365
|
logger?.debug(`start recording with validated config`, validatedOptions);
|
|
356
366
|
analysisRef.current = { ...defaultAnalysis }; // Reset analysis data
|
|
357
367
|
fullAnalysisRef.current = { ...defaultAnalysis };
|
|
358
|
-
const { onAudioStream, onRecordingInterrupted, onAudioAnalysis, ...options } = validatedOptions;
|
|
368
|
+
const { onAudioStream, onRecordingInterrupted, onAudioAnalysis, keepFullAnalysis: _keepFullAnalysis, ...options } = validatedOptions;
|
|
359
369
|
const { enableProcessing } = options;
|
|
360
370
|
const maxRecentDataDuration = 10000; // TODO compute maxRecentDataDuration based on screen dimensions
|
|
361
371
|
if (typeof onAudioStream === 'function') {
|
|
@@ -392,7 +402,7 @@ function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl, } = {}
|
|
|
392
402
|
logger?.debug(`preparing recording`, recordingOptions);
|
|
393
403
|
analysisRef.current = { ...defaultAnalysis }; // Reset analysis data
|
|
394
404
|
fullAnalysisRef.current = { ...defaultAnalysis };
|
|
395
|
-
const { onAudioStream, onRecordingInterrupted, onAudioAnalysis, ...options } = recordingOptions;
|
|
405
|
+
const { onAudioStream, onRecordingInterrupted, onAudioAnalysis, keepFullAnalysis: _keepFullAnalysis, ...options } = recordingOptions;
|
|
396
406
|
// Store onAudioStream for later use when recording starts
|
|
397
407
|
if (typeof onAudioStream === 'function') {
|
|
398
408
|
onAudioStreamRef.current = onAudioStream;
|
|
@@ -410,7 +420,15 @@ function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl, } = {}
|
|
|
410
420
|
const stopRecording = (0, react_1.useCallback)(async () => {
|
|
411
421
|
logger?.debug(`stoping recording`);
|
|
412
422
|
const stopResult = await audioStudio.stopRecording();
|
|
413
|
-
|
|
423
|
+
if (shouldKeepFullAnalysis(recordingConfigRef.current)) {
|
|
424
|
+
stopResult.analysisData = fullAnalysisRef.current;
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
// `keepFullAnalysis` is a hook-level retention policy. If a platform
|
|
428
|
+
// starts returning native analysisData in the future, keep opt-out
|
|
429
|
+
// semantics explicit and avoid leaking a full history here.
|
|
430
|
+
delete stopResult.analysisData;
|
|
431
|
+
}
|
|
414
432
|
if (analysisListenerRef.current) {
|
|
415
433
|
analysisListenerRef.current.remove();
|
|
416
434
|
analysisListenerRef.current = null;
|