@siteed/expo-audio-studio 2.8.6 → 2.10.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/android/build.gradle +9 -0
  3. package/android/src/androidTest/assets/chorus.wav +0 -0
  4. package/android/src/androidTest/assets/jfk.wav +0 -0
  5. package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
  6. package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
  7. package/android/src/androidTest/java/net/siteed/audiostream/AudioProcessorInstrumentedTest.kt +197 -0
  8. package/android/src/androidTest/java/net/siteed/audiostream/AudioRecorderInstrumentedTest.kt +541 -0
  9. package/android/src/androidTest/java/net/siteed/audiostream/integration/BufferDurationIntegrationTest.kt +324 -0
  10. package/android/src/androidTest/java/net/siteed/audiostream/integration/OutputControlIntegrationTest.kt +340 -0
  11. package/android/src/androidTest/java/net/siteed/audiostream/integration/README.md +95 -0
  12. package/android/src/androidTest/java/net/siteed/audiostream/integration/run_integration_tests.sh +28 -0
  13. package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +264 -13
  14. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +3 -15
  15. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +118 -55
  16. package/android/src/main/java/net/siteed/audiostream/LogUtils.kt +32 -4
  17. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +50 -15
  18. package/android/src/test/java/net/siteed/audiostream/AudioFileHandlerTest.kt +279 -0
  19. package/android/src/test/java/net/siteed/audiostream/AudioFormatUtilsTest.kt +273 -0
  20. package/android/src/test/resources/chorus.wav +0 -0
  21. package/android/src/test/resources/generate_test_audio.py +94 -0
  22. package/android/src/test/resources/jfk.wav +0 -0
  23. package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
  24. package/android/src/test/resources/recorder_hello_world.wav +0 -0
  25. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
  26. package/build/cjs/ExpoAudioStream.types.js.map +1 -1
  27. package/build/cjs/ExpoAudioStream.web.js +38 -35
  28. package/build/cjs/ExpoAudioStream.web.js.map +1 -1
  29. package/build/cjs/WebRecorder.web.js +122 -102
  30. package/build/cjs/WebRecorder.web.js.map +1 -1
  31. package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
  32. package/build/esm/ExpoAudioStream.types.js.map +1 -1
  33. package/build/esm/ExpoAudioStream.web.js +38 -35
  34. package/build/esm/ExpoAudioStream.web.js.map +1 -1
  35. package/build/esm/WebRecorder.web.js +122 -102
  36. package/build/esm/WebRecorder.web.js.map +1 -1
  37. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts +3 -1
  38. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -1
  39. package/build/types/ExpoAudioStream.types.d.ts +54 -22
  40. package/build/types/ExpoAudioStream.types.d.ts.map +1 -1
  41. package/build/types/ExpoAudioStream.web.d.ts.map +1 -1
  42. package/build/types/WebRecorder.web.d.ts +19 -3
  43. package/build/types/WebRecorder.web.d.ts.map +1 -1
  44. package/ios/AudioNotificationManager.swift +2 -6
  45. package/ios/AudioStreamManager.swift +116 -50
  46. package/ios/ExpoAudioStream.podspec +6 -0
  47. package/ios/ExpoAudioStreamModule.swift +11 -8
  48. package/ios/ExpoAudioStudioTests/AudioFileHandlerTests.swift +338 -0
  49. package/ios/ExpoAudioStudioTests/AudioFormatUtilsTests.swift +331 -0
  50. package/ios/ExpoAudioStudioTests/AudioTestHelpers.swift +130 -0
  51. package/ios/ExpoAudioStudioTests/Info.plist +22 -0
  52. package/ios/ExpoAudioStudioTests/SimpleAudioTest.swift +98 -0
  53. package/ios/ExpoAudioStudioTests/TestAudioGenerator.swift +75 -0
  54. package/ios/RecordingSettings.swift +53 -22
  55. package/ios/tests/integration/buffer_duration_test.swift +185 -0
  56. package/ios/tests/integration/output_control_test.swift +322 -0
  57. package/ios/tests/integration/run_integration_tests.sh +27 -0
  58. package/ios/tests/standalone/audio_processing_test.swift +144 -0
  59. package/ios/tests/standalone/audio_recording_test.swift +277 -0
  60. package/ios/tests/standalone/audio_streaming_test.swift +249 -0
  61. package/ios/tests/standalone/standalone_test.swift +144 -0
  62. package/package.json +140 -133
  63. package/src/AudioAnalysis/AudioAnalysis.types.ts +8 -1
  64. package/src/ExpoAudioStream.types.ts +66 -22
  65. package/src/ExpoAudioStream.web.ts +45 -39
  66. package/src/WebRecorder.web.ts +164 -130
  67. package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +0 -56
  68. package/ios/siteedexpoaudiostudio.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  69. package/ios/siteedexpoaudiostudio.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  70. /package/plugin/build/{index.d.ts → index.d.cts} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoAudioStream.web.js","sourceRoot":"","sources":["../../src/ExpoAudioStream.web.ts"],"names":[],"mappings":"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","sourcesContent":["// src/ExpoAudioStreamModule.web.ts\nimport { LegacyEventEmitter } from 'expo-modules-core'\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport {\n AudioRecording,\n AudioStreamStatus,\n BitDepth,\n ConsoleLike,\n RecordingConfig,\n RecordingInterruptionReason,\n StartRecordingResult,\n} from './ExpoAudioStream.types'\nimport { WebRecorder } from './WebRecorder.web'\nimport { AudioEventPayload } from './events'\nimport { encodingToBitDepth } from './utils/encodingToBitDepth'\n\nexport interface AudioStreamEvent {\n type: string\n device?: string\n timestamp: Date\n}\n\nexport interface ExpoAudioStreamOptions {\n logger?: ConsoleLike\n eventCallback?: (event: AudioStreamEvent) => void\n}\n\nexport interface EmitAudioEventProps {\n data: Float32Array\n position: number\n compression?: {\n data: Blob\n size: number\n totalSize: number\n mimeType: string\n format: string\n bitrate: number\n }\n}\nexport type EmitAudioEventFunction = (_: EmitAudioEventProps) => void\nexport type EmitAudioAnalysisFunction = (_: AudioAnalysis) => void\n\nexport interface ExpoAudioStreamWebProps {\n logger?: ConsoleLike\n audioWorkletUrl: string\n featuresExtratorUrl: string\n maxBufferSize?: number // Maximum number of chunks to keep in memory\n}\n\nexport class ExpoAudioStreamWeb extends LegacyEventEmitter {\n customRecorder: WebRecorder | null\n audioChunks: Float32Array[]\n isRecording: boolean\n isPaused: boolean\n recordingStartTime: number\n pausedTime: number\n currentDurationMs: number\n currentSize: number\n currentInterval: number\n currentIntervalAnalysis: number\n lastEmittedSize: number\n lastEmittedTime: number\n lastEmittedCompressionSize: number\n lastEmittedAnalysisTime: number\n streamUuid: string | null\n extension: 'webm' | 'wav' = 'wav' // Default extension is 'wav'\n recordingConfig?: RecordingConfig\n bitDepth: BitDepth // Bit depth of the audio\n audioWorkletUrl: string\n featuresExtratorUrl: string\n logger?: ConsoleLike\n latestPosition: number = 0\n totalCompressedSize: number = 0\n private readonly maxBufferSize: number\n private eventCallback?: (event: AudioStreamEvent) => void\n\n constructor({\n audioWorkletUrl,\n featuresExtratorUrl,\n logger,\n maxBufferSize = 100, // Default to storing last 100 chunks (1 chunk = 0.5 seconds)\n }: ExpoAudioStreamWebProps) {\n const mockNativeModule = {\n addListener: () => {},\n removeListeners: () => {},\n }\n super(mockNativeModule) // Pass the mock native module to the parent class\n\n this.logger = logger\n this.customRecorder = null\n this.audioChunks = []\n this.isRecording = false\n this.isPaused = false\n this.recordingStartTime = 0\n this.pausedTime = 0\n this.currentDurationMs = 0\n this.currentSize = 0\n this.bitDepth = 32 // Default\n this.currentInterval = 1000 // Default interval in ms\n this.currentIntervalAnalysis = 500 // Default analysis interval in ms\n this.lastEmittedSize = 0\n this.lastEmittedTime = 0\n this.latestPosition = 0\n this.lastEmittedCompressionSize = 0\n this.lastEmittedAnalysisTime = 0\n this.streamUuid = null // Initialize UUID on first recording start\n this.audioWorkletUrl = audioWorkletUrl\n this.featuresExtratorUrl = featuresExtratorUrl\n this.maxBufferSize = maxBufferSize\n }\n\n // Utility to handle user media stream\n async getMediaStream() {\n try {\n this.logger?.debug('Requesting user media (microphone)...')\n\n // First check if the browser supports the necessary audio APIs\n if (!navigator?.mediaDevices?.getUserMedia) {\n this.logger?.error(\n 'Browser does not support mediaDevices.getUserMedia'\n )\n throw new Error('Browser does not support audio recording')\n }\n\n // Get media with detailed audio constraints for better diagnostics\n const constraints = {\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n // Add deviceId constraint if specified\n ...(this.recordingConfig?.deviceId\n ? {\n deviceId: {\n exact: this.recordingConfig.deviceId,\n },\n }\n : {}),\n },\n }\n\n this.logger?.debug('Media constraints:', constraints)\n\n const stream =\n await navigator.mediaDevices.getUserMedia(constraints)\n\n // Get detailed info about the audio track for debugging\n const audioTracks = stream.getAudioTracks()\n if (audioTracks.length > 0) {\n const track = audioTracks[0]\n const settings = track.getSettings()\n this.logger?.debug('Audio track obtained:', {\n label: track.label,\n id: track.id,\n enabled: track.enabled,\n muted: track.muted,\n readyState: track.readyState,\n settings,\n })\n } else {\n this.logger?.warn('Stream has no audio tracks!')\n }\n\n return stream\n } catch (error) {\n this.logger?.error('Failed to get media stream:', error)\n throw error\n }\n }\n\n // Prepare recording with options\n async prepareRecording(\n recordingConfig: RecordingConfig = {}\n ): Promise<boolean> {\n if (this.isRecording) {\n this.logger?.warn(\n 'Cannot prepare: Recording is already in progress'\n )\n return false\n }\n\n try {\n // Check permissions and initialize basic settings\n await this.getMediaStream().then((stream) => {\n // Just verify we can access the microphone by getting a stream, then release it\n stream.getTracks().forEach((track) => track.stop())\n })\n\n this.bitDepth = encodingToBitDepth({\n encoding: recordingConfig.encoding ?? 'pcm_32bit',\n })\n\n // Store recording configuration for later use\n this.recordingConfig = recordingConfig\n\n // Use custom filename if provided, otherwise fallback to timestamp\n if (recordingConfig.filename) {\n // Remove any existing extension from the filename\n this.streamUuid = recordingConfig.filename.replace(\n /\\.[^/.]+$/,\n ''\n )\n } else {\n this.streamUuid = Date.now().toString()\n }\n\n this.logger?.debug('Recording preparation completed successfully')\n return true\n } catch (error) {\n this.logger?.error('Error preparing recording:', error)\n return false\n }\n }\n\n // Start recording with options\n async startRecording(\n recordingConfig: RecordingConfig = {}\n ): Promise<StartRecordingResult> {\n if (this.isRecording) {\n throw new Error('Recording is already in progress')\n }\n\n // If we haven't prepared or have different settings, prepare now\n if (\n !this.recordingConfig ||\n this.recordingConfig.sampleRate !== recordingConfig.sampleRate ||\n this.recordingConfig.channels !== recordingConfig.channels ||\n this.recordingConfig.encoding !== recordingConfig.encoding\n ) {\n await this.prepareRecording(recordingConfig)\n } else {\n this.logger?.debug(\n 'Using previously prepared recording configuration'\n )\n }\n\n // Save recording config for reference\n this.recordingConfig = recordingConfig\n\n const audioContext = new (window.AudioContext ||\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore - Allow webkitAudioContext for Safari\n window.webkitAudioContext)()\n const stream = await this.getMediaStream()\n\n const source = audioContext.createMediaStreamSource(stream)\n\n this.customRecorder = new WebRecorder({\n logger: this.logger,\n audioContext,\n source,\n recordingConfig,\n emitAudioEventCallback: this.customRecorderEventCallback.bind(this),\n emitAudioAnalysisCallback:\n this.customRecorderAnalysisCallback.bind(this),\n onInterruption: this.handleRecordingInterruption.bind(this),\n })\n await this.customRecorder.init()\n this.customRecorder.start()\n\n this.isRecording = true\n this.recordingStartTime = Date.now()\n this.pausedTime = 0\n this.isPaused = false\n this.lastEmittedSize = 0\n this.lastEmittedTime = 0\n this.lastEmittedCompressionSize = 0\n this.currentInterval = recordingConfig.interval ?? 1000\n this.currentIntervalAnalysis = recordingConfig.intervalAnalysis ?? 500\n this.lastEmittedAnalysisTime = Date.now()\n\n // Use custom filename if provided, otherwise fallback to timestamp\n if (recordingConfig.filename) {\n // Remove any existing extension from the filename\n this.streamUuid = recordingConfig.filename.replace(/\\.[^/.]+$/, '')\n } else {\n this.streamUuid = Date.now().toString()\n }\n\n const fileUri = `${this.streamUuid}.${this.extension}`\n const streamConfig: StartRecordingResult = {\n fileUri,\n mimeType: `audio/${this.extension}`,\n bitDepth: this.bitDepth,\n channels: recordingConfig.channels ?? 1,\n sampleRate: recordingConfig.sampleRate ?? 44100,\n compression: recordingConfig.compression\n ? {\n ...recordingConfig.compression,\n bitrate: recordingConfig.compression?.bitrate ?? 128000,\n size: 0,\n mimeType: 'audio/webm',\n format: recordingConfig.compression?.format ?? 'opus',\n compressedFileUri: '',\n }\n : undefined,\n }\n return streamConfig\n }\n\n /**\n * Centralized handler for recording interruptions\n */\n private handleRecordingInterruption(event: {\n reason: RecordingInterruptionReason | string\n isPaused: boolean\n timestamp: number\n message?: string\n }): void {\n this.logger?.debug(`Received recording interruption: ${event.reason}`)\n\n // Update local state if the interruption should pause recording\n if (event.isPaused) {\n this.isPaused = true\n\n // If this is a device disconnection, handle according to behavior setting\n if (event.reason === 'deviceDisconnected') {\n this.pausedTime = Date.now()\n\n // Check if we should try fallback to another device\n if (\n this.recordingConfig?.deviceDisconnectionBehavior ===\n 'fallback'\n ) {\n this.logger?.debug(\n 'Device disconnected with fallback behavior - attempting to switch to default device'\n )\n\n // Try to restart with default device\n this.handleDeviceFallback().catch((error) => {\n // If fallback fails, emit warning\n this.logger?.error('Device fallback failed:', error)\n this.emit('onRecordingInterrupted', {\n reason: 'deviceSwitchFailed',\n isPaused: true,\n timestamp: Date.now(),\n message:\n 'Failed to switch to fallback device. Recording paused.',\n })\n })\n } else {\n // Just warn about disconnection if fallback not enabled\n this.logger?.warn(\n 'Device disconnected - recording paused automatically'\n )\n this.emit('onRecordingInterrupted', event)\n }\n } else {\n // For other interruption types, just emit the event\n this.emit('onRecordingInterrupted', event)\n }\n } else {\n // If not causing a pause, just forward the event\n this.emit('onRecordingInterrupted', event)\n }\n }\n\n /**\n * Handler for audio events from the WebRecorder\n */\n private customRecorderEventCallback({\n data,\n position,\n compression,\n }: EmitAudioEventProps): void {\n // Keep only the latest chunks based on maxBufferSize\n this.audioChunks.push(new Float32Array(data))\n if (this.audioChunks.length > this.maxBufferSize) {\n this.audioChunks.shift() // Remove oldest chunk\n }\n this.currentSize += data.byteLength\n this.emitAudioEvent({ data, position, compression })\n this.lastEmittedTime = Date.now()\n this.lastEmittedSize = this.currentSize\n this.lastEmittedCompressionSize = compression?.size ?? 0\n }\n\n /**\n * Handler for audio analysis events from the WebRecorder\n */\n private customRecorderAnalysisCallback(\n audioAnalysisData: AudioAnalysis\n ): void {\n this.emit('AudioAnalysis', audioAnalysisData)\n }\n\n // Get recording duration\n private getRecordingDuration(): number {\n if (!this.isRecording) {\n return 0\n }\n\n return this.currentDurationMs\n }\n\n emitAudioEvent({ data, position, compression }: EmitAudioEventProps) {\n const fileUri = `${this.streamUuid}.${this.extension}`\n if (compression?.size) {\n this.lastEmittedCompressionSize = compression.size\n this.totalCompressedSize = compression.totalSize\n }\n\n // Update latest position for tracking\n this.latestPosition = position\n\n // Calculate duration of this chunk in ms\n const sampleRate = this.recordingConfig?.sampleRate || 44100\n const chunkDurationMs = (data.length / sampleRate) * 1000\n\n // Handle duration calculation\n if (this.customRecorder?.isFirstChunkAfterSwitch) {\n this.logger?.debug(\n `Processing first chunk after device switch, duration preserved at ${this.currentDurationMs}ms`\n )\n this.customRecorder.isFirstChunkAfterSwitch = false\n } else {\n this.currentDurationMs += chunkDurationMs\n }\n\n const audioEventPayload: AudioEventPayload = {\n fileUri,\n mimeType: `audio/${this.extension}`,\n lastEmittedSize: this.lastEmittedSize,\n deltaSize: data.byteLength,\n position,\n totalSize: this.currentSize,\n buffer: data,\n streamUuid: this.streamUuid ?? '',\n compression: compression\n ? {\n data: compression?.data,\n totalSize: this.totalCompressedSize,\n eventDataSize: compression?.size ?? 0,\n position,\n }\n : undefined,\n }\n\n this.emit('AudioData', audioEventPayload)\n }\n\n // Stop recording\n async stopRecording(): Promise<AudioRecording> {\n if (!this.customRecorder) {\n throw new Error('Recorder is not initialized')\n }\n\n this.logger?.debug('Starting stop process')\n\n try {\n const { compressedBlob, uncompressedBlob } =\n await this.customRecorder.stop()\n\n this.isRecording = false\n this.isPaused = false\n\n let compression: AudioRecording['compression']\n let fileUri = `${this.streamUuid}.${this.extension}`\n let mimeType = `audio/${this.extension}`\n\n // Handle both compressed and uncompressed blobs according to configuration\n const compressionEnabled =\n this.recordingConfig?.compression?.enabled ?? false\n\n // Process compressed blob if available\n if (compressedBlob) {\n const compressedUri = URL.createObjectURL(compressedBlob)\n const compressedInfo = {\n compressedFileUri: compressedUri,\n size: compressedBlob.size,\n mimeType: 'audio/webm',\n format: 'opus',\n bitrate:\n this.recordingConfig?.compression?.bitrate ?? 128000,\n }\n\n // If compression is enabled, use compressed blob as primary format\n if (compressionEnabled) {\n this.logger?.debug(\n 'Using compressed audio as primary output'\n )\n fileUri = compressedUri\n mimeType = 'audio/webm'\n\n // Store compression info\n compression = compressedInfo\n } else {\n // Compression was enabled during recording but not set as primary\n // Store as alternate format\n compression = compressedInfo\n }\n }\n\n // Process uncompressed WAV if available\n if (uncompressedBlob) {\n const wavUri = URL.createObjectURL(uncompressedBlob)\n\n // If compression is disabled or no compressed blob is available,\n // use WAV as primary format\n if (!compressionEnabled || !compressedBlob) {\n this.logger?.debug(\n 'Using uncompressed WAV as primary output'\n )\n fileUri = wavUri\n mimeType = 'audio/wav'\n }\n }\n\n // Use the stored streamUuid for the final filename\n const filename = `${this.streamUuid}.${this.extension}`\n const result: AudioRecording = {\n fileUri,\n filename,\n bitDepth: this.bitDepth,\n createdAt: this.recordingStartTime,\n channels: this.recordingConfig?.channels ?? 1,\n sampleRate: this.recordingConfig?.sampleRate ?? 44100,\n durationMs: this.currentDurationMs,\n size: this.currentSize,\n mimeType,\n compression,\n }\n\n // Reset after creating the result\n this.streamUuid = null\n\n // Reset recording state variables to prepare for next recording\n this.currentDurationMs = 0\n this.currentSize = 0\n this.lastEmittedSize = 0\n this.totalCompressedSize = 0\n this.lastEmittedCompressionSize = 0\n this.audioChunks = []\n\n return result\n } catch (error) {\n this.logger?.error('Error stopping recording:', error)\n throw error\n }\n }\n\n // Pause recording\n async pauseRecording() {\n if (!this.isRecording) {\n throw new Error('Recording is not active')\n }\n\n if (this.isPaused) {\n this.logger?.debug('Recording already paused, skipping')\n return\n }\n\n try {\n if (this.customRecorder) {\n this.customRecorder.pause()\n }\n this.isPaused = true\n this.pausedTime = Date.now()\n } catch (error) {\n this.logger?.error('Error in pauseRecording', error)\n // Even if the pause operation failed, make sure our state is consistent\n this.isPaused = true\n this.pausedTime = Date.now()\n }\n }\n\n // Resume recording\n async resumeRecording() {\n if (!this.isPaused) {\n throw new Error('Recording is not paused')\n }\n\n this.logger?.debug('Resuming recording', {\n deviceDisconnectionBehavior:\n this.recordingConfig?.deviceDisconnectionBehavior,\n isDeviceDisconnected: this.customRecorder?.isDeviceDisconnected,\n })\n\n try {\n // If we have no recorder, or if the device is disconnected, always attempt fallback\n if (\n !this.customRecorder ||\n this.customRecorder.isDeviceDisconnected\n ) {\n this.logger?.debug(\n 'No recorder exists or device disconnected - attempting fallback on resume'\n )\n await this.handleDeviceFallback()\n // handleDeviceFallback will manage resuming if successful, or emit error if failed.\n return\n }\n\n // Normal resume path - device is still connected\n this.customRecorder.resume()\n this.isPaused = false\n\n // Adjust the recording start time to account for the pause duration\n const pauseDuration = Date.now() - this.pausedTime\n this.recordingStartTime += pauseDuration\n this.pausedTime = 0\n\n this.emit('onRecordingInterrupted', {\n reason: 'userResumed',\n isPaused: false,\n timestamp: Date.now(),\n })\n } catch (error) {\n this.logger?.error('Resume failed:', error)\n // Fallback to emitting a general failure if resume fails unexpectedly\n this.emit('onRecordingInterrupted', {\n reason: 'resumeFailed', // Use a more specific reason\n isPaused: true, // Remain paused if resume fails\n timestamp: Date.now(),\n message:\n 'Failed to resume recording. Please stop and start again.',\n })\n }\n }\n\n // Get current status\n status() {\n const durationMs = this.getRecordingDuration()\n\n const status: AudioStreamStatus = {\n isRecording: this.isRecording,\n isPaused: this.isPaused,\n durationMs,\n size: this.currentSize,\n interval: this.currentInterval,\n intervalAnalysis: this.currentIntervalAnalysis,\n mimeType: `audio/${this.extension}`,\n compression: this.recordingConfig?.compression?.enabled\n ? {\n size: this.totalCompressedSize,\n mimeType: 'audio/webm',\n format: this.recordingConfig.compression.format ?? 'opus',\n bitrate:\n this.recordingConfig.compression.bitrate ?? 128000,\n compressedFileUri: `${this.streamUuid}.webm`,\n }\n : undefined,\n }\n return status\n }\n\n /**\n * Handles device fallback when the current device is disconnected\n */\n private async handleDeviceFallback(): Promise<boolean> {\n this.logger?.debug('Starting device fallback procedure')\n\n if (!this.isRecording) {\n return false\n }\n\n try {\n // Save important state before switching\n const currentPosition = this.latestPosition\n const existingAudioChunks = [...this.audioChunks]\n\n // Save compressed chunks if available\n let compressedChunks: Blob[] = []\n if (this.customRecorder) {\n try {\n compressedChunks = this.customRecorder.getCompressedChunks()\n } catch (err) {\n this.logger?.warn('Failed to get compressed chunks:', err)\n }\n }\n\n // Save the current counter value for continuity\n let currentDataPointCounter = 0\n if (this.customRecorder) {\n currentDataPointCounter =\n this.customRecorder.getDataPointCounter()\n }\n\n // Clean up existing recorder\n if (this.customRecorder) {\n try {\n this.customRecorder.cleanup()\n } catch (cleanupError) {\n this.logger?.warn('Error during cleanup:', cleanupError)\n }\n }\n\n // Keep recording state true but mark as paused\n this.isPaused = true\n this.pausedTime = Date.now()\n\n // Store current size and other stats\n const previousTotalSize = this.currentSize\n const previousLastEmittedSize = this.lastEmittedSize\n const previousCompressedSize = this.totalCompressedSize\n\n // Try to get a fallback device\n const fallbackDeviceInfo = await this.getFallbackDevice()\n if (!fallbackDeviceInfo) {\n this.emit('onRecordingInterrupted', {\n reason: 'deviceSwitchFailed',\n isPaused: true,\n timestamp: Date.now(),\n message:\n 'Failed to switch to fallback device. Recording paused.',\n })\n return false\n }\n\n // Start recording with the new device\n try {\n const stream = await this.requestPermissionsAndGetUserMedia(\n fallbackDeviceInfo.deviceId\n )\n const audioContext = new (window.AudioContext ||\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore - Allow webkitAudioContext for Safari\n window.webkitAudioContext)()\n\n const source = audioContext.createMediaStreamSource(stream)\n\n // Create a new recorder with the fallback device\n this.customRecorder = new WebRecorder({\n logger: this.logger,\n audioContext,\n source,\n recordingConfig: this.recordingConfig || {},\n emitAudioEventCallback:\n this.customRecorderEventCallback.bind(this),\n emitAudioAnalysisCallback:\n this.customRecorderAnalysisCallback.bind(this),\n onInterruption: this.handleRecordingInterruption.bind(this),\n })\n\n await this.customRecorder.init()\n\n // Set the initial position to continue from the previous device\n this.customRecorder.setPosition(currentPosition)\n\n // Reset the data point counter to continue from where the previous device left off\n if (currentDataPointCounter > 0) {\n this.customRecorder.resetDataPointCounter(\n currentDataPointCounter\n )\n }\n\n // Prepare the recorder to handle the device switch properly\n this.customRecorder.prepareForDeviceSwitch()\n\n // Restore the existing audio chunks\n if (existingAudioChunks.length > 0) {\n this.audioChunks = existingAudioChunks\n }\n\n // Restore compressed chunks if available\n if (compressedChunks.length > 0) {\n this.customRecorder.setCompressedChunks(compressedChunks)\n }\n\n // Start the new recorder while preserving counters\n this.customRecorder.start(true)\n\n // Update recording state\n this.isPaused = false\n this.recordingStartTime = Date.now()\n\n // Restore size counters to maintain continuity\n this.currentSize = previousTotalSize\n this.lastEmittedSize = previousLastEmittedSize\n this.totalCompressedSize = previousCompressedSize\n\n // Notify that we switched to a fallback device\n if (this.eventCallback) {\n this.eventCallback({\n type: 'deviceFallback',\n device: fallbackDeviceInfo.deviceId,\n timestamp: new Date(),\n })\n }\n return true\n } catch (error) {\n this.logger?.error(\n 'Failed to start recording with fallback device',\n error\n )\n this.isPaused = true\n this.emit('onRecordingInterrupted', {\n reason: 'deviceSwitchFailed',\n isPaused: true,\n timestamp: Date.now(),\n message:\n 'Failed to switch to fallback device. Recording paused.',\n })\n return false\n }\n } catch (error) {\n this.logger?.error('Failed to use fallback device', error)\n this.isPaused = true\n this.emit('onRecordingInterrupted', {\n reason: 'deviceSwitchFailed',\n isPaused: true,\n timestamp: Date.now(),\n message:\n 'Failed to switch to fallback device. Recording paused.',\n })\n return false\n }\n }\n\n /**\n * Attempts to get a fallback audio device\n */\n private async getFallbackDevice(): Promise<MediaDeviceInfo | null> {\n try {\n // Get list of available audio input devices\n const devices = await navigator.mediaDevices.enumerateDevices()\n const audioInputDevices = devices.filter(\n (device) => device.kind === 'audioinput'\n )\n\n if (audioInputDevices.length === 0) {\n return null\n }\n\n // Try to find a device that's not the current one\n if (this.customRecorder) {\n try {\n // Use mediaDevices.enumerateDevices to find the current active device\n const tracks = navigator.mediaDevices\n .getUserMedia({ audio: true })\n .then((stream) => {\n const track = stream.getAudioTracks()[0]\n return track ? track.label : ''\n })\n .catch(() => '')\n\n const currentTrackLabel = await tracks\n\n if (currentTrackLabel) {\n // Find a device with a different label\n const differentDevice = audioInputDevices.find(\n (device) =>\n device.label &&\n device.label !== currentTrackLabel\n )\n\n if (differentDevice) {\n return differentDevice\n }\n }\n } catch (err) {\n this.logger?.warn(\n 'Error determining current device, using default'\n )\n }\n }\n\n // Return the first available device (default device)\n return audioInputDevices[0]\n } catch (error) {\n this.logger?.error('Error finding fallback device:', error)\n return null\n }\n }\n\n /**\n * Gets user media with specific device ID\n */\n private async requestPermissionsAndGetUserMedia(\n deviceId: string\n ): Promise<MediaStream> {\n try {\n // Request the specific device\n return await navigator.mediaDevices.getUserMedia({\n audio: {\n deviceId: { exact: deviceId },\n },\n })\n } catch (error) {\n this.logger?.error(\n `Failed to get media for device ${deviceId}`,\n error\n )\n // Try with default constraints as fallback\n return await navigator.mediaDevices.getUserMedia({ audio: true })\n }\n }\n\n init(options?: ExpoAudioStreamOptions): Promise<void> {\n try {\n this.logger = options?.logger\n this.eventCallback = options?.eventCallback\n return Promise.resolve()\n } catch (error) {\n this.logger?.error('Error initializing ExpoAudioStream', error)\n return Promise.reject(error)\n }\n }\n}\n"]}
