@siteed/expo-audio-studio 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -1
- package/build/AudioDeviceManager.d.ts +1 -1
- package/build/AudioDeviceManager.js +1 -1
- package/build/AudioDeviceManager.js.map +1 -1
- package/build/ExpoAudioStream.types.d.ts +14 -0
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStream.web.d.ts.map +1 -1
- package/build/ExpoAudioStream.web.js +80 -9
- package/build/ExpoAudioStream.web.js.map +1 -1
- package/build/WebRecorder.web.d.ts +14 -4
- package/build/WebRecorder.web.d.ts.map +1 -1
- package/build/WebRecorder.web.js +121 -14
- package/build/WebRecorder.web.js.map +1 -1
- package/build/utils/writeWavHeader.d.ts +3 -18
- package/build/utils/writeWavHeader.d.ts.map +1 -1
- package/build/utils/writeWavHeader.js +19 -26
- package/build/utils/writeWavHeader.js.map +1 -1
- package/package.json +1 -1
- package/src/AudioDeviceManager.ts +1 -1
- package/src/ExpoAudioStream.types.ts +16 -0
- package/src/ExpoAudioStream.web.ts +99 -9
- package/src/WebRecorder.web.ts +146 -21
- package/src/utils/writeWavHeader.ts +26 -25
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebRecorder.web.js","sourceRoot":"","sources":["../src/WebRecorder.web.ts"],"names":[],"mappings":"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","sourcesContent":["// packages/expo-audio-stream/src/WebRecorder.web.ts\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport { ConsoleLike, RecordingConfig } from './ExpoAudioStream.types'\nimport {\n EmitAudioAnalysisFunction,\n EmitAudioEventFunction,\n} from './ExpoAudioStream.web'\nimport { encodingToBitDepth } from './utils/encodingToBitDepth'\nimport { InlineFeaturesExtractor } from './workers/InlineFeaturesExtractor.web'\nimport { InlineAudioWebWorker } from './workers/inlineAudioWebWorker.web'\n\ninterface AudioWorkletEvent {\n data: {\n command: string\n recordedData?: Float32Array\n sampleRate?: number\n position?: number\n }\n}\n\ninterface AudioFeaturesEvent {\n data: {\n command: string\n result: AudioAnalysis\n }\n}\n\nconst DEFAULT_WEB_BITDEPTH = 32\nconst DEFAULT_SEGMENT_DURATION_MS = 100\nconst DEFAULT_WEB_INTERVAL = 500\nconst DEFAULT_WEB_NUMBER_OF_CHANNELS = 1\n\nconst TAG = 'WebRecorder'\n\nexport class WebRecorder {\n public audioContext: AudioContext\n private audioWorkletNode!: AudioWorkletNode\n private featureExtractorWorker?: Worker\n private source: MediaStreamAudioSourceNode\n private emitAudioEventCallback: EmitAudioEventFunction\n private emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n private config: RecordingConfig\n private position: number = 0\n private numberOfChannels: number // Number of audio channels\n private bitDepth: number // Bit depth of the audio\n private exportBitDepth: number // Bit depth of the audio\n private audioAnalysisData: AudioAnalysis // Keep updating the full audio analysis data with latest events\n private logger?: ConsoleLike\n private compressedMediaRecorder: MediaRecorder | null = null\n private compressedChunks: Blob[] = []\n private compressedSize: number = 0\n private pendingCompressedChunk: Blob | null = null\n private dataPointIdCounter: number = 0 // Add this property to track the counter\n private deviceDisconnectionHandler: (() => void) | null = null\n private mediaStream: MediaStream | null = null\n private onInterruptionCallback?: (event: {\n reason: string\n isPaused: boolean\n timestamp: number\n }) => void\n private _isDeviceDisconnected: boolean = false\n\n /**\n * Flag to indicate whether this is the first audio chunk after a device switch\n * Used to maintain proper duration counting\n */\n public isFirstChunkAfterSwitch: boolean = false\n\n /**\n * Gets whether the recording device has been disconnected\n */\n get isDeviceDisconnected(): boolean {\n return this._isDeviceDisconnected\n }\n\n /**\n * Initializes a new WebRecorder instance for audio recording and processing\n * @param audioContext - The AudioContext to use for recording\n * @param source - The MediaStreamAudioSourceNode providing the audio input\n * @param recordingConfig - Configuration options for the recording\n * @param emitAudioEventCallback - Callback function for audio data events\n * @param emitAudioAnalysisCallback - Callback function for audio analysis events\n * @param onInterruption - Callback for recording interruptions\n * @param logger - Optional logger for debugging information\n */\n constructor({\n audioContext,\n source,\n recordingConfig,\n emitAudioEventCallback,\n emitAudioAnalysisCallback,\n onInterruption,\n logger,\n }: {\n audioContext: AudioContext\n source: MediaStreamAudioSourceNode\n recordingConfig: RecordingConfig\n emitAudioEventCallback: EmitAudioEventFunction\n emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n onInterruption?: (event: {\n reason: string\n isPaused: boolean\n timestamp: number\n }) => void\n logger?: ConsoleLike\n }) {\n this.audioContext = audioContext\n this.source = source\n this.emitAudioEventCallback = emitAudioEventCallback\n this.emitAudioAnalysisCallback = emitAudioAnalysisCallback\n this.config = recordingConfig\n this.logger = logger\n\n const audioContextFormat = this.checkAudioContextFormat({\n sampleRate: this.audioContext.sampleRate,\n })\n this.logger?.debug('Initialized WebRecorder with config:', {\n sampleRate: audioContextFormat.sampleRate,\n bitDepth: audioContextFormat.bitDepth,\n numberOfChannels: audioContextFormat.numberOfChannels,\n })\n\n this.bitDepth = audioContextFormat.bitDepth\n this.numberOfChannels =\n audioContextFormat.numberOfChannels ||\n DEFAULT_WEB_NUMBER_OF_CHANNELS // Default to 1 if not available\n this.exportBitDepth =\n encodingToBitDepth({\n encoding: recordingConfig.encoding ?? 'pcm_32bit',\n }) ||\n audioContextFormat.bitDepth ||\n DEFAULT_WEB_BITDEPTH\n\n this.audioAnalysisData = {\n amplitudeRange: { min: 0, max: 0 },\n rmsRange: { min: 0, max: 0 },\n dataPoints: [],\n durationMs: 0,\n samples: 0,\n bitDepth: this.bitDepth,\n numberOfChannels: this.numberOfChannels,\n sampleRate: this.config.sampleRate || this.audioContext.sampleRate,\n segmentDurationMs:\n this.config.segmentDurationMs ?? DEFAULT_SEGMENT_DURATION_MS, // Default to 100ms segments\n }\n\n if (recordingConfig.enableProcessing) {\n this.initFeatureExtractorWorker()\n }\n\n // Initialize compressed recording if enabled\n if (recordingConfig.compression?.enabled) {\n this.initializeCompressedRecorder()\n }\n\n this.mediaStream = source.mediaStream\n this.onInterruptionCallback = onInterruption\n\n // Setup device disconnection detection\n this.setupDeviceDisconnectionDetection()\n }\n\n /**\n * Initializes the audio worklet using an inline script\n * Creates and connects the audio processing pipeline\n */\n async init() {\n try {\n // Create and use inline audio worklet\n const blob = new Blob([InlineAudioWebWorker], {\n type: 'application/javascript',\n })\n const url = URL.createObjectURL(blob)\n await this.audioContext.audioWorklet.addModule(url)\n\n this.audioWorkletNode = new AudioWorkletNode(\n this.audioContext,\n 'recorder-processor'\n )\n\n this.audioWorkletNode.port.onmessage = async (\n event: AudioWorkletEvent\n ) => {\n const command = event.data.command\n if (command !== 'newData') return\n\n const pcmBufferFloat = event.data.recordedData\n if (!pcmBufferFloat) {\n this.logger?.warn('Received empty audio buffer', event)\n return\n }\n\n // Process data in smaller chunks and emit immediately\n const chunkSize = this.audioContext.sampleRate * 2 // Reduce to 2 seconds chunks\n const sampleRate =\n event.data.sampleRate ?? this.audioContext.sampleRate\n const duration = pcmBufferFloat.length / sampleRate\n\n // Use incoming position if provided by worklet, otherwise use our tracked position\n const incomingPosition =\n typeof event.data.position === 'number'\n ? event.data.position\n : this.position\n\n // Calculate bytes per sample based on bit depth\n const bytesPerSample = this.bitDepth / 8\n\n // Emit chunks without storing them\n for (let i = 0; i < pcmBufferFloat.length; i += chunkSize) {\n const chunk = pcmBufferFloat.slice(i, i + chunkSize)\n const chunkPosition = incomingPosition + i / sampleRate\n\n // Calculate byte positions and samples\n const startPosition = Math.floor(i * bytesPerSample)\n const endPosition = Math.floor(\n (i + chunk.length) * bytesPerSample\n )\n const samples = chunk.length // Number of samples in this chunk\n\n // Process features if enabled\n if (\n this.config.enableProcessing &&\n this.featureExtractorWorker\n ) {\n this.featureExtractorWorker.postMessage({\n command: 'process',\n channelData: chunk,\n sampleRate,\n segmentDurationMs:\n this.config.segmentDurationMs ??\n DEFAULT_SEGMENT_DURATION_MS, // Default to 100ms\n bitDepth: this.bitDepth,\n fullAudioDurationMs: chunkPosition * 1000,\n numberOfChannels: this.numberOfChannels,\n features: this.config.features,\n intervalAnalysis: this.config.intervalAnalysis,\n startPosition,\n endPosition,\n samples,\n })\n }\n\n // Emit chunk immediately\n this.emitAudioEventCallback({\n data: chunk,\n position: chunkPosition,\n compression: this.pendingCompressedChunk\n ? {\n data: this.pendingCompressedChunk,\n size: this.pendingCompressedChunk.size,\n totalSize: this.compressedSize,\n mimeType: 'audio/webm',\n format: 'opus',\n bitrate:\n this.config.compression?.bitrate ??\n 128000,\n }\n : undefined,\n })\n }\n\n // Update our position based on the worklet's position if provided\n this.position = incomingPosition + duration\n this.pendingCompressedChunk = null\n }\n\n this.logger?.debug(\n `WebRecorder initialized -- recordSampleRate=${this.audioContext.sampleRate}, startPosition=${this.position}`,\n this.config\n )\n this.audioWorkletNode.port.postMessage({\n command: 'init',\n recordSampleRate: this.audioContext.sampleRate,\n exportSampleRate:\n this.config.sampleRate ?? this.audioContext.sampleRate,\n bitDepth: this.bitDepth,\n exportBitDepth: this.exportBitDepth,\n channels: this.numberOfChannels,\n interval: this.config.interval ?? DEFAULT_WEB_INTERVAL,\n position: this.position, // Pass the current position to the processor\n // enableLogging: !!this.logger,\n })\n\n // Connect the source to the AudioWorkletNode and start recording\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n } catch (error) {\n console.error(`[${TAG}] Failed to initialize WebRecorder`, error)\n }\n }\n\n /**\n * Initializes the feature extractor worker for audio analysis\n * Creates an inline worker from a blob for audio feature extraction\n */\n initFeatureExtractorWorker() {\n try {\n const blob = new Blob([InlineFeaturesExtractor], {\n type: 'application/javascript',\n })\n const url = URL.createObjectURL(blob)\n this.featureExtractorWorker = new Worker(url)\n this.featureExtractorWorker.onmessage =\n this.handleFeatureExtractorMessage.bind(this)\n this.featureExtractorWorker.onerror = (error) => {\n console.error(`[${TAG}] Feature extractor worker error:`, error)\n }\n\n // Initialize worker with counter if needed\n if (this.dataPointIdCounter > 0) {\n this.featureExtractorWorker.postMessage({\n command: 'resetCounter',\n value: this.dataPointIdCounter,\n })\n this.logger?.debug(\n `Initialized worker with counter value ${this.dataPointIdCounter}`\n )\n }\n\n this.logger?.log(\n 'Feature extractor worker initialized successfully'\n )\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize feature extractor worker`,\n error\n )\n }\n }\n\n /**\n * Processes audio analysis results from the feature extractor worker\n * Updates the audio analysis data and emits events\n * @param event - The event containing audio analysis results\n */\n handleFeatureExtractorMessage(event: AudioFeaturesEvent) {\n if (event.data.command === 'features') {\n const segmentResult = event.data.result\n\n // Track existing IDs to prevent duplicates\n const existingIds = new Set(\n this.audioAnalysisData.dataPoints.map((dp) => dp.id)\n )\n\n // Filter out datapoints with duplicate IDs\n const uniqueNewDataPoints = segmentResult.dataPoints.filter(\n (dp) => {\n return !existingIds.has(dp.id)\n }\n )\n\n // Log filtered duplicates if any\n if (\n uniqueNewDataPoints.length < segmentResult.dataPoints.length &&\n this.logger?.warn\n ) {\n this.logger.warn(\n `Filtered ${segmentResult.dataPoints.length - uniqueNewDataPoints.length} duplicate datapoints`\n )\n }\n\n // Update counter based on the highest ID seen\n if (uniqueNewDataPoints.length > 0) {\n const lastDataPoint =\n uniqueNewDataPoints[uniqueNewDataPoints.length - 1]\n\n if (lastDataPoint && typeof lastDataPoint.id === 'number') {\n const nextIdValue = lastDataPoint.id + 1\n\n if (nextIdValue > this.dataPointIdCounter) {\n this.dataPointIdCounter = nextIdValue\n this.logger?.debug(\n `Counter updated to ${this.dataPointIdCounter}`\n )\n }\n }\n }\n\n // Add unique data points to our analysis data\n this.audioAnalysisData.dataPoints.push(...uniqueNewDataPoints)\n this.audioAnalysisData.durationMs += segmentResult.durationMs\n this.audioAnalysisData.sampleRate = segmentResult.sampleRate\n\n // Merge amplitude ranges\n if (segmentResult.amplitudeRange) {\n if (!this.audioAnalysisData.amplitudeRange) {\n this.audioAnalysisData.amplitudeRange = {\n ...segmentResult.amplitudeRange,\n }\n } else {\n this.audioAnalysisData.amplitudeRange = {\n min: Math.min(\n this.audioAnalysisData.amplitudeRange.min,\n segmentResult.amplitudeRange.min\n ),\n max: Math.max(\n this.audioAnalysisData.amplitudeRange.max,\n segmentResult.amplitudeRange.max\n ),\n }\n }\n }\n\n // Merge RMS ranges\n if (segmentResult.rmsRange) {\n if (!this.audioAnalysisData.rmsRange) {\n this.audioAnalysisData.rmsRange = {\n ...segmentResult.rmsRange,\n }\n } else {\n this.audioAnalysisData.rmsRange = {\n min: Math.min(\n this.audioAnalysisData.rmsRange.min,\n segmentResult.rmsRange.min\n ),\n max: Math.max(\n this.audioAnalysisData.rmsRange.max,\n segmentResult.rmsRange.max\n ),\n }\n }\n }\n\n // Send filtered result to avoid duplicate IDs\n const filteredSegmentResult = {\n ...segmentResult,\n dataPoints: uniqueNewDataPoints,\n }\n\n this.emitAudioAnalysisCallback(filteredSegmentResult)\n }\n }\n\n /**\n * Reset the data point counter to a specific value or zero\n * @param startCounterFrom Optional value to start the counter from (for continuing from previous recordings)\n */\n resetDataPointCounter(startCounterFrom?: number): void {\n // Set the counter with the passed value or 0\n this.dataPointIdCounter =\n startCounterFrom !== undefined ? startCounterFrom : 0\n this.logger?.debug(\n `Reset data point counter to ${this.dataPointIdCounter}`\n )\n\n // Update worker counter if available\n if (this.featureExtractorWorker) {\n this.featureExtractorWorker.postMessage({\n command: 'resetCounter',\n value: this.dataPointIdCounter,\n })\n } else {\n this.logger?.warn(\n 'No feature extractor worker available to update counter'\n )\n }\n }\n\n /**\n * Get the current data point counter value\n * @returns The current value of the data point counter\n */\n getDataPointCounter(): number {\n return this.dataPointIdCounter\n }\n\n /**\n * Prepares the recorder for continuity after device switch\n * Sets up all necessary state to maintain proper recording continuity\n */\n prepareForDeviceSwitch(): void {\n this.isFirstChunkAfterSwitch = true\n this.logger?.debug(\n `Prepared for device switch at position ${this.position}s`\n )\n }\n\n /**\n * Starts the audio recording process\n * Connects the audio nodes and begins capturing audio data\n * @param preserveCounters If true, do not reset the counter (used for device switching)\n */\n start(preserveCounters = false) {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n\n // Only reset the counter when not preserving state (e.g., for a fresh recording)\n if (!preserveCounters) {\n this.logger?.debug(\n 'Starting fresh recording, resetting counter to 0'\n )\n this.resetDataPointCounter(0) // Explicitly reset to 0 for new recordings\n this.isFirstChunkAfterSwitch = false\n } else {\n this.logger?.debug(\n `Preserving counter at ${this.dataPointIdCounter} during device switch`\n )\n }\n\n if (this.compressedMediaRecorder) {\n this.compressedMediaRecorder.start(this.config.interval ?? 1000)\n }\n }\n\n /**\n * Stops the audio recording process and returns the recorded data\n * @param externalAudioChunks Optional array of Float32Array chunks from previous devices\n * @returns Promise resolving to an object containing PCM data and optional compressed blob\n */\n async stop(\n externalAudioChunks?: Float32Array[]\n ): Promise<{ pcmData: Float32Array; compressedBlob?: Blob }> {\n try {\n // Log what's happening for debugging\n this.logger?.debug('Stopping recording and collecting final data')\n\n // Stop any compressed recording first\n if (\n this.compressedMediaRecorder &&\n this.compressedMediaRecorder.state !== 'inactive'\n ) {\n this.compressedMediaRecorder.stop()\n }\n\n // Wait for any pending compressed chunks to be processed\n if (this.compressedMediaRecorder) {\n // Small delay to ensure all data is processed\n await new Promise((resolve) => setTimeout(resolve, 100))\n }\n\n // Return the compressed blob if available\n return {\n pcmData: new Float32Array(), // Return empty array since we're streaming\n compressedBlob:\n this.compressedChunks.length > 0\n ? new Blob(this.compressedChunks, {\n type: 'audio/webm;codecs=opus',\n })\n : undefined,\n }\n } finally {\n this.cleanup()\n // Reset the chunks array\n this.compressedChunks = []\n this.compressedSize = 0\n this.pendingCompressedChunk = null\n }\n }\n\n /**\n * Cleans up resources when recording is stopped\n * Closes audio context and disconnects nodes\n */\n public cleanup() {\n // Remove device disconnection handler\n if (this.deviceDisconnectionHandler) {\n this.deviceDisconnectionHandler()\n this.deviceDisconnectionHandler = null\n }\n\n // Check if AudioContext is already closed before attempting to close it\n if (this.audioContext && this.audioContext.state !== 'closed') {\n try {\n this.audioContext.close()\n } catch (e) {\n // Ignore closure errors - this happens if already closed\n }\n }\n\n // Safely disconnect audioWorkletNode if it exists\n if (this.audioWorkletNode) {\n try {\n this.audioWorkletNode.disconnect()\n } catch (e) {\n // Ignore disconnection errors - node might be already disconnected\n }\n }\n\n // Safely disconnect source if it exists\n if (this.source) {\n try {\n this.source.disconnect()\n } catch (e) {\n // Ignore disconnection errors - source might be already disconnected\n }\n }\n\n // Always stop media stream tracks to release hardware resources\n this.stopMediaStreamTracks()\n\n // Mark as disconnected to prevent future errors\n this._isDeviceDisconnected = true\n }\n\n /**\n * Pauses the audio recording process\n * Disconnects audio nodes and pauses the media recorder\n */\n pause() {\n try {\n // Note: We're just pausing, not disconnecting the device\n // Simply disconnect nodes temporarily without marking device as disconnected\n this.source.disconnect(this.audioWorkletNode)\n this.audioWorkletNode.disconnect(this.audioContext.destination)\n this.audioWorkletNode.port.postMessage({ command: 'pause' })\n\n if (this.compressedMediaRecorder?.state === 'recording') {\n this.compressedMediaRecorder.pause()\n }\n\n this.logger?.debug('Recording paused successfully')\n } catch (error) {\n this.logger?.error('Error in pause(): ', error)\n // Already disconnected, just ignore and continue\n }\n }\n\n /**\n * Stops all media stream tracks to release hardware resources\n * Ensures recording indicators (like microphone icon) are turned off\n */\n public stopMediaStreamTracks() {\n // Stop all audio tracks to stop the recording icon\n if (this.mediaStream) {\n const tracks = this.mediaStream.getTracks()\n tracks.forEach((track) => track.stop())\n } else if (this.source?.mediaStream) {\n const tracks = this.source.mediaStream.getTracks()\n tracks.forEach((track) => track.stop())\n }\n }\n\n /**\n * Determines the audio format capabilities of the current audio context\n * @param sampleRate - The sample rate to check\n * @returns Object containing format information (sample rate, bit depth, channels)\n */\n private checkAudioContextFormat({ sampleRate }: { sampleRate: number }) {\n // Create a silent AudioBuffer\n const frameCount = sampleRate * 1.0 // 1 second buffer\n const audioBuffer = this.audioContext.createBuffer(\n 1,\n frameCount,\n sampleRate\n )\n\n // Check the format\n const channelData = audioBuffer.getChannelData(0)\n const bitDepth = channelData.BYTES_PER_ELEMENT * 8 // 4 bytes per element means 32-bit\n\n return {\n sampleRate: audioBuffer.sampleRate,\n bitDepth,\n numberOfChannels: audioBuffer.numberOfChannels,\n }\n }\n\n /**\n * Resumes a paused recording\n * Reconnects audio nodes and resumes the media recorder\n */\n resume() {\n // If device was disconnected, we can't resume\n if (this._isDeviceDisconnected) {\n this.logger?.warn('Cannot resume recording: device disconnected')\n return\n }\n\n try {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.audioWorkletNode.port.postMessage({ command: 'resume' })\n this.compressedMediaRecorder?.resume()\n } catch (error) {\n this.logger?.error('Error in resume(): ', error)\n }\n }\n\n /**\n * Initializes the compressed media recorder if compression is enabled\n * Sets up event handlers for compressed audio data\n */\n private initializeCompressedRecorder() {\n try {\n const mimeType = 'audio/webm;codecs=opus'\n if (!MediaRecorder.isTypeSupported(mimeType)) {\n this.logger?.warn(\n 'Opus compression not supported in this browser'\n )\n return\n }\n\n this.compressedMediaRecorder = new MediaRecorder(\n this.source.mediaStream,\n {\n mimeType,\n audioBitsPerSecond:\n this.config.compression?.bitrate ?? 128000,\n }\n )\n\n this.compressedMediaRecorder.ondataavailable = (event) => {\n if (event.data.size > 0) {\n this.compressedChunks.push(event.data)\n this.compressedSize += event.data.size\n this.pendingCompressedChunk = event.data\n }\n }\n } catch (error) {\n this.logger?.error(\n 'Failed to initialize compressed recorder:',\n error\n )\n }\n }\n\n /**\n * Processes features if enabled\n */\n processFeatures(\n chunk: Float32Array,\n sampleRate: number,\n chunkPosition: number,\n startPosition: number,\n endPosition: number,\n samples: number\n ) {\n if (this.config.enableProcessing && this.featureExtractorWorker) {\n this.featureExtractorWorker.postMessage({\n command: 'process',\n channelData: chunk,\n sampleRate,\n segmentDurationMs:\n this.config.segmentDurationMs ??\n DEFAULT_SEGMENT_DURATION_MS, // Default to 100ms\n bitDepth: this.bitDepth,\n fullAudioDurationMs: chunkPosition * 1000,\n numberOfChannels: this.numberOfChannels,\n features: this.config.features,\n intervalAnalysis: this.config.intervalAnalysis,\n startPosition,\n endPosition,\n samples,\n })\n }\n }\n\n /**\n * Sets up detection for device disconnection events\n */\n private setupDeviceDisconnectionDetection() {\n if (!this.mediaStream) return\n\n // Function to handle track ending (which happens on device disconnection)\n const handleTrackEnded = () => {\n this.logger?.warn('Audio track ended - device disconnected')\n this._isDeviceDisconnected = true\n\n // Use the callback to notify parent component about device disconnection\n if (this.onInterruptionCallback) {\n this.onInterruptionCallback({\n reason: 'deviceDisconnected',\n isPaused: true,\n timestamp: Date.now(),\n })\n this.logger?.debug('Notified about device disconnection')\n }\n\n // Ensure we disconnect nodes to prevent zombie recordings\n if (this.audioWorkletNode) {\n this.audioWorkletNode.port.postMessage({\n command: 'deviceDisconnected',\n })\n\n try {\n this.source.disconnect(this.audioWorkletNode)\n this.audioWorkletNode.disconnect()\n } catch (e) {\n // Ignore disconnection errors as the track might already be gone\n }\n }\n }\n\n // Add listeners to all audio tracks\n const tracks = this.mediaStream.getAudioTracks()\n tracks.forEach((track) => {\n track.addEventListener('ended', handleTrackEnded)\n })\n\n // Store the handler for cleanup\n this.deviceDisconnectionHandler = () => {\n tracks.forEach((track) => {\n track.removeEventListener('ended', handleTrackEnded)\n })\n }\n }\n\n /**\n * Explicitly set the position for continuous recording across device switches\n * @param position The position in seconds to continue from\n */\n setPosition(position: number): void {\n if (position >= 0) {\n this.position = position\n this.logger?.debug(`Position explicitly set to ${position} seconds`)\n } else {\n this.logger?.warn(`Invalid position value: ${position}, ignoring`)\n }\n }\n\n /**\n * Get the current position in seconds\n * @returns The current position\n */\n getPosition(): number {\n return this.position\n }\n\n /**\n * Gets the current compressed chunks\n * @returns Array of current compressed audio chunks\n */\n getCompressedChunks(): Blob[] {\n return [...this.compressedChunks]\n }\n\n /**\n * Sets the compressed chunks from a previous recorder\n * @param chunks Array of compressed chunks from a previous recorder\n */\n setCompressedChunks(chunks: Blob[]): void {\n if (chunks && chunks.length > 0) {\n this.logger?.debug(\n `Adding ${chunks.length} compressed chunks from previous device`\n )\n this.compressedChunks = [...chunks, ...this.compressedChunks]\n // Update size\n this.compressedSize = this.compressedChunks.reduce(\n (size, chunk) => size + chunk.size,\n 0\n )\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"WebRecorder.web.js","sourceRoot":"","sources":["../src/WebRecorder.web.ts"],"names":[],"mappings":"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","sourcesContent":["// packages/expo-audio-stream/src/WebRecorder.web.ts\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport { ConsoleLike, RecordingConfig } from './ExpoAudioStream.types'\nimport {\n EmitAudioAnalysisFunction,\n EmitAudioEventFunction,\n} from './ExpoAudioStream.web'\nimport { encodingToBitDepth } from './utils/encodingToBitDepth'\nimport { writeWavHeader } from './utils/writeWavHeader'\nimport { InlineFeaturesExtractor } from './workers/InlineFeaturesExtractor.web'\nimport { InlineAudioWebWorker } from './workers/inlineAudioWebWorker.web'\n\ninterface AudioWorkletEvent {\n data: {\n command: string\n recordedData?: Float32Array\n sampleRate?: number\n position?: number\n message?: string // For debug messages\n }\n}\n\ninterface AudioFeaturesEvent {\n data: {\n command: string\n result: AudioAnalysis\n }\n}\n\nconst DEFAULT_WEB_BITDEPTH = 32\nconst DEFAULT_SEGMENT_DURATION_MS = 100\nconst DEFAULT_WEB_INTERVAL = 500\nconst DEFAULT_WEB_NUMBER_OF_CHANNELS = 1\n\nconst TAG = 'WebRecorder'\n\nexport class WebRecorder {\n public audioContext: AudioContext\n private audioWorkletNode!: AudioWorkletNode\n private featureExtractorWorker?: Worker\n private source: MediaStreamAudioSourceNode\n private emitAudioEventCallback: EmitAudioEventFunction\n private emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n private config: RecordingConfig\n private position: number = 0\n private numberOfChannels: number // Number of audio channels\n private bitDepth: number // Bit depth of the audio\n private exportBitDepth: number // Bit depth of the audio\n private audioAnalysisData: AudioAnalysis // Keep updating the full audio analysis data with latest events\n private logger?: ConsoleLike\n private compressedMediaRecorder: MediaRecorder | null = null\n private compressedChunks: Blob[] = []\n private compressedSize: number = 0\n private pendingCompressedChunk: Blob | null = null\n private dataPointIdCounter: number = 0 // Add this property to track the counter\n private deviceDisconnectionHandler: (() => void) | null = null\n private mediaStream: MediaStream | null = null\n private onInterruptionCallback?: (event: {\n reason: string\n isPaused: boolean\n timestamp: number\n }) => void\n private _isDeviceDisconnected: boolean = false\n private pcmData: Float32Array | null = null // Store original PCM data\n private totalSampleCount: number = 0\n\n /**\n * Flag to indicate whether this is the first audio chunk after a device switch\n * Used to maintain proper duration counting\n */\n public isFirstChunkAfterSwitch: boolean = false\n\n /**\n * Gets whether the recording device has been disconnected\n */\n get isDeviceDisconnected(): boolean {\n return this._isDeviceDisconnected\n }\n\n /**\n * Initializes a new WebRecorder instance for audio recording and processing\n * @param audioContext - The AudioContext to use for recording\n * @param source - The MediaStreamAudioSourceNode providing the audio input\n * @param recordingConfig - Configuration options for the recording\n * @param emitAudioEventCallback - Callback function for audio data events\n * @param emitAudioAnalysisCallback - Callback function for audio analysis events\n * @param onInterruption - Callback for recording interruptions\n * @param logger - Optional logger for debugging information\n */\n constructor({\n audioContext,\n source,\n recordingConfig,\n emitAudioEventCallback,\n emitAudioAnalysisCallback,\n onInterruption,\n logger,\n }: {\n audioContext: AudioContext\n source: MediaStreamAudioSourceNode\n recordingConfig: RecordingConfig\n emitAudioEventCallback: EmitAudioEventFunction\n emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n onInterruption?: (event: {\n reason: string\n isPaused: boolean\n timestamp: number\n }) => void\n logger?: ConsoleLike\n }) {\n this.audioContext = audioContext\n this.source = source\n this.emitAudioEventCallback = emitAudioEventCallback\n this.emitAudioAnalysisCallback = emitAudioAnalysisCallback\n this.config = recordingConfig\n this.logger = logger\n\n const audioContextFormat = this.checkAudioContextFormat({\n sampleRate: this.audioContext.sampleRate,\n })\n this.logger?.debug('Initialized WebRecorder with config:', {\n sampleRate: audioContextFormat.sampleRate,\n bitDepth: audioContextFormat.bitDepth,\n numberOfChannels: audioContextFormat.numberOfChannels,\n })\n\n this.bitDepth = audioContextFormat.bitDepth\n this.numberOfChannels =\n audioContextFormat.numberOfChannels ||\n DEFAULT_WEB_NUMBER_OF_CHANNELS // Default to 1 if not available\n this.exportBitDepth =\n encodingToBitDepth({\n encoding: recordingConfig.encoding ?? 'pcm_32bit',\n }) ||\n audioContextFormat.bitDepth ||\n DEFAULT_WEB_BITDEPTH\n\n this.audioAnalysisData = {\n amplitudeRange: { min: 0, max: 0 },\n rmsRange: { min: 0, max: 0 },\n dataPoints: [],\n durationMs: 0,\n samples: 0,\n bitDepth: this.bitDepth,\n numberOfChannels: this.numberOfChannels,\n sampleRate: this.config.sampleRate || this.audioContext.sampleRate,\n segmentDurationMs:\n this.config.segmentDurationMs ?? DEFAULT_SEGMENT_DURATION_MS, // Default to 100ms segments\n }\n\n if (recordingConfig.enableProcessing) {\n this.initFeatureExtractorWorker()\n }\n\n // Initialize compressed recording if enabled\n if (recordingConfig.compression?.enabled) {\n this.initializeCompressedRecorder()\n }\n\n this.mediaStream = source.mediaStream\n this.onInterruptionCallback = onInterruption\n\n // Setup device disconnection detection\n this.setupDeviceDisconnectionDetection()\n }\n\n /**\n * Initializes the audio worklet using an inline script\n * Creates and connects the audio processing pipeline\n */\n async init() {\n try {\n // Create and use inline audio worklet\n const blob = new Blob([InlineAudioWebWorker], {\n type: 'application/javascript',\n })\n const url = URL.createObjectURL(blob)\n await this.audioContext.audioWorklet.addModule(url)\n\n this.audioWorkletNode = new AudioWorkletNode(\n this.audioContext,\n 'recorder-processor'\n )\n\n this.audioWorkletNode.port.onmessage = async (\n event: AudioWorkletEvent\n ) => {\n const command = event.data.command\n if (command === 'debug') {\n this.logger?.debug(`[AudioWorklet] ${event.data.message}`)\n return\n }\n\n if (command !== 'newData') return\n\n const pcmBufferFloat = event.data.recordedData\n if (!pcmBufferFloat) {\n this.logger?.warn('Received empty audio buffer', event)\n return\n }\n\n // Process data in smaller chunks and emit immediately\n const sampleRate =\n event.data.sampleRate ?? this.audioContext.sampleRate\n // Use chunk size from config interval or default to 2 seconds\n const intervalMs = this.config.interval ?? DEFAULT_WEB_INTERVAL\n const chunkSize = Math.floor(sampleRate * (intervalMs / 1000))\n const duration = pcmBufferFloat.length / sampleRate\n\n // Use incoming position if provided by worklet, otherwise use our tracked position\n const incomingPosition =\n typeof event.data.position === 'number'\n ? event.data.position\n : this.position\n\n // Calculate bytes per sample based on bit depth\n const bytesPerSample = this.bitDepth / 8\n\n // Emit chunks without storing them\n for (let i = 0; i < pcmBufferFloat.length; i += chunkSize) {\n const chunk = pcmBufferFloat.slice(i, i + chunkSize)\n const chunkPosition = incomingPosition + i / sampleRate\n\n // Calculate byte positions and samples\n const startPosition = Math.floor(i * bytesPerSample)\n const endPosition = Math.floor(\n (i + chunk.length) * bytesPerSample\n )\n const samples = chunk.length // Number of samples in this chunk\n\n // Process features if enabled\n if (\n this.config.enableProcessing &&\n this.featureExtractorWorker\n ) {\n this.featureExtractorWorker.postMessage({\n command: 'process',\n channelData: chunk,\n sampleRate,\n segmentDurationMs:\n this.config.segmentDurationMs ??\n DEFAULT_SEGMENT_DURATION_MS, // Default to 100ms\n bitDepth: this.bitDepth,\n fullAudioDurationMs: chunkPosition * 1000,\n numberOfChannels: this.numberOfChannels,\n features: this.config.features,\n intervalAnalysis: this.config.intervalAnalysis,\n startPosition,\n endPosition,\n samples,\n })\n }\n\n // Only store PCM data if web.storeUncompressedAudio is not explicitly false\n const shouldStoreUncompressed =\n this.config.web?.storeUncompressedAudio !== false\n\n // Store PCM chunks when needed\n if (shouldStoreUncompressed) {\n // Store the original Float32Array data for later WAV creation\n this.appendPcmData(chunk)\n this.totalSampleCount += chunk.length\n }\n\n // Emit chunk immediately\n this.emitAudioEventCallback({\n data: chunk,\n position: chunkPosition,\n compression: this.pendingCompressedChunk\n ? {\n data: this.pendingCompressedChunk,\n size: this.pendingCompressedChunk.size,\n totalSize: this.compressedSize,\n mimeType: 'audio/webm',\n format: 'opus',\n bitrate:\n this.config.compression?.bitrate ??\n 128000,\n }\n : undefined,\n })\n }\n\n // Update our position based on the worklet's position if provided\n this.position = incomingPosition + duration\n this.pendingCompressedChunk = null\n }\n\n // Ensure we use all relevant settings from config\n const recordSampleRate = this.audioContext.sampleRate\n const exportSampleRate =\n this.config.sampleRate ?? this.audioContext.sampleRate\n const channels = this.config.channels ?? this.numberOfChannels\n const interval = this.config.interval ?? DEFAULT_WEB_INTERVAL\n\n this.logger?.debug(`WebRecorder initialized with config:`, {\n recordSampleRate,\n exportSampleRate,\n bitDepth: this.bitDepth,\n exportBitDepth: this.exportBitDepth,\n channels,\n interval,\n position: this.position,\n deviceId: this.config.deviceId || 'default',\n compression: this.config.compression\n ? {\n enabled: this.config.compression.enabled,\n format: this.config.compression.format,\n bitrate: this.config.compression.bitrate,\n }\n : 'disabled',\n })\n\n // Initialize the worklet with all settings from config\n this.audioWorkletNode.port.postMessage({\n command: 'init',\n recordSampleRate,\n exportSampleRate,\n bitDepth: this.bitDepth,\n exportBitDepth: this.exportBitDepth,\n channels,\n interval,\n position: this.position, // Pass the current position to the processor\n enableLogging: true,\n })\n\n // Connect the source to the AudioWorkletNode and start recording\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n } catch (error) {\n console.error(`[${TAG}] Failed to initialize WebRecorder`, error)\n }\n }\n\n /**\n * Append new PCM data to the existing buffer\n * @param newData New Float32Array data to append\n */\n private appendPcmData(newData: Float32Array): void {\n // Clone the incoming data to ensure it's not modified\n const dataToAdd = new Float32Array(newData)\n\n if (!this.pcmData) {\n // First chunk - create a copy to avoid references to original data\n this.pcmData = new Float32Array(dataToAdd)\n return\n }\n\n // Create a new buffer with increased size\n const newBuffer = new Float32Array(\n this.pcmData.length + dataToAdd.length\n )\n\n // Copy existing data\n newBuffer.set(this.pcmData)\n\n // Append new data\n newBuffer.set(dataToAdd, this.pcmData.length)\n\n // Replace existing buffer\n this.pcmData = newBuffer\n }\n\n /**\n * Initializes the feature extractor worker for audio analysis\n * Creates an inline worker from a blob for audio feature extraction\n */\n initFeatureExtractorWorker() {\n try {\n const blob = new Blob([InlineFeaturesExtractor], {\n type: 'application/javascript',\n })\n const url = URL.createObjectURL(blob)\n this.featureExtractorWorker = new Worker(url)\n this.featureExtractorWorker.onmessage =\n this.handleFeatureExtractorMessage.bind(this)\n this.featureExtractorWorker.onerror = (error) => {\n console.error(`[${TAG}] Feature extractor worker error:`, error)\n }\n\n // Initialize worker with counter if needed\n if (this.dataPointIdCounter > 0) {\n this.featureExtractorWorker.postMessage({\n command: 'resetCounter',\n value: this.dataPointIdCounter,\n })\n this.logger?.debug(\n `Initialized worker with counter value ${this.dataPointIdCounter}`\n )\n }\n\n this.logger?.log(\n 'Feature extractor worker initialized successfully'\n )\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize feature extractor worker`,\n error\n )\n }\n }\n\n /**\n * Processes audio analysis results from the feature extractor worker\n * Updates the audio analysis data and emits events\n * @param event - The event containing audio analysis results\n */\n handleFeatureExtractorMessage(event: AudioFeaturesEvent) {\n if (event.data.command === 'features') {\n const segmentResult = event.data.result\n\n // Track existing IDs to prevent duplicates\n const existingIds = new Set(\n this.audioAnalysisData.dataPoints.map((dp) => dp.id)\n )\n\n // Filter out datapoints with duplicate IDs\n const uniqueNewDataPoints = segmentResult.dataPoints.filter(\n (dp) => {\n return !existingIds.has(dp.id)\n }\n )\n\n // Log filtered duplicates if any\n if (\n uniqueNewDataPoints.length < segmentResult.dataPoints.length &&\n this.logger?.warn\n ) {\n this.logger.warn(\n `Filtered ${segmentResult.dataPoints.length - uniqueNewDataPoints.length} duplicate datapoints`\n )\n }\n\n // Update counter based on the highest ID seen\n if (uniqueNewDataPoints.length > 0) {\n const lastDataPoint =\n uniqueNewDataPoints[uniqueNewDataPoints.length - 1]\n\n if (lastDataPoint && typeof lastDataPoint.id === 'number') {\n const nextIdValue = lastDataPoint.id + 1\n\n if (nextIdValue > this.dataPointIdCounter) {\n this.dataPointIdCounter = nextIdValue\n this.logger?.debug(\n `Counter updated to ${this.dataPointIdCounter}`\n )\n }\n }\n }\n\n // Add unique data points to our analysis data\n this.audioAnalysisData.dataPoints.push(...uniqueNewDataPoints)\n this.audioAnalysisData.durationMs += segmentResult.durationMs\n this.audioAnalysisData.sampleRate = segmentResult.sampleRate\n\n // Merge amplitude ranges\n if (segmentResult.amplitudeRange) {\n if (!this.audioAnalysisData.amplitudeRange) {\n this.audioAnalysisData.amplitudeRange = {\n ...segmentResult.amplitudeRange,\n }\n } else {\n this.audioAnalysisData.amplitudeRange = {\n min: Math.min(\n this.audioAnalysisData.amplitudeRange.min,\n segmentResult.amplitudeRange.min\n ),\n max: Math.max(\n this.audioAnalysisData.amplitudeRange.max,\n segmentResult.amplitudeRange.max\n ),\n }\n }\n }\n\n // Merge RMS ranges\n if (segmentResult.rmsRange) {\n if (!this.audioAnalysisData.rmsRange) {\n this.audioAnalysisData.rmsRange = {\n ...segmentResult.rmsRange,\n }\n } else {\n this.audioAnalysisData.rmsRange = {\n min: Math.min(\n this.audioAnalysisData.rmsRange.min,\n segmentResult.rmsRange.min\n ),\n max: Math.max(\n this.audioAnalysisData.rmsRange.max,\n segmentResult.rmsRange.max\n ),\n }\n }\n }\n\n // Send filtered result to avoid duplicate IDs\n const filteredSegmentResult = {\n ...segmentResult,\n dataPoints: uniqueNewDataPoints,\n }\n\n this.emitAudioAnalysisCallback(filteredSegmentResult)\n }\n }\n\n /**\n * Reset the data point counter to a specific value or zero\n * @param startCounterFrom Optional value to start the counter from (for continuing from previous recordings)\n */\n resetDataPointCounter(startCounterFrom?: number): void {\n // Set the counter with the passed value or 0\n this.dataPointIdCounter =\n startCounterFrom !== undefined ? startCounterFrom : 0\n this.logger?.debug(\n `Reset data point counter to ${this.dataPointIdCounter}`\n )\n\n // Update worker counter if available\n if (this.featureExtractorWorker) {\n this.featureExtractorWorker.postMessage({\n command: 'resetCounter',\n value: this.dataPointIdCounter,\n })\n } else {\n this.logger?.warn(\n 'No feature extractor worker available to update counter'\n )\n }\n }\n\n /**\n * Get the current data point counter value\n * @returns The current value of the data point counter\n */\n getDataPointCounter(): number {\n return this.dataPointIdCounter\n }\n\n /**\n * Prepares the recorder for continuity after device switch\n * Sets up all necessary state to maintain proper recording continuity\n */\n prepareForDeviceSwitch(): void {\n this.isFirstChunkAfterSwitch = true\n this.logger?.debug(\n `Prepared for device switch at position ${this.position}s`\n )\n }\n\n /**\n * Starts the audio recording process\n * Connects the audio nodes and begins capturing audio data\n * @param preserveCounters If true, do not reset the counter (used for device switching)\n */\n start(preserveCounters = false) {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n\n // Only reset the counter when not preserving state (e.g., for a fresh recording)\n if (!preserveCounters) {\n this.logger?.debug(\n 'Starting fresh recording, resetting counter to 0'\n )\n this.resetDataPointCounter(0) // Explicitly reset to 0 for new recordings\n this.isFirstChunkAfterSwitch = false\n\n // Clear PCM data for new recording\n this.pcmData = null\n this.totalSampleCount = 0\n } else {\n this.logger?.debug(\n `Preserving counter at ${this.dataPointIdCounter} during device switch`\n )\n }\n\n if (this.compressedMediaRecorder) {\n this.compressedMediaRecorder.start(this.config.interval ?? 1000)\n }\n }\n\n /**\n * Creates a WAV file from the stored PCM data\n */\n private createWavFromPcmData(): Blob | null {\n try {\n // Check if we have PCM data\n if (!this.pcmData || this.pcmData.length === 0) {\n this.logger?.warn('No PCM data available to create WAV file')\n return null\n }\n\n const sampleRate =\n this.config.sampleRate || this.audioContext.sampleRate\n const channels = this.numberOfChannels || 1\n\n // Convert float32 PCM data to 16-bit PCM for WAV\n const bytesPerSample = 2 // 16-bit = 2 bytes\n const dataLength = this.pcmData.length * bytesPerSample\n const buffer = new ArrayBuffer(dataLength)\n const view = new DataView(buffer)\n\n // Convert Float32Array (-1 to 1) to Int16Array (-32768 to 32767)\n for (let i = 0; i < this.pcmData.length; i++) {\n const sample = Math.max(-1, Math.min(1, this.pcmData[i]))\n const int16Value = Math.round(sample * 32767)\n view.setInt16(i * 2, int16Value, true)\n }\n\n // Use the existing writeWavHeader utility to add a WAV header\n const wavBuffer = writeWavHeader({\n buffer,\n sampleRate,\n numChannels: channels,\n bitDepth: 16,\n isFloat: false,\n })\n\n return new Blob([wavBuffer], { type: 'audio/wav' })\n } catch (error) {\n this.logger?.error('Error creating WAV file from PCM data:', error)\n return null\n }\n }\n\n /**\n * Stops the audio recording process and returns the recorded data\n * @returns Promise resolving to an object containing compressed and/or uncompressed blobs\n */\n async stop(): Promise<{ compressedBlob?: Blob; uncompressedBlob?: Blob }> {\n try {\n // Stop any compressed recording first\n if (\n this.compressedMediaRecorder &&\n this.compressedMediaRecorder.state !== 'inactive'\n ) {\n this.compressedMediaRecorder.stop()\n }\n\n // Wait for any pending compressed chunks to be processed\n if (this.compressedMediaRecorder) {\n // Small delay to ensure all data is processed\n await new Promise((resolve) => setTimeout(resolve, 100))\n }\n\n // Create uncompressed WAV file from the PCM data\n let uncompressedBlob: Blob | undefined\n\n // Only create WAV if we have PCM data\n if (this.pcmData && this.pcmData.length > 0) {\n uncompressedBlob =\n (await this.createWavFromPcmData()) || undefined\n }\n\n // Return the compressed and/or uncompressed blobs if available\n return {\n compressedBlob:\n this.compressedChunks.length > 0\n ? new Blob(this.compressedChunks, {\n type: 'audio/webm;codecs=opus',\n })\n : undefined,\n uncompressedBlob,\n }\n } finally {\n this.cleanup()\n // Reset the chunks array\n this.compressedChunks = []\n this.compressedSize = 0\n this.pendingCompressedChunk = null\n this.pcmData = null\n this.totalSampleCount = 0\n }\n }\n\n /**\n * Cleans up resources when recording is stopped\n * Closes audio context and disconnects nodes\n */\n public cleanup() {\n // Remove device disconnection handler\n if (this.deviceDisconnectionHandler) {\n this.deviceDisconnectionHandler()\n this.deviceDisconnectionHandler = null\n }\n\n // Check if AudioContext is already closed before attempting to close it\n if (this.audioContext && this.audioContext.state !== 'closed') {\n try {\n this.audioContext.close()\n } catch (e) {\n // Ignore closure errors - this happens if already closed\n }\n }\n\n // Safely disconnect audioWorkletNode if it exists\n if (this.audioWorkletNode) {\n try {\n this.audioWorkletNode.disconnect()\n } catch (e) {\n // Ignore disconnection errors - node might be already disconnected\n }\n }\n\n // Safely disconnect source if it exists\n if (this.source) {\n try {\n this.source.disconnect()\n } catch (e) {\n // Ignore disconnection errors - source might be already disconnected\n }\n }\n\n // Always stop media stream tracks to release hardware resources\n this.stopMediaStreamTracks()\n\n // Mark as disconnected to prevent future errors\n this._isDeviceDisconnected = true\n }\n\n /**\n * Pauses the audio recording process\n * Disconnects audio nodes and pauses the media recorder\n */\n pause() {\n try {\n // Note: We're just pausing, not disconnecting the device\n // Simply disconnect nodes temporarily without marking device as disconnected\n this.source.disconnect(this.audioWorkletNode)\n this.audioWorkletNode.disconnect(this.audioContext.destination)\n this.audioWorkletNode.port.postMessage({ command: 'pause' })\n\n if (this.compressedMediaRecorder?.state === 'recording') {\n this.compressedMediaRecorder.pause()\n }\n\n this.logger?.debug('Recording paused successfully')\n } catch (error) {\n this.logger?.error('Error in pause(): ', error)\n // Already disconnected, just ignore and continue\n }\n }\n\n /**\n * Stops all media stream tracks to release hardware resources\n * Ensures recording indicators (like microphone icon) are turned off\n */\n public stopMediaStreamTracks() {\n // Stop all audio tracks to stop the recording icon\n if (this.mediaStream) {\n const tracks = this.mediaStream.getTracks()\n tracks.forEach((track) => track.stop())\n } else if (this.source?.mediaStream) {\n const tracks = this.source.mediaStream.getTracks()\n tracks.forEach((track) => track.stop())\n }\n }\n\n /**\n * Determines the audio format capabilities of the current audio context\n * @param sampleRate - The sample rate to check\n * @returns Object containing format information (sample rate, bit depth, channels)\n */\n private checkAudioContextFormat({ sampleRate }: { sampleRate: number }) {\n // Create a silent AudioBuffer\n const frameCount = sampleRate * 1.0 // 1 second buffer\n const audioBuffer = this.audioContext.createBuffer(\n 1,\n frameCount,\n sampleRate\n )\n\n // Check the format\n const channelData = audioBuffer.getChannelData(0)\n const bitDepth = channelData.BYTES_PER_ELEMENT * 8 // 4 bytes per element means 32-bit\n\n return {\n sampleRate: audioBuffer.sampleRate,\n bitDepth,\n numberOfChannels: audioBuffer.numberOfChannels,\n }\n }\n\n /**\n * Resumes a paused recording\n * Reconnects audio nodes and resumes the media recorder\n */\n resume() {\n // If device was disconnected, we can't resume\n if (this._isDeviceDisconnected) {\n this.logger?.warn('Cannot resume recording: device disconnected')\n return\n }\n\n try {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.audioWorkletNode.port.postMessage({ command: 'resume' })\n this.compressedMediaRecorder?.resume()\n } catch (error) {\n this.logger?.error('Error in resume(): ', error)\n }\n }\n\n /**\n * Initializes the compressed media recorder if compression is enabled\n * Sets up event handlers for compressed audio data\n */\n private initializeCompressedRecorder() {\n try {\n const mimeType = 'audio/webm;codecs=opus'\n if (!MediaRecorder.isTypeSupported(mimeType)) {\n this.logger?.warn(\n 'Opus compression not supported in this browser'\n )\n return\n }\n\n this.compressedMediaRecorder = new MediaRecorder(\n this.source.mediaStream,\n {\n mimeType,\n audioBitsPerSecond:\n this.config.compression?.bitrate ?? 128000,\n }\n )\n\n this.compressedMediaRecorder.ondataavailable = (event) => {\n if (event.data.size > 0) {\n this.compressedChunks.push(event.data)\n this.compressedSize += event.data.size\n this.pendingCompressedChunk = event.data\n }\n }\n } catch (error) {\n this.logger?.error(\n 'Failed to initialize compressed recorder:',\n error\n )\n }\n }\n\n /**\n * Processes features if enabled\n */\n processFeatures(\n chunk: Float32Array,\n sampleRate: number,\n chunkPosition: number,\n startPosition: number,\n endPosition: number,\n samples: number\n ) {\n if (this.config.enableProcessing && this.featureExtractorWorker) {\n this.featureExtractorWorker.postMessage({\n command: 'process',\n channelData: chunk,\n sampleRate,\n segmentDurationMs:\n this.config.segmentDurationMs ??\n DEFAULT_SEGMENT_DURATION_MS, // Default to 100ms\n bitDepth: this.bitDepth,\n fullAudioDurationMs: chunkPosition * 1000,\n numberOfChannels: this.numberOfChannels,\n features: this.config.features,\n intervalAnalysis: this.config.intervalAnalysis,\n startPosition,\n endPosition,\n samples,\n })\n }\n }\n\n /**\n * Sets up detection for device disconnection events\n */\n private setupDeviceDisconnectionDetection() {\n if (!this.mediaStream) return\n\n // Function to handle track ending (which happens on device disconnection)\n const handleTrackEnded = () => {\n this.logger?.warn('Audio track ended - device disconnected')\n this._isDeviceDisconnected = true\n\n // Use the callback to notify parent component about device disconnection\n if (this.onInterruptionCallback) {\n this.onInterruptionCallback({\n reason: 'deviceDisconnected',\n isPaused: true,\n timestamp: Date.now(),\n })\n this.logger?.debug('Notified about device disconnection')\n }\n\n // Ensure we disconnect nodes to prevent zombie recordings\n if (this.audioWorkletNode) {\n this.audioWorkletNode.port.postMessage({\n command: 'deviceDisconnected',\n })\n\n try {\n this.source.disconnect(this.audioWorkletNode)\n this.audioWorkletNode.disconnect()\n } catch (e) {\n // Ignore disconnection errors as the track might already be gone\n }\n }\n }\n\n // Add listeners to all audio tracks\n const tracks = this.mediaStream.getAudioTracks()\n tracks.forEach((track) => {\n track.addEventListener('ended', handleTrackEnded)\n })\n\n // Store the handler for cleanup\n this.deviceDisconnectionHandler = () => {\n tracks.forEach((track) => {\n track.removeEventListener('ended', handleTrackEnded)\n })\n }\n }\n\n /**\n * Explicitly set the position for continuous recording across device switches\n * @param position The position in seconds to continue from\n */\n setPosition(position: number): void {\n if (position >= 0) {\n this.position = position\n this.logger?.debug(`Position explicitly set to ${position} seconds`)\n } else {\n this.logger?.warn(`Invalid position value: ${position}, ignoring`)\n }\n }\n\n /**\n * Get the current position in seconds\n * @returns The current position\n */\n getPosition(): number {\n return this.position\n }\n\n /**\n * Gets the current compressed chunks\n * @returns Array of current compressed audio chunks\n */\n getCompressedChunks(): Blob[] {\n return [...this.compressedChunks]\n }\n\n /**\n * Sets the compressed chunks from a previous recorder\n * @param chunks Array of compressed chunks from a previous recorder\n */\n setCompressedChunks(chunks: Blob[]): void {\n if (chunks && chunks.length > 0) {\n this.logger?.debug(\n `Adding ${chunks.length} compressed chunks from previous device`\n )\n this.compressedChunks = [...chunks, ...this.compressedChunks]\n // Update size\n this.compressedSize = this.compressedChunks.reduce(\n (size, chunk) => size + chunk.size,\n 0\n )\n }\n }\n}\n"]}
|
|
@@ -10,6 +10,8 @@ export interface WavHeaderOptions {
|
|
|
10
10
|
numChannels: number;
|
|
11
11
|
/** The bit depth of the audio (e.g., 16, 24, or 32). */
|
|
12
12
|
bitDepth: number;
|
|
13
|
+
/** Whether the audio data is in float format (only applies to 32-bit) */
|
|
14
|
+
isFloat?: boolean;
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* Writes or updates a WAV (RIFF) header based on the provided options.
|
|
@@ -27,23 +29,6 @@ export interface WavHeaderOptions {
|
|
|
27
29
|
* @returns An ArrayBuffer containing the WAV header, or the header combined with the provided audio data.
|
|
28
30
|
*
|
|
29
31
|
* @throws {Error} Throws an error if the provided options are invalid or if the buffer is too small.
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* // Create a standalone WAV header
|
|
33
|
-
* const header = writeWavHeader({
|
|
34
|
-
* sampleRate: 44100,
|
|
35
|
-
* numChannels: 2,
|
|
36
|
-
* bitDepth: 16
|
|
37
|
-
* });
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* // Create a WAV header and combine it with audio data
|
|
41
|
-
* const completeWav = writeWavHeader({
|
|
42
|
-
* buffer: audioData,
|
|
43
|
-
* sampleRate: 44100,
|
|
44
|
-
* numChannels: 2,
|
|
45
|
-
* bitDepth: 16
|
|
46
|
-
* });
|
|
47
32
|
*/
|
|
48
|
-
export declare const writeWavHeader: ({ buffer, sampleRate, numChannels, bitDepth, }: WavHeaderOptions) => ArrayBuffer;
|
|
33
|
+
export declare const writeWavHeader: ({ buffer, sampleRate, numChannels, bitDepth, isFloat, }: WavHeaderOptions) => ArrayBuffer;
|
|
49
34
|
//# sourceMappingURL=writeWavHeader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeWavHeader.d.ts","sourceRoot":"","sources":["../../src/utils/writeWavHeader.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,+FAA+F;IAC/F,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAA;IAClB,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAA;IACnB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"writeWavHeader.d.ts","sourceRoot":"","sources":["../../src/utils/writeWavHeader.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,+FAA+F;IAC/F,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAA;IAClB,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAA;IACnB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAA;IAChB,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,4DAMxB,gBAAgB,KAAG,WAyErB,CAAA"}
|
|
@@ -15,25 +15,11 @@
|
|
|
15
15
|
* @returns An ArrayBuffer containing the WAV header, or the header combined with the provided audio data.
|
|
16
16
|
*
|
|
17
17
|
* @throws {Error} Throws an error if the provided options are invalid or if the buffer is too small.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* // Create a standalone WAV header
|
|
21
|
-
* const header = writeWavHeader({
|
|
22
|
-
* sampleRate: 44100,
|
|
23
|
-
* numChannels: 2,
|
|
24
|
-
* bitDepth: 16
|
|
25
|
-
* });
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* // Create a WAV header and combine it with audio data
|
|
29
|
-
* const completeWav = writeWavHeader({
|
|
30
|
-
* buffer: audioData,
|
|
31
|
-
* sampleRate: 44100,
|
|
32
|
-
* numChannels: 2,
|
|
33
|
-
* bitDepth: 16
|
|
34
|
-
* });
|
|
35
18
|
*/
|
|
36
|
-
export const writeWavHeader = ({ buffer, sampleRate, numChannels, bitDepth,
|
|
19
|
+
export const writeWavHeader = ({ buffer, sampleRate, numChannels, bitDepth, isFloat = bitDepth === 32, // Default to float for 32-bit
|
|
20
|
+
}) => {
|
|
21
|
+
// For 32-bit float, we use format 3, otherwise format 1 for PCM
|
|
22
|
+
const audioFormat = isFloat ? 3 : 1; // 3 = IEEE float, 1 = PCM
|
|
37
23
|
const bytesPerSample = bitDepth / 8;
|
|
38
24
|
const blockAlign = numChannels * bytesPerSample;
|
|
39
25
|
const byteRate = sampleRate * blockAlign;
|
|
@@ -45,21 +31,26 @@ export const writeWavHeader = ({ buffer, sampleRate, numChannels, bitDepth, }) =
|
|
|
45
31
|
};
|
|
46
32
|
// Function to write or update the header
|
|
47
33
|
const writeHeader = (view, dataSize = 0xffffffff) => {
|
|
34
|
+
// RIFF chunk descriptor
|
|
48
35
|
writeString(view, 0, 'RIFF'); // ChunkID
|
|
49
|
-
view.setUint32(4, 36 + dataSize, true); // ChunkSize
|
|
36
|
+
view.setUint32(4, 36 + dataSize, true); // ChunkSize: 4 + (8 + 16) + (8 + dataSize)
|
|
50
37
|
writeString(view, 8, 'WAVE'); // Format
|
|
38
|
+
// "fmt " sub-chunk
|
|
51
39
|
writeString(view, 12, 'fmt '); // Subchunk1ID
|
|
52
|
-
view.setUint32(16, 16, true); // Subchunk1Size (16 for PCM)
|
|
53
|
-
view.setUint16(20,
|
|
40
|
+
view.setUint32(16, 16, true); // Subchunk1Size (16 for PCM/Float)
|
|
41
|
+
view.setUint16(20, audioFormat, true); // AudioFormat (3 for float, 1 for PCM)
|
|
54
42
|
view.setUint16(22, numChannels, true); // NumChannels
|
|
55
43
|
view.setUint32(24, sampleRate, true); // SampleRate
|
|
56
|
-
view.setUint32(28, byteRate, true); // ByteRate
|
|
57
|
-
view.setUint16(32, blockAlign, true); // BlockAlign
|
|
44
|
+
view.setUint32(28, byteRate, true); // ByteRate = SampleRate * NumChannels * BitsPerSample/8
|
|
45
|
+
view.setUint16(32, blockAlign, true); // BlockAlign = NumChannels * BitsPerSample/8
|
|
58
46
|
view.setUint16(34, bitDepth, true); // BitsPerSample
|
|
47
|
+
// "data" sub-chunk
|
|
59
48
|
writeString(view, 36, 'data'); // Subchunk2ID
|
|
60
|
-
view.setUint32(40, dataSize, true); // Subchunk2Size
|
|
49
|
+
view.setUint32(40, dataSize, true); // Subchunk2Size = NumSamples * NumChannels * BitsPerSample/8
|
|
61
50
|
};
|
|
62
51
|
if (buffer) {
|
|
52
|
+
// Handle existing buffer
|
|
53
|
+
// Check for minimum size
|
|
63
54
|
if (buffer.byteLength < 44) {
|
|
64
55
|
throw new Error('Buffer is too small to contain a valid WAV header');
|
|
65
56
|
}
|
|
@@ -72,16 +63,18 @@ export const writeWavHeader = ({ buffer, sampleRate, numChannels, bitDepth, }) =
|
|
|
72
63
|
return buffer;
|
|
73
64
|
}
|
|
74
65
|
else {
|
|
75
|
-
//
|
|
66
|
+
// Create a new buffer with header + data
|
|
76
67
|
const newBuffer = new ArrayBuffer(44 + buffer.byteLength);
|
|
77
68
|
const newView = new DataView(newBuffer);
|
|
69
|
+
// Write header to new buffer
|
|
78
70
|
writeHeader(newView, buffer.byteLength);
|
|
71
|
+
// Copy audio data after header
|
|
79
72
|
new Uint8Array(newBuffer).set(new Uint8Array(buffer), 44);
|
|
80
73
|
return newBuffer;
|
|
81
74
|
}
|
|
82
75
|
}
|
|
83
76
|
else {
|
|
84
|
-
// Create
|
|
77
|
+
// Create standalone header
|
|
85
78
|
const headerBuffer = new ArrayBuffer(44);
|
|
86
79
|
const view = new DataView(headerBuffer);
|
|
87
80
|
writeHeader(view);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeWavHeader.js","sourceRoot":"","sources":["../../src/utils/writeWavHeader.ts"],"names":[],"mappings":"AAAA,yDAAyD;
|
|
1
|
+
{"version":3,"file":"writeWavHeader.js","sourceRoot":"","sources":["../../src/utils/writeWavHeader.ts"],"names":[],"mappings":"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","sourcesContent":["// packages/expo-audio-stream/src/utils/writeWavHeader.ts\n\n/**\n * Options for creating a WAV header.\n */\nexport interface WavHeaderOptions {\n /** Optional buffer containing audio data. If provided, it will be combined with the header. */\n buffer?: ArrayBuffer\n /** The sample rate of the audio in Hz (e.g., 44100). */\n sampleRate: number\n /** The number of audio channels (e.g., 1 for mono, 2 for stereo). */\n numChannels: number\n /** The bit depth of the audio (e.g., 16, 24, or 32). */\n bitDepth: number\n /** Whether the audio data is in float format (only applies to 32-bit) */\n isFloat?: boolean\n}\n\n/**\n * Writes or updates a WAV (RIFF) header based on the provided options.\n *\n * This function can be used in three ways:\n * 1. To create a standalone WAV header (when no buffer is provided).\n * 2. To create a WAV header and combine it with existing audio data (when a buffer without a header is provided).\n * 3. To update an existing WAV header in the provided buffer.\n *\n * For streaming audio where the final size is unknown, this function sets the size fields\n * to the maximum 32-bit value (0xFFFFFFFF). These can be updated later using the\n * `updateWavHeaderSize` function once the final size is known.\n *\n * @param options - The options for creating or updating the WAV header.\n * @returns An ArrayBuffer containing the WAV header, or the header combined with the provided audio data.\n *\n * @throws {Error} Throws an error if the provided options are invalid or if the buffer is too small.\n */\nexport const writeWavHeader = ({\n buffer,\n sampleRate,\n numChannels,\n bitDepth,\n isFloat = bitDepth === 32, // Default to float for 32-bit\n}: WavHeaderOptions): ArrayBuffer => {\n // For 32-bit float, we use format 3, otherwise format 1 for PCM\n const audioFormat = isFloat ? 3 : 1 // 3 = IEEE float, 1 = PCM\n\n const bytesPerSample = bitDepth / 8\n const blockAlign = numChannels * bytesPerSample\n const byteRate = sampleRate * blockAlign\n\n // Function to write a string to the DataView\n const writeString = (view: DataView, offset: number, string: string) => {\n for (let i = 0; i < string.length; i++) {\n view.setUint8(offset + i, string.charCodeAt(i))\n }\n }\n\n // Function to write or update the header\n const writeHeader = (view: DataView, dataSize: number = 0xffffffff) => {\n // RIFF chunk descriptor\n writeString(view, 0, 'RIFF') // ChunkID\n view.setUint32(4, 36 + dataSize, true) // ChunkSize: 4 + (8 + 16) + (8 + dataSize)\n writeString(view, 8, 'WAVE') // Format\n\n // \"fmt \" sub-chunk\n writeString(view, 12, 'fmt ') // Subchunk1ID\n view.setUint32(16, 16, true) // Subchunk1Size (16 for PCM/Float)\n view.setUint16(20, audioFormat, true) // AudioFormat (3 for float, 1 for PCM)\n view.setUint16(22, numChannels, true) // NumChannels\n view.setUint32(24, sampleRate, true) // SampleRate\n view.setUint32(28, byteRate, true) // ByteRate = SampleRate * NumChannels * BitsPerSample/8\n view.setUint16(32, blockAlign, true) // BlockAlign = NumChannels * BitsPerSample/8\n view.setUint16(34, bitDepth, true) // BitsPerSample\n\n // \"data\" sub-chunk\n writeString(view, 36, 'data') // Subchunk2ID\n view.setUint32(40, dataSize, true) // Subchunk2Size = NumSamples * NumChannels * BitsPerSample/8\n }\n\n if (buffer) {\n // Handle existing buffer\n\n // Check for minimum size\n if (buffer.byteLength < 44) {\n throw new Error('Buffer is too small to contain a valid WAV header')\n }\n\n const view = new DataView(buffer)\n\n // Check if the buffer already has a WAV header by looking for \"RIFF\" at the start\n const existingHeader = view.getUint32(0, false) === 0x52494646 // \"RIFF\" in ASCII\n\n if (existingHeader) {\n // Update the existing header\n writeHeader(view, buffer.byteLength - 44)\n return buffer\n } else {\n // Create a new buffer with header + data\n const newBuffer = new ArrayBuffer(44 + buffer.byteLength)\n const newView = new DataView(newBuffer)\n\n // Write header to new buffer\n writeHeader(newView, buffer.byteLength)\n\n // Copy audio data after header\n new Uint8Array(newBuffer).set(new Uint8Array(buffer), 44)\n return newBuffer\n }\n } else {\n // Create standalone header\n const headerBuffer = new ArrayBuffer(44)\n const view = new DataView(headerBuffer)\n writeHeader(view)\n return headerBuffer\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siteed/expo-audio-studio",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Comprehensive audio processing library for React Native and Expo with recording, analysis, visualization, and streaming capabilities across iOS, Android, and web",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -130,7 +130,7 @@ export class AudioDeviceManager {
|
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
132
|
* Get all available audio input devices
|
|
133
|
-
* @param options Optional settings
|
|
133
|
+
* @param options Optional settings to force refresh the device list. Can include a refresh flag.
|
|
134
134
|
* @returns Promise resolving to an array of audio devices conforming to AudioDevice interface
|
|
135
135
|
*/
|
|
136
136
|
async getAvailableDevices(options?: {
|
|
@@ -207,6 +207,19 @@ export interface IOSConfig {
|
|
|
207
207
|
audioSession?: AudioSessionConfig
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
/** Web platform specific configuration options */
|
|
211
|
+
export interface WebConfig {
|
|
212
|
+
/**
|
|
213
|
+
* Whether to store uncompressed audio data for WAV generation
|
|
214
|
+
*
|
|
215
|
+
* When true, all PCM chunks are stored in memory to create a WAV file when compression is disabled
|
|
216
|
+
* When false, uncompressed audio won't be available, but memory usage will be lower
|
|
217
|
+
*
|
|
218
|
+
* Default: true (for backward compatibility)
|
|
219
|
+
*/
|
|
220
|
+
storeUncompressedAudio?: boolean
|
|
221
|
+
}
|
|
222
|
+
|
|
210
223
|
// Add new type for interruption reasons
|
|
211
224
|
export type RecordingInterruptionReason =
|
|
212
225
|
/** Audio focus was lost to another app */
|
|
@@ -312,6 +325,9 @@ export interface RecordingConfig {
|
|
|
312
325
|
/** iOS-specific configuration */
|
|
313
326
|
ios?: IOSConfig
|
|
314
327
|
|
|
328
|
+
/** Web-specific configuration options */
|
|
329
|
+
web?: WebConfig
|
|
330
|
+
|
|
315
331
|
/** Duration of each segment in milliseconds for analysis (default: 100) */
|
|
316
332
|
segmentDurationMs?: number
|
|
317
333
|
|
|
@@ -113,7 +113,56 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
113
113
|
// Utility to handle user media stream
|
|
114
114
|
async getMediaStream() {
|
|
115
115
|
try {
|
|
116
|
-
|
|
116
|
+
this.logger?.debug('Requesting user media (microphone)...')
|
|
117
|
+
|
|
118
|
+
// First check if the browser supports the necessary audio APIs
|
|
119
|
+
if (!navigator?.mediaDevices?.getUserMedia) {
|
|
120
|
+
this.logger?.error(
|
|
121
|
+
'Browser does not support mediaDevices.getUserMedia'
|
|
122
|
+
)
|
|
123
|
+
throw new Error('Browser does not support audio recording')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Get media with detailed audio constraints for better diagnostics
|
|
127
|
+
const constraints = {
|
|
128
|
+
audio: {
|
|
129
|
+
echoCancellation: true,
|
|
130
|
+
noiseSuppression: true,
|
|
131
|
+
autoGainControl: true,
|
|
132
|
+
// Add deviceId constraint if specified
|
|
133
|
+
...(this.recordingConfig?.deviceId
|
|
134
|
+
? {
|
|
135
|
+
deviceId: {
|
|
136
|
+
exact: this.recordingConfig.deviceId,
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
: {}),
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.logger?.debug('Media constraints:', constraints)
|
|
144
|
+
|
|
145
|
+
const stream =
|
|
146
|
+
await navigator.mediaDevices.getUserMedia(constraints)
|
|
147
|
+
|
|
148
|
+
// Get detailed info about the audio track for debugging
|
|
149
|
+
const audioTracks = stream.getAudioTracks()
|
|
150
|
+
if (audioTracks.length > 0) {
|
|
151
|
+
const track = audioTracks[0]
|
|
152
|
+
const settings = track.getSettings()
|
|
153
|
+
this.logger?.debug('Audio track obtained:', {
|
|
154
|
+
label: track.label,
|
|
155
|
+
id: track.id,
|
|
156
|
+
enabled: track.enabled,
|
|
157
|
+
muted: track.muted,
|
|
158
|
+
readyState: track.readyState,
|
|
159
|
+
settings,
|
|
160
|
+
})
|
|
161
|
+
} else {
|
|
162
|
+
this.logger?.warn('Stream has no audio tracks!')
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return stream
|
|
117
166
|
} catch (error) {
|
|
118
167
|
this.logger?.error('Failed to get media stream:', error)
|
|
119
168
|
throw error
|
|
@@ -400,7 +449,8 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
400
449
|
this.logger?.debug('Starting stop process')
|
|
401
450
|
|
|
402
451
|
try {
|
|
403
|
-
const { compressedBlob } =
|
|
452
|
+
const { compressedBlob, uncompressedBlob } =
|
|
453
|
+
await this.customRecorder.stop()
|
|
404
454
|
|
|
405
455
|
this.isRecording = false
|
|
406
456
|
this.isPaused = false
|
|
@@ -409,20 +459,52 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
409
459
|
let fileUri = `${this.streamUuid}.${this.extension}`
|
|
410
460
|
let mimeType = `audio/${this.extension}`
|
|
411
461
|
|
|
412
|
-
//
|
|
413
|
-
|
|
462
|
+
// Handle both compressed and uncompressed blobs according to configuration
|
|
463
|
+
const compressionEnabled =
|
|
464
|
+
this.recordingConfig?.compression?.enabled ?? false
|
|
465
|
+
|
|
466
|
+
// Process compressed blob if available
|
|
467
|
+
if (compressedBlob) {
|
|
414
468
|
const compressedUri = URL.createObjectURL(compressedBlob)
|
|
415
|
-
|
|
469
|
+
const compressedInfo = {
|
|
416
470
|
compressedFileUri: compressedUri,
|
|
417
471
|
size: compressedBlob.size,
|
|
418
472
|
mimeType: 'audio/webm',
|
|
419
473
|
format: 'opus',
|
|
420
|
-
bitrate:
|
|
474
|
+
bitrate:
|
|
475
|
+
this.recordingConfig?.compression?.bitrate ?? 128000,
|
|
421
476
|
}
|
|
422
477
|
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
|
|
478
|
+
// If compression is enabled, use compressed blob as primary format
|
|
479
|
+
if (compressionEnabled) {
|
|
480
|
+
this.logger?.debug(
|
|
481
|
+
'Using compressed audio as primary output'
|
|
482
|
+
)
|
|
483
|
+
fileUri = compressedUri
|
|
484
|
+
mimeType = 'audio/webm'
|
|
485
|
+
|
|
486
|
+
// Store compression info
|
|
487
|
+
compression = compressedInfo
|
|
488
|
+
} else {
|
|
489
|
+
// Compression was enabled during recording but not set as primary
|
|
490
|
+
// Store as alternate format
|
|
491
|
+
compression = compressedInfo
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Process uncompressed WAV if available
|
|
496
|
+
if (uncompressedBlob) {
|
|
497
|
+
const wavUri = URL.createObjectURL(uncompressedBlob)
|
|
498
|
+
|
|
499
|
+
// If compression is disabled or no compressed blob is available,
|
|
500
|
+
// use WAV as primary format
|
|
501
|
+
if (!compressionEnabled || !compressedBlob) {
|
|
502
|
+
this.logger?.debug(
|
|
503
|
+
'Using uncompressed WAV as primary output'
|
|
504
|
+
)
|
|
505
|
+
fileUri = wavUri
|
|
506
|
+
mimeType = 'audio/wav'
|
|
507
|
+
}
|
|
426
508
|
}
|
|
427
509
|
|
|
428
510
|
// Use the stored streamUuid for the final filename
|
|
@@ -443,6 +525,14 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
443
525
|
// Reset after creating the result
|
|
444
526
|
this.streamUuid = null
|
|
445
527
|
|
|
528
|
+
// Reset recording state variables to prepare for next recording
|
|
529
|
+
this.currentDurationMs = 0
|
|
530
|
+
this.currentSize = 0
|
|
531
|
+
this.lastEmittedSize = 0
|
|
532
|
+
this.totalCompressedSize = 0
|
|
533
|
+
this.lastEmittedCompressionSize = 0
|
|
534
|
+
this.audioChunks = []
|
|
535
|
+
|
|
446
536
|
return result
|
|
447
537
|
} catch (error) {
|
|
448
538
|
this.logger?.error('Error stopping recording:', error)
|