1
+ {"version":3,"file":"ExpoAudioStream.web.js","sourceRoot":"","sources":["../../src/ExpoAudioStream.web.ts"],"names":[],"mappings":"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","sourcesContent":["// src/ExpoAudioStreamModule.web.ts\nimport { LegacyEventEmitter } from 'expo-modules-core'\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport {\n AudioRecording,\n AudioStreamStatus,\n BitDepth,\n ConsoleLike,\n RecordingConfig,\n RecordingInterruptionReason,\n StartRecordingResult,\n} from './ExpoAudioStream.types'\nimport { WebRecorder } from './WebRecorder.web'\nimport { AudioEventPayload } from './events'\nimport { encodingToBitDepth } from './utils/encodingToBitDepth'\n\nexport interface AudioStreamEvent {\n type: string\n device?: string\n timestamp: Date\n}\n\nexport interface ExpoAudioStreamOptions {\n logger?: ConsoleLike\n eventCallback?: (event: AudioStreamEvent) => void\n}\n\nexport interface EmitAudioEventProps {\n data: Float32Array\n position: number\n compression?: {\n data: Blob\n size: number\n totalSize: number\n mimeType: string\n format: string\n bitrate: number\n }\n}\nexport type EmitAudioEventFunction = (_: EmitAudioEventProps) => void\nexport type EmitAudioAnalysisFunction = (_: AudioAnalysis) => void\n\nexport interface ExpoAudioStreamWebProps {\n logger?: ConsoleLike\n audioWorkletUrl: string\n featuresExtratorUrl: string\n maxBufferSize?: number // Maximum number of chunks to keep in memory\n}\n\nexport class ExpoAudioStreamWeb extends LegacyEventEmitter {\n customRecorder: WebRecorder | null\n audioChunks: Float32Array[]\n isRecording: boolean\n isPaused: boolean\n recordingStartTime: number\n pausedTime: number\n currentDurationMs: number\n currentSize: number\n currentInterval: number\n currentIntervalAnalysis: number\n lastEmittedSize: number\n lastEmittedTime: number\n lastEmittedCompressionSize: number\n lastEmittedAnalysisTime: number\n streamUuid: string | null\n extension: 'webm' | 'wav' = 'wav' // Default extension is 'wav'\n recordingConfig?: RecordingConfig\n bitDepth: BitDepth // Bit depth of the audio\n audioWorkletUrl: string\n featuresExtratorUrl: string\n logger?: ConsoleLike\n latestPosition: number = 0\n totalCompressedSize: number = 0\n private readonly maxBufferSize: number\n private eventCallback?: (event: AudioStreamEvent) => void\n\n constructor({\n audioWorkletUrl,\n featuresExtratorUrl,\n logger,\n maxBufferSize = 100, // Default to storing last 100 chunks (1 chunk = 0.5 seconds)\n }: ExpoAudioStreamWebProps) {\n const mockNativeModule = {\n addListener: () => {},\n removeListeners: () => {},\n }\n super(mockNativeModule) // Pass the mock native module to the parent class\n\n this.logger = logger\n this.customRecorder = null\n this.audioChunks = []\n this.isRecording = false\n this.isPaused = false\n this.recordingStartTime = 0\n this.pausedTime = 0\n this.currentDurationMs = 0\n this.currentSize = 0\n this.bitDepth = 32 // Default\n this.currentInterval = 1000 // Default interval in ms\n this.currentIntervalAnalysis = 500 // Default analysis interval in ms\n this.lastEmittedSize = 0\n this.lastEmittedTime = 0\n this.latestPosition = 0\n this.lastEmittedCompressionSize = 0\n this.lastEmittedAnalysisTime = 0\n this.streamUuid = null // Initialize UUID on first recording start\n this.audioWorkletUrl = audioWorkletUrl\n this.featuresExtratorUrl = featuresExtratorUrl\n this.maxBufferSize = maxBufferSize\n }\n\n // Utility to handle user media stream\n async getMediaStream() {\n try {\n this.logger?.debug('Requesting user media (microphone)...')\n\n // First check if the browser supports the necessary audio APIs\n if (!navigator?.mediaDevices?.getUserMedia) {\n this.logger?.error(\n 'Browser does not support mediaDevices.getUserMedia'\n )\n throw new Error('Browser does not support audio recording')\n }\n\n // Get media with detailed audio constraints for better diagnostics\n const constraints = {\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n // Add deviceId constraint if specified\n ...(this.recordingConfig?.deviceId\n ? {\n deviceId: {\n exact: this.recordingConfig.deviceId,\n },\n }\n : {}),\n },\n }\n\n this.logger?.debug('Media constraints:', constraints)\n\n const stream =\n await navigator.mediaDevices.getUserMedia(constraints)\n\n // Get detailed info about the audio track for debugging\n const audioTracks = stream.getAudioTracks()\n if (audioTracks.length > 0) {\n const track = audioTracks[0]\n const settings = track.getSettings()\n this.logger?.debug('Audio track obtained:', {\n label: track.label,\n id: track.id,\n enabled: track.enabled,\n muted: track.muted,\n readyState: track.readyState,\n settings,\n })\n } else {\n this.logger?.warn('Stream has no audio tracks!')\n }\n\n return stream\n } catch (error) {\n this.logger?.error('Failed to get media stream:', error)\n throw error\n }\n }\n\n // Prepare recording with options\n async prepareRecording(\n recordingConfig: RecordingConfig = {}\n ): Promise<boolean> {\n if (this.isRecording) {\n this.logger?.warn(\n 'Cannot prepare: Recording is already in progress'\n )\n return false\n }\n\n try {\n // Check permissions and initialize basic settings\n await this.getMediaStream().then((stream) => {\n // Just verify we can access the microphone by getting a stream, then release it\n stream.getTracks().forEach((track) => track.stop())\n })\n\n this.bitDepth = encodingToBitDepth({\n encoding: recordingConfig.encoding ?? 'pcm_32bit',\n })\n\n // Store recording configuration for later use\n this.recordingConfig = recordingConfig\n\n // Use custom filename if provided, otherwise fallback to timestamp\n if (recordingConfig.filename) {\n // Remove any existing extension from the filename\n this.streamUuid = recordingConfig.filename.replace(\n /\\.[^/.]+$/,\n ''\n )\n } else {\n this.streamUuid = Date.now().toString()\n }\n\n this.logger?.debug('Recording preparation completed successfully')\n return true\n } catch (error) {\n this.logger?.error('Error preparing recording:', error)\n return false\n }\n }\n\n // Start recording with options\n async startRecording(\n recordingConfig: RecordingConfig = {}\n ): Promise<StartRecordingResult> {\n if (this.isRecording) {\n throw new Error('Recording is already in progress')\n }\n\n // If we haven't prepared or have different settings, prepare now\n if (\n !this.recordingConfig ||\n this.recordingConfig.sampleRate !== recordingConfig.sampleRate ||\n this.recordingConfig.channels !== recordingConfig.channels ||\n this.recordingConfig.encoding !== recordingConfig.encoding\n ) {\n await this.prepareRecording(recordingConfig)\n } else {\n this.logger?.debug(\n 'Using previously prepared recording configuration'\n )\n }\n\n // Save recording config for reference\n this.recordingConfig = recordingConfig\n\n const audioContext = new (window.AudioContext ||\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore - Allow webkitAudioContext for Safari\n window.webkitAudioContext)()\n const stream = await this.getMediaStream()\n\n const source = audioContext.createMediaStreamSource(stream)\n\n this.customRecorder = new WebRecorder({\n logger: this.logger,\n audioContext,\n source,\n recordingConfig,\n emitAudioEventCallback: this.customRecorderEventCallback.bind(this),\n emitAudioAnalysisCallback:\n this.customRecorderAnalysisCallback.bind(this),\n onInterruption: this.handleRecordingInterruption.bind(this),\n })\n await this.customRecorder.init()\n this.customRecorder.start()\n\n this.isRecording = true\n this.recordingStartTime = Date.now()\n this.pausedTime = 0\n this.isPaused = false\n this.lastEmittedSize = 0\n this.lastEmittedTime = 0\n this.lastEmittedCompressionSize = 0\n this.currentInterval = recordingConfig.interval ?? 1000\n this.currentIntervalAnalysis = recordingConfig.intervalAnalysis ?? 500\n this.lastEmittedAnalysisTime = Date.now()\n\n // Use custom filename if provided, otherwise fallback to timestamp\n if (recordingConfig.filename) {\n // Remove any existing extension from the filename\n this.streamUuid = recordingConfig.filename.replace(/\\.[^/.]+$/, '')\n } else {\n this.streamUuid = Date.now().toString()\n }\n\n const fileUri = `${this.streamUuid}.${this.extension}`\n const streamConfig: StartRecordingResult = {\n fileUri,\n mimeType: `audio/${this.extension}`,\n bitDepth: this.bitDepth,\n channels: recordingConfig.channels ?? 1,\n sampleRate: recordingConfig.sampleRate ?? 44100,\n compression: recordingConfig.output?.compressed?.enabled\n ? {\n ...recordingConfig.output.compressed,\n bitrate:\n recordingConfig.output.compressed.bitrate ?? 128000,\n size: 0,\n mimeType: 'audio/webm',\n format:\n recordingConfig.output.compressed.format ?? 'opus',\n compressedFileUri: '',\n }\n : undefined,\n }\n return streamConfig\n }\n\n /**\n * Centralized handler for recording interruptions\n */\n private handleRecordingInterruption(event: {\n reason: RecordingInterruptionReason | string\n isPaused: boolean\n timestamp: number\n message?: string\n }): void {\n this.logger?.debug(`Received recording interruption: ${event.reason}`)\n\n // Update local state if the interruption should pause recording\n if (event.isPaused) {\n this.isPaused = true\n\n // If this is a device disconnection, handle according to behavior setting\n if (event.reason === 'deviceDisconnected') {\n this.pausedTime = Date.now()\n\n // Check if we should try fallback to another device\n if (\n this.recordingConfig?.deviceDisconnectionBehavior ===\n 'fallback'\n ) {\n this.logger?.debug(\n 'Device disconnected with fallback behavior - attempting to switch to default device'\n )\n\n // Try to restart with default device\n this.handleDeviceFallback().catch((error) => {\n // If fallback fails, emit warning\n this.logger?.error('Device fallback failed:', error)\n this.emit('onRecordingInterrupted', {\n reason: 'deviceSwitchFailed',\n isPaused: true,\n timestamp: Date.now(),\n message:\n 'Failed to switch to fallback device. Recording paused.',\n })\n })\n } else {\n // Just warn about disconnection if fallback not enabled\n this.logger?.warn(\n 'Device disconnected - recording paused automatically'\n )\n this.emit('onRecordingInterrupted', event)\n }\n } else {\n // For other interruption types, just emit the event\n this.emit('onRecordingInterrupted', event)\n }\n } else {\n // If not causing a pause, just forward the event\n this.emit('onRecordingInterrupted', event)\n }\n }\n\n /**\n * Handler for audio events from the WebRecorder\n */\n private customRecorderEventCallback({\n data,\n position,\n compression,\n }: EmitAudioEventProps): void {\n // Keep only the latest chunks based on maxBufferSize\n this.audioChunks.push(new Float32Array(data))\n if (this.audioChunks.length > this.maxBufferSize) {\n this.audioChunks.shift() // Remove oldest chunk\n }\n this.currentSize += data.byteLength\n this.emitAudioEvent({ data, position, compression })\n this.lastEmittedTime = Date.now()\n this.lastEmittedSize = this.currentSize\n this.lastEmittedCompressionSize = compression?.size ?? 0\n }\n\n /**\n * Handler for audio analysis events from the WebRecorder\n */\n private customRecorderAnalysisCallback(\n audioAnalysisData: AudioAnalysis\n ): void {\n this.emit('AudioAnalysis', audioAnalysisData)\n }\n\n // Get recording duration\n private getRecordingDuration(): number {\n if (!this.isRecording) {\n return 0\n }\n\n return this.currentDurationMs\n }\n\n emitAudioEvent({ data, position, compression }: EmitAudioEventProps) {\n const fileUri = `${this.streamUuid}.${this.extension}`\n if (compression?.size) {\n this.lastEmittedCompressionSize = compression.size\n this.totalCompressedSize = compression.totalSize\n }\n\n // Update latest position for tracking\n this.latestPosition = position\n\n // Calculate duration of this chunk in ms\n const sampleRate = this.recordingConfig?.sampleRate || 44100\n const chunkDurationMs = (data.length / sampleRate) * 1000\n\n // Handle duration calculation\n if (this.customRecorder?.isFirstChunkAfterSwitch) {\n this.logger?.debug(\n `Processing first chunk after device switch, duration preserved at ${this.currentDurationMs}ms`\n )\n this.customRecorder.isFirstChunkAfterSwitch = false\n } else {\n this.currentDurationMs += chunkDurationMs\n }\n\n const audioEventPayload: AudioEventPayload = {\n fileUri,\n mimeType: `audio/${this.extension}`,\n lastEmittedSize: this.lastEmittedSize,\n deltaSize: data.byteLength,\n position,\n totalSize: this.currentSize,\n buffer: data,\n streamUuid: this.streamUuid ?? '',\n compression: compression\n ? {\n data: compression?.data,\n totalSize: this.totalCompressedSize,\n eventDataSize: compression?.size ?? 0,\n position,\n }\n : undefined,\n }\n\n this.emit('AudioData', audioEventPayload)\n }\n\n // Stop recording\n async stopRecording(): Promise<AudioRecording> {\n if (!this.customRecorder) {\n throw new Error('Recorder is not initialized')\n }\n\n this.logger?.debug('Starting stop process')\n\n try {\n const { compressedBlob, uncompressedBlob } =\n await this.customRecorder.stop()\n\n this.isRecording = false\n this.isPaused = false\n\n let compression: AudioRecording['compression']\n let fileUri = `${this.streamUuid}.${this.extension}`\n let mimeType = `audio/${this.extension}`\n\n // Handle both compressed and uncompressed blobs according to new output configuration\n const primaryEnabled =\n this.recordingConfig?.output?.primary?.enabled ?? true\n const compressedEnabled =\n this.recordingConfig?.output?.compressed?.enabled ?? false\n\n // Process compressed blob if available and enabled\n if (compressedBlob && compressedEnabled) {\n const compressedUri = URL.createObjectURL(compressedBlob)\n const compressedInfo = {\n compressedFileUri: compressedUri,\n size: compressedBlob.size,\n mimeType: 'audio/webm',\n format:\n this.recordingConfig?.output?.compressed?.format ??\n 'opus',\n bitrate:\n this.recordingConfig?.output?.compressed?.bitrate ??\n 128000,\n }\n\n // Store compression info\n compression = compressedInfo\n\n // If primary is disabled, use compressed as main file\n if (!primaryEnabled) {\n this.logger?.debug(\n 'Using compressed audio as primary output (primary disabled)'\n )\n fileUri = compressedUri\n mimeType = 'audio/webm'\n }\n }\n\n // Process uncompressed WAV if available and primary is enabled\n if (uncompressedBlob && primaryEnabled) {\n const wavUri = URL.createObjectURL(uncompressedBlob)\n fileUri = wavUri\n mimeType = 'audio/wav'\n } else if (!primaryEnabled && !compressedEnabled) {\n // No outputs enabled - streaming only mode\n this.logger?.debug('No outputs enabled - streaming only mode')\n fileUri = ''\n mimeType = 'audio/wav'\n }\n\n // Use the stored streamUuid for the final filename\n const filename = fileUri\n ? `${this.streamUuid}.${this.extension}`\n : 'stream-only'\n const result: AudioRecording = {\n fileUri,\n filename,\n bitDepth: this.bitDepth,\n createdAt: this.recordingStartTime,\n channels: this.recordingConfig?.channels ?? 1,\n sampleRate: this.recordingConfig?.sampleRate ?? 44100,\n durationMs: this.currentDurationMs,\n size: primaryEnabled ? this.currentSize : 0,\n mimeType,\n compression,\n }\n\n // Reset after creating the result\n this.streamUuid = null\n\n // Reset recording state variables to prepare for next recording\n this.currentDurationMs = 0\n this.currentSize = 0\n this.lastEmittedSize = 0\n this.totalCompressedSize = 0\n this.lastEmittedCompressionSize = 0\n this.audioChunks = []\n\n return result\n } catch (error) {\n this.logger?.error('Error stopping recording:', error)\n throw error\n }\n }\n\n // Pause recording\n async pauseRecording() {\n if (!this.isRecording) {\n throw new Error('Recording is not active')\n }\n\n if (this.isPaused) {\n this.logger?.debug('Recording already paused, skipping')\n return\n }\n\n try {\n if (this.customRecorder) {\n this.customRecorder.pause()\n }\n this.isPaused = true\n this.pausedTime = Date.now()\n } catch (error) {\n this.logger?.error('Error in pauseRecording', error)\n // Even if the pause operation failed, make sure our state is consistent\n this.isPaused = true\n this.pausedTime = Date.now()\n }\n }\n\n // Resume recording\n async resumeRecording() {\n if (!this.isPaused) {\n throw new Error('Recording is not paused')\n }\n\n this.logger?.debug('Resuming recording', {\n deviceDisconnectionBehavior:\n this.recordingConfig?.deviceDisconnectionBehavior,\n isDeviceDisconnected: this.customRecorder?.isDeviceDisconnected,\n })\n\n try {\n // If we have no recorder, or if the device is disconnected, always attempt fallback\n if (\n !this.customRecorder ||\n this.customRecorder.isDeviceDisconnected\n ) {\n this.logger?.debug(\n 'No recorder exists or device disconnected - attempting fallback on resume'\n )\n await this.handleDeviceFallback()\n // handleDeviceFallback will manage resuming if successful, or emit error if failed.\n return\n }\n\n // Normal resume path - device is still connected\n this.customRecorder.resume()\n this.isPaused = false\n\n // Adjust the recording start time to account for the pause duration\n const pauseDuration = Date.now() - this.pausedTime\n this.recordingStartTime += pauseDuration\n this.pausedTime = 0\n\n this.emit('onRecordingInterrupted', {\n reason: 'userResumed',\n isPaused: false,\n timestamp: Date.now(),\n })\n } catch (error) {\n this.logger?.error('Resume failed:', error)\n // Fallback to emitting a general failure if resume fails unexpectedly\n this.emit('onRecordingInterrupted', {\n reason: 'resumeFailed', // Use a more specific reason\n isPaused: true, // Remain paused if resume fails\n timestamp: Date.now(),\n message:\n 'Failed to resume recording. Please stop and start again.',\n })\n }\n }\n\n // Get current status\n status() {\n const durationMs = this.getRecordingDuration()\n\n const status: AudioStreamStatus = {\n isRecording: this.isRecording,\n isPaused: this.isPaused,\n durationMs,\n size: this.currentSize,\n interval: this.currentInterval,\n intervalAnalysis: this.currentIntervalAnalysis,\n mimeType: `audio/${this.extension}`,\n compression: this.recordingConfig?.output?.compressed?.enabled\n ? {\n size: this.totalCompressedSize,\n mimeType: 'audio/webm',\n format:\n this.recordingConfig.output.compressed.format ??\n 'opus',\n bitrate:\n this.recordingConfig.output.compressed.bitrate ??\n 128000,\n compressedFileUri: `${this.streamUuid}.webm`,\n }\n : undefined,\n }\n return status\n }\n\n /**\n * Handles device fallback when the current device is disconnected\n */\n private async handleDeviceFallback(): Promise<boolean> {\n this.logger?.debug('Starting device fallback procedure')\n\n if (!this.isRecording) {\n return false\n }\n\n try {\n // Save important state before switching\n const currentPosition = this.latestPosition\n const existingAudioChunks = [...this.audioChunks]\n\n // Save compressed chunks if available\n let compressedChunks: Blob[] = []\n if (this.customRecorder) {\n try {\n compressedChunks = this.customRecorder.getCompressedChunks()\n } catch (err) {\n this.logger?.warn('Failed to get compressed chunks:', err)\n }\n }\n\n // Save the current counter value for continuity\n let currentDataPointCounter = 0\n if (this.customRecorder) {\n currentDataPointCounter =\n this.customRecorder.getDataPointCounter()\n }\n\n // Clean up existing recorder\n if (this.customRecorder) {\n try {\n this.customRecorder.cleanup()\n } catch (cleanupError) {\n this.logger?.warn('Error during cleanup:', cleanupError)\n }\n }\n\n // Keep recording state true but mark as paused\n this.isPaused = true\n this.pausedTime = Date.now()\n\n // Store current size and other stats\n const previousTotalSize = this.currentSize\n const previousLastEmittedSize = this.lastEmittedSize\n const previousCompressedSize = this.totalCompressedSize\n\n // Try to get a fallback device\n const fallbackDeviceInfo = await this.getFallbackDevice()\n if (!fallbackDeviceInfo) {\n this.emit('onRecordingInterrupted', {\n reason: 'deviceSwitchFailed',\n isPaused: true,\n timestamp: Date.now(),\n message:\n 'Failed to switch to fallback device. Recording paused.',\n })\n return false\n }\n\n // Start recording with the new device\n try {\n const stream = await this.requestPermissionsAndGetUserMedia(\n fallbackDeviceInfo.deviceId\n )\n const audioContext = new (window.AudioContext ||\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore - Allow webkitAudioContext for Safari\n window.webkitAudioContext)()\n\n const source = audioContext.createMediaStreamSource(stream)\n\n // Create a new recorder with the fallback device\n this.customRecorder = new WebRecorder({\n logger: this.logger,\n audioContext,\n source,\n recordingConfig: this.recordingConfig || {},\n emitAudioEventCallback:\n this.customRecorderEventCallback.bind(this),\n emitAudioAnalysisCallback:\n this.customRecorderAnalysisCallback.bind(this),\n onInterruption: this.handleRecordingInterruption.bind(this),\n })\n\n await this.customRecorder.init()\n\n // Set the initial position to continue from the previous device\n this.customRecorder.setPosition(currentPosition)\n\n // Reset the data point counter to continue from where the previous device left off\n if (currentDataPointCounter > 0) {\n this.customRecorder.resetDataPointCounter(\n currentDataPointCounter\n )\n }\n\n // Prepare the recorder to handle the device switch properly\n this.customRecorder.prepareForDeviceSwitch()\n\n // Restore the existing audio chunks\n if (existingAudioChunks.length > 0) {\n this.audioChunks = existingAudioChunks\n }\n\n // Restore compressed chunks if available\n if (compressedChunks.length > 0) {\n this.customRecorder.setCompressedChunks(compressedChunks)\n }\n\n // Start the new recorder while preserving counters\n this.customRecorder.start(true)\n\n // Update recording state\n this.isPaused = false\n this.recordingStartTime = Date.now()\n\n // Restore size counters to maintain continuity\n this.currentSize = previousTotalSize\n this.lastEmittedSize = previousLastEmittedSize\n this.totalCompressedSize = previousCompressedSize\n\n // Notify that we switched to a fallback device\n if (this.eventCallback) {\n this.eventCallback({\n type: 'deviceFallback',\n device: fallbackDeviceInfo.deviceId,\n timestamp: new Date(),\n })\n }\n return true\n } catch (error) {\n this.logger?.error(\n 'Failed to start recording with fallback device',\n error\n )\n this.isPaused = true\n this.emit('onRecordingInterrupted', {\n reason: 'deviceSwitchFailed',\n isPaused: true,\n timestamp: Date.now(),\n message:\n 'Failed to switch to fallback device. Recording paused.',\n })\n return false\n }\n } catch (error) {\n this.logger?.error('Failed to use fallback device', error)\n this.isPaused = true\n this.emit('onRecordingInterrupted', {\n reason: 'deviceSwitchFailed',\n isPaused: true,\n timestamp: Date.now(),\n message:\n 'Failed to switch to fallback device. Recording paused.',\n })\n return false\n }\n }\n\n /**\n * Attempts to get a fallback audio device\n */\n private async getFallbackDevice(): Promise<MediaDeviceInfo | null> {\n try {\n // Get list of available audio input devices\n const devices = await navigator.mediaDevices.enumerateDevices()\n const audioInputDevices = devices.filter(\n (device) => device.kind === 'audioinput'\n )\n\n if (audioInputDevices.length === 0) {\n return null\n }\n\n // Try to find a device that's not the current one\n if (this.customRecorder) {\n try {\n // Use mediaDevices.enumerateDevices to find the current active device\n const tracks = navigator.mediaDevices\n .getUserMedia({ audio: true })\n .then((stream) => {\n const track = stream.getAudioTracks()[0]\n return track ? track.label : ''\n })\n .catch(() => '')\n\n const currentTrackLabel = await tracks\n\n if (currentTrackLabel) {\n // Find a device with a different label\n const differentDevice = audioInputDevices.find(\n (device) =>\n device.label &&\n device.label !== currentTrackLabel\n )\n\n if (differentDevice) {\n return differentDevice\n }\n }\n } catch (err) {\n this.logger?.warn(\n 'Error determining current device, using default',\n err\n )\n }\n }\n\n // Return the first available device (default device)\n return audioInputDevices[0]\n } catch (error) {\n this.logger?.error('Error finding fallback device:', error)\n return null\n }\n }\n\n /**\n * Gets user media with specific device ID\n */\n private async requestPermissionsAndGetUserMedia(\n deviceId: string\n ): Promise<MediaStream> {\n try {\n // Request the specific device\n return await navigator.mediaDevices.getUserMedia({\n audio: {\n deviceId: { exact: deviceId },\n },\n })\n } catch (error) {\n this.logger?.error(\n `Failed to get media for device ${deviceId}`,\n error\n )\n // Try with default constraints as fallback\n return await navigator.mediaDevices.getUserMedia({ audio: true })\n }\n }\n\n init(options?: ExpoAudioStreamOptions): Promise<void> {\n try {\n this.logger = options?.logger\n this.eventCallback = options?.eventCallback\n return Promise.resolve()\n } catch (error) {\n this.logger?.error('Error initializing ExpoAudioStream', error)\n return Promise.reject(error)\n }\n }\n}\n"]}
@@ -94,7 +94,7 @@ export class WebRecorder {
94
94
  this.initFeatureExtractorWorker();
95
95
  }
96
96
  // Initialize compressed recording if enabled
97
- if (recordingConfig.compression?.enabled) {
97
+ if (recordingConfig.output?.compressed?.enabled) {
98
98
  this.initializeCompressedRecorder();
99
99
  }
100
100
  this.mediaStream = source.mediaStream;
@@ -138,6 +138,8 @@ export class WebRecorder {
138
138
  const incomingPosition = typeof event.data.position === 'number'
139
139
  ? event.data.position
140
140
  : this.position;
141
+ // Simple position tracking for logging (no duplicate filtering)
142
+ this.logger?.debug(`Audio chunk: position=${incomingPosition.toFixed(3)}s, size=${pcmBufferFloat.length}`);
141
143
  // Calculate bytes per sample based on bit depth
142
144
  const bytesPerSample = this.bitDepth / 8;
143
145
  // Emit chunks without storing them
@@ -148,6 +150,14 @@ export class WebRecorder {
148
150
  const startPosition = Math.floor(i * bytesPerSample);
149
151
  const endPosition = Math.floor((i + chunk.length) * bytesPerSample);
150
152
  const samples = chunk.length; // Number of samples in this chunk
153
+ // Only store PCM data if primary output is enabled
154
+ const shouldStoreUncompressed = this.config.output?.primary?.enabled ?? true;
155
+ // Store PCM chunks when needed - this is for the final WAV file
156
+ if (shouldStoreUncompressed) {
157
+ // Store the original Float32Array data for later WAV creation
158
+ this.appendPcmData(chunk);
159
+ this.totalSampleCount += chunk.length;
160
+ }
151
161
  // Process features if enabled
152
162
  if (this.config.enableProcessing &&
153
163
  this.featureExtractorWorker) {
@@ -167,34 +177,30 @@ export class WebRecorder {
167
177
  samples,
168
178
  });
169
179
  }
170
- // Only store PCM data if web.storeUncompressedAudio is not explicitly false
171
- const shouldStoreUncompressed = this.config.web?.storeUncompressedAudio !== false;
172
- // Store PCM chunks when needed
173
- if (shouldStoreUncompressed) {
174
- // Store the original Float32Array data for later WAV creation
175
- this.appendPcmData(chunk);
176
- this.totalSampleCount += chunk.length;
177
- }
178
- // Emit chunk immediately
180
+ // Prepare compression data if available
181
+ const compression = this.pendingCompressedChunk
182
+ ? {
183
+ data: this.pendingCompressedChunk,
184
+ size: this.pendingCompressedChunk.size,
185
+ totalSize: this.compressedSize,
186
+ mimeType: 'audio/webm',
187
+ format: this.config.output?.compressed?.format ??
188
+ 'opus',
189
+ bitrate: this.config.output?.compressed?.bitrate ??
190
+ 128000,
191
+ }
192
+ : undefined;
193
+ // Emit chunk immediately - whether compressed or not
179
194
  this.emitAudioEventCallback({
180
195
  data: chunk,
181
196
  position: chunkPosition,
182
- compression: this.pendingCompressedChunk
183
- ? {
184
- data: this.pendingCompressedChunk,
185
- size: this.pendingCompressedChunk.size,
186
- totalSize: this.compressedSize,
187
- mimeType: 'audio/webm',
188
- format: 'opus',
189
- bitrate: this.config.compression?.bitrate ??
190
- 128000,
191
- }
192
- : undefined,
197
+ compression,
193
198
  });
199
+ // Reset pending compressed chunk after we've used it
200
+ this.pendingCompressedChunk = null;
194
201
  }
195
202
  // Update our position based on the worklet's position if provided
196
203
  this.position = incomingPosition + duration;
197
- this.pendingCompressedChunk = null;
198
204
  };
199
205
  // Ensure we use all relevant settings from config
200
206
  const recordSampleRate = this.audioContext.sampleRate;
@@ -209,12 +215,12 @@ export class WebRecorder {
209
215
  channels,
210
216
  interval,
211
217
  position: this.position,
212
- deviceId: this.config.deviceId || 'default',
213
- compression: this.config.compression
218
+ deviceId: this.config.deviceId ?? 'default',
219
+ compression: this.config.output?.compressed
214
220
  ? {
215
- enabled: this.config.compression.enabled,
216
- format: this.config.compression.format,
217
- bitrate: this.config.compression.bitrate,
221
+ enabled: this.config.output.compressed.enabled,
222
+ format: this.config.output.compressed.format,
223
+ bitrate: this.config.output.compressed.bitrate,
218
224
  }
219
225
  : 'disabled',
220
226
  });
@@ -295,78 +301,85 @@ export class WebRecorder {
295
301
  * @param event - The event containing audio analysis results
296
302
  */
297
303
  handleFeatureExtractorMessage(event) {
298
- if (event.data.command === 'features') {
299
- const segmentResult = event.data.result;
300
- // Track existing IDs to prevent duplicates
301
- const existingIds = new Set(this.audioAnalysisData.dataPoints.map((dp) => dp.id));
302
- // Filter out datapoints with duplicate IDs
303
- const uniqueNewDataPoints = segmentResult.dataPoints.filter((dp) => {
304
- return !existingIds.has(dp.id);
305
- });
306
- // Log filtered duplicates if any
307
- if (uniqueNewDataPoints.length < segmentResult.dataPoints.length &&
308
- this.logger?.warn) {
309
- this.logger.warn(`Filtered ${segmentResult.dataPoints.length - uniqueNewDataPoints.length} duplicate datapoints`);
310
- }
311
- // Update counter based on the highest ID seen
312
- if (uniqueNewDataPoints.length > 0) {
313
- const lastDataPoint = uniqueNewDataPoints[uniqueNewDataPoints.length - 1];
314
- if (lastDataPoint && typeof lastDataPoint.id === 'number') {
315
- const nextIdValue = lastDataPoint.id + 1;
316
- if (nextIdValue > this.dataPointIdCounter) {
317
- this.dataPointIdCounter = nextIdValue;
318
- this.logger?.debug(`Counter updated to ${this.dataPointIdCounter}`);
319
- }
320
- }
321
- }
322
- // Add unique data points to our analysis data
323
- this.audioAnalysisData.dataPoints.push(...uniqueNewDataPoints);
324
- this.audioAnalysisData.durationMs += segmentResult.durationMs;
325
- this.audioAnalysisData.sampleRate = segmentResult.sampleRate;
326
- // Merge amplitude ranges
327
- if (segmentResult.amplitudeRange) {
328
- if (!this.audioAnalysisData.amplitudeRange) {
329
- this.audioAnalysisData.amplitudeRange = {
330
- ...segmentResult.amplitudeRange,
331
- };
332
- }
333
- else {
334
- this.audioAnalysisData.amplitudeRange = {
335
- min: Math.min(this.audioAnalysisData.amplitudeRange.min, segmentResult.amplitudeRange.min),
336
- max: Math.max(this.audioAnalysisData.amplitudeRange.max, segmentResult.amplitudeRange.max),
337
- };
338
- }
339
- }
340
- // Merge RMS ranges
341
- if (segmentResult.rmsRange) {
342
- if (!this.audioAnalysisData.rmsRange) {
343
- this.audioAnalysisData.rmsRange = {
344
- ...segmentResult.rmsRange,
345
- };
346
- }
347
- else {
348
- this.audioAnalysisData.rmsRange = {
349
- min: Math.min(this.audioAnalysisData.rmsRange.min, segmentResult.rmsRange.min),
350
- max: Math.max(this.audioAnalysisData.rmsRange.max, segmentResult.rmsRange.max),
351
- };
352
- }
304
+ if (event.data.command !== 'features')
305
+ return;
306
+ const segmentResult = event.data.result;
307
+ const uniqueNewDataPoints = this.filterUniqueDataPoints(segmentResult.dataPoints);
308
+ // Update counter based on the highest ID seen
309
+ this.updateDataPointCounter(uniqueNewDataPoints);
310
+ // Update analysis data with the new results
311
+ this.updateAudioAnalysisData(segmentResult, uniqueNewDataPoints);
312
+ // Send filtered result to avoid duplicate IDs
313
+ const filteredSegmentResult = {
314
+ ...segmentResult,
315
+ dataPoints: uniqueNewDataPoints,
316
+ };
317
+ this.emitAudioAnalysisCallback(filteredSegmentResult);
318
+ }
319
+ /**
320
+ * Filters out data points with duplicate IDs
321
+ */
322
+ filterUniqueDataPoints(dataPoints) {
323
+ // Track existing IDs to prevent duplicates
324
+ const existingIds = new Set(this.audioAnalysisData.dataPoints.map((dp) => dp.id));
325
+ // Filter out datapoints with duplicate IDs
326
+ const uniquePoints = dataPoints.filter((dp) => !existingIds.has(dp.id));
327
+ // Log filtered duplicates if any
328
+ if (uniquePoints.length < dataPoints.length && this.logger?.warn) {
329
+ this.logger.warn(`Filtered ${dataPoints.length - uniquePoints.length} duplicate datapoints`);
330
+ }
331
+ return uniquePoints;
332
+ }
333
+ /**
334
+ * Updates the counter based on the highest ID in datapoints
335
+ */
336
+ updateDataPointCounter(dataPoints) {
337
+ if (dataPoints.length === 0)
338
+ return;
339
+ const lastDataPoint = dataPoints[dataPoints.length - 1];
340
+ if (lastDataPoint && typeof lastDataPoint.id === 'number') {
341
+ const nextIdValue = lastDataPoint.id + 1;
342
+ if (nextIdValue > this.dataPointIdCounter) {
343
+ this.dataPointIdCounter = nextIdValue;
344
+ this.logger?.debug(`Counter updated to ${this.dataPointIdCounter}`);
353
345
  }
354
- // Send filtered result to avoid duplicate IDs
355
- const filteredSegmentResult = {
356
- ...segmentResult,
357
- dataPoints: uniqueNewDataPoints,
358
- };
359
- this.emitAudioAnalysisCallback(filteredSegmentResult);
360
346
  }
361
347
  }
348
+ /**
349
+ * Updates audio analysis data with segment results
350
+ */
351
+ updateAudioAnalysisData(segmentResult, uniqueDataPoints) {
352
+ // Add unique data points to our analysis data
353
+ this.audioAnalysisData.dataPoints.push(...uniqueDataPoints);
354
+ this.audioAnalysisData.durationMs += segmentResult.durationMs;
355
+ this.audioAnalysisData.sampleRate = segmentResult.sampleRate;
356
+ // Update amplitude range if present
357
+ if (segmentResult.amplitudeRange) {
358
+ this.audioAnalysisData.amplitudeRange = this.mergeRange(this.audioAnalysisData.amplitudeRange, segmentResult.amplitudeRange);
359
+ }
360
+ // Update RMS range if present
361
+ if (segmentResult.rmsRange) {
362
+ this.audioAnalysisData.rmsRange = this.mergeRange(this.audioAnalysisData.rmsRange, segmentResult.rmsRange);
363
+ }
364
+ }
365
+ /**
366
+ * Merges value ranges
367
+ */
368
+ mergeRange(existing, newRange) {
369
+ if (!existing)
370
+ return { ...newRange };
371
+ return {
372
+ min: Math.min(existing.min, newRange.min),
373
+ max: Math.max(existing.max, newRange.max),
374
+ };
375
+ }
362
376
  /**
363
377
  * Reset the data point counter to a specific value or zero
364
378
  * @param startCounterFrom Optional value to start the counter from (for continuing from previous recordings)
365
379
  */
366
380
  resetDataPointCounter(startCounterFrom) {
367
381
  // Set the counter with the passed value or 0
368
- this.dataPointIdCounter =
369
- startCounterFrom !== undefined ? startCounterFrom : 0;
382
+ this.dataPointIdCounter = startCounterFrom ?? 0;
370
383
  this.logger?.debug(`Reset data point counter to ${this.dataPointIdCounter}`);
371
384
  // Update worker counter if available
372
385
  if (this.featureExtractorWorker) {
@@ -428,7 +441,7 @@ export class WebRecorder {
428
441
  this.logger?.warn('No PCM data available to create WAV file');
429
442
  return null;
430
443
  }
431
- const sampleRate = this.config.sampleRate || this.audioContext.sampleRate;
444
+ const sampleRate = this.config.sampleRate ?? this.audioContext.sampleRate;
432
445
  const channels = this.numberOfChannels || 1;
433
446
  // Convert float32 PCM data to 16-bit PCM for WAV
434
447
  const bytesPerSample = 2; // 16-bit = 2 bytes
@@ -476,8 +489,7 @@ export class WebRecorder {
476
489
  let uncompressedBlob;
477
490
  // Only create WAV if we have PCM data
478
491
  if (this.pcmData && this.pcmData.length > 0) {
479
- uncompressedBlob =
480
- (await this.createWavFromPcmData()) || undefined;
492
+ uncompressedBlob = this.createWavFromPcmData() || undefined;
481
493
  }
482
494
  // Return the compressed and/or uncompressed blobs if available
483
495
  return {
@@ -497,6 +509,7 @@ export class WebRecorder {
497
509
  this.pendingCompressedChunk = null;
498
510
  this.pcmData = null;
499
511
  this.totalSampleCount = 0;
512
+ this.dataPointIdCounter = 0; // Reset counter
500
513
  }
501
514
  }
502
515
  /**
@@ -511,12 +524,10 @@ export class WebRecorder {
511
524
  }
512
525
  // Check if AudioContext is already closed before attempting to close it
513
526
  if (this.audioContext && this.audioContext.state !== 'closed') {
514
- try {
515
- this.audioContext.close();
516
- }
517
- catch (e) {
518
- // Ignore closure errors - this happens if already closed
519
- }
527
+ this.audioContext.close().catch((e) => {
528
+ // Log closure errors but continue cleanup
529
+ this.logger?.warn('Error closing AudioContext:', e);
530
+ });
520
531
  }
521
532
  // Safely disconnect audioWorkletNode if it exists
522
533
  if (this.audioWorkletNode) {
@@ -524,7 +535,8 @@ export class WebRecorder {
524
535
  this.audioWorkletNode.disconnect();
525
536
  }
526
537
  catch (e) {
527
- // Ignore disconnection errors - node might be already disconnected
538
+ // Log disconnection errors but continue cleanup
539
+ this.logger?.warn('Error disconnecting audioWorkletNode:', e);
528
540
  }
529
541
  }
530
542
  // Safely disconnect source if it exists
@@ -533,7 +545,8 @@ export class WebRecorder {
533
545
  this.source.disconnect();
534
546
  }
535
547
  catch (e) {
536
- // Ignore disconnection errors - source might be already disconnected
548
+ // Log disconnection errors but continue cleanup
549
+ this.logger?.warn('Error disconnecting source:', e);
537
550
  }
538
551
  }
539
552
  // Always stop media stream tracks to release hardware resources
@@ -613,6 +626,8 @@ export class WebRecorder {
613
626
  }
614
627
  catch (error) {
615
628
  this.logger?.error('Error in resume(): ', error);
629
+ // Rethrow the error to inform callers
630
+ throw new Error(`Failed to resume recording: ${error instanceof Error ? error.message : 'unknown error'}`);
616
631
  }
617
632
  }
618
633
  /**
@@ -628,18 +643,22 @@ export class WebRecorder {
628
643
  }
629
644
  this.compressedMediaRecorder = new MediaRecorder(this.source.mediaStream, {
630
645
  mimeType,
631
- audioBitsPerSecond: this.config.compression?.bitrate ?? 128000,
646
+ audioBitsPerSecond: this.config.output?.compressed?.bitrate ?? 128000,
632
647
  });
633
648
  this.compressedMediaRecorder.ondataavailable = (event) => {
634
649
  if (event.data.size > 0) {
650
+ // Store the compressed chunk for final blob creation
635
651
  this.compressedChunks.push(event.data);
636
652
  this.compressedSize += event.data.size;
653
+ // Store the pending compressed chunk for the next PCM chunk to use
637
654
  this.pendingCompressedChunk = event.data;
638
655
  }
639
656
  };
640
657
  }
641
658
  catch (error) {
642
659
  this.logger?.error('Failed to initialize compressed recorder:', error);
660
+ // Setting to null to indicate initialization failed
661
+ this.compressedMediaRecorder = null;
643
662
  }
644
663
  }
645
664
  /**
@@ -694,6 +713,7 @@ export class WebRecorder {
694
713
  }
695
714
  catch (e) {
696
715
  // Ignore disconnection errors as the track might already be gone
716
+ this.logger?.warn('Error disconnecting audioWorkletNode:', e);
697
717
  }
698
718
  }
699
719
  };