@siteed/expo-audio-studio 2.8.5 → 2.9.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.
@@ -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 web.storeUncompressedAudio is not explicitly false
154
+ const shouldStoreUncompressed = this.config.web?.storeUncompressedAudio !== false;
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,28 @@ 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: 'opus',
188
+ bitrate: this.config.compression?.bitrate ?? 128000,
189
+ }
190
+ : undefined;
191
+ // Emit chunk immediately - whether compressed or not
179
192
  this.emitAudioEventCallback({
180
193
  data: chunk,
181
194
  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,
195
+ compression,
193
196
  });
197
+ // Reset pending compressed chunk after we've used it
198
+ this.pendingCompressedChunk = null;
194
199
  }
195
200
  // Update our position based on the worklet's position if provided
196
201
  this.position = incomingPosition + duration;
197
- this.pendingCompressedChunk = null;
198
202
  };
199
203
  // Ensure we use all relevant settings from config
200
204
  const recordSampleRate = this.audioContext.sampleRate;
@@ -209,7 +213,7 @@ export class WebRecorder {
209
213
  channels,
210
214
  interval,
211
215
  position: this.position,
212
- deviceId: this.config.deviceId || 'default',
216
+ deviceId: this.config.deviceId ?? 'default',
213
217
  compression: this.config.compression
214
218
  ? {
215
219
  enabled: this.config.compression.enabled,
@@ -295,78 +299,85 @@ export class WebRecorder {
295
299
  * @param event - The event containing audio analysis results
296
300
  */
297
301
  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
- }
302
+ if (event.data.command !== 'features')
303
+ return;
304
+ const segmentResult = event.data.result;
305
+ const uniqueNewDataPoints = this.filterUniqueDataPoints(segmentResult.dataPoints);
306
+ // Update counter based on the highest ID seen
307
+ this.updateDataPointCounter(uniqueNewDataPoints);
308
+ // Update analysis data with the new results
309
+ this.updateAudioAnalysisData(segmentResult, uniqueNewDataPoints);
310
+ // Send filtered result to avoid duplicate IDs
311
+ const filteredSegmentResult = {
312
+ ...segmentResult,
313
+ dataPoints: uniqueNewDataPoints,
314
+ };
315
+ this.emitAudioAnalysisCallback(filteredSegmentResult);
316
+ }
317
+ /**
318
+ * Filters out data points with duplicate IDs
319
+ */
320
+ filterUniqueDataPoints(dataPoints) {
321
+ // Track existing IDs to prevent duplicates
322
+ const existingIds = new Set(this.audioAnalysisData.dataPoints.map((dp) => dp.id));
323
+ // Filter out datapoints with duplicate IDs
324
+ const uniquePoints = dataPoints.filter((dp) => !existingIds.has(dp.id));
325
+ // Log filtered duplicates if any
326
+ if (uniquePoints.length < dataPoints.length && this.logger?.warn) {
327
+ this.logger.warn(`Filtered ${dataPoints.length - uniquePoints.length} duplicate datapoints`);
328
+ }
329
+ return uniquePoints;
330
+ }
331
+ /**
332
+ * Updates the counter based on the highest ID in datapoints
333
+ */
334
+ updateDataPointCounter(dataPoints) {
335
+ if (dataPoints.length === 0)
336
+ return;
337
+ const lastDataPoint = dataPoints[dataPoints.length - 1];
338
+ if (lastDataPoint && typeof lastDataPoint.id === 'number') {
339
+ const nextIdValue = lastDataPoint.id + 1;
340
+ if (nextIdValue > this.dataPointIdCounter) {
341
+ this.dataPointIdCounter = nextIdValue;
342
+ this.logger?.debug(`Counter updated to ${this.dataPointIdCounter}`);
353
343
  }
354
- // Send filtered result to avoid duplicate IDs
355
- const filteredSegmentResult = {
356
- ...segmentResult,
357
- dataPoints: uniqueNewDataPoints,
358
- };
359
- this.emitAudioAnalysisCallback(filteredSegmentResult);
360
344
  }
361
345
  }
346
+ /**
347
+ * Updates audio analysis data with segment results
348
+ */
349
+ updateAudioAnalysisData(segmentResult, uniqueDataPoints) {
350
+ // Add unique data points to our analysis data
351
+ this.audioAnalysisData.dataPoints.push(...uniqueDataPoints);
352
+ this.audioAnalysisData.durationMs += segmentResult.durationMs;
353
+ this.audioAnalysisData.sampleRate = segmentResult.sampleRate;
354
+ // Update amplitude range if present
355
+ if (segmentResult.amplitudeRange) {
356
+ this.audioAnalysisData.amplitudeRange = this.mergeRange(this.audioAnalysisData.amplitudeRange, segmentResult.amplitudeRange);
357
+ }
358
+ // Update RMS range if present
359
+ if (segmentResult.rmsRange) {
360
+ this.audioAnalysisData.rmsRange = this.mergeRange(this.audioAnalysisData.rmsRange, segmentResult.rmsRange);
361
+ }
362
+ }
363
+ /**
364
+ * Merges value ranges
365
+ */
366
+ mergeRange(existing, newRange) {
367
+ if (!existing)
368
+ return { ...newRange };
369
+ return {
370
+ min: Math.min(existing.min, newRange.min),
371
+ max: Math.max(existing.max, newRange.max),
372
+ };
373
+ }
362
374
  /**
363
375
  * Reset the data point counter to a specific value or zero
364
376
  * @param startCounterFrom Optional value to start the counter from (for continuing from previous recordings)
365
377
  */
366
378
  resetDataPointCounter(startCounterFrom) {
367
379
  // Set the counter with the passed value or 0
368
- this.dataPointIdCounter =
369
- startCounterFrom !== undefined ? startCounterFrom : 0;
380
+ this.dataPointIdCounter = startCounterFrom ?? 0;
370
381
  this.logger?.debug(`Reset data point counter to ${this.dataPointIdCounter}`);
371
382
  // Update worker counter if available
372
383
  if (this.featureExtractorWorker) {
@@ -428,7 +439,7 @@ export class WebRecorder {
428
439
  this.logger?.warn('No PCM data available to create WAV file');
429
440
  return null;
430
441
  }
431
- const sampleRate = this.config.sampleRate || this.audioContext.sampleRate;
442
+ const sampleRate = this.config.sampleRate ?? this.audioContext.sampleRate;
432
443
  const channels = this.numberOfChannels || 1;
433
444
  // Convert float32 PCM data to 16-bit PCM for WAV
434
445
  const bytesPerSample = 2; // 16-bit = 2 bytes
@@ -476,8 +487,7 @@ export class WebRecorder {
476
487
  let uncompressedBlob;
477
488
  // Only create WAV if we have PCM data
478
489
  if (this.pcmData && this.pcmData.length > 0) {
479
- uncompressedBlob =
480
- (await this.createWavFromPcmData()) || undefined;
490
+ uncompressedBlob = this.createWavFromPcmData() || undefined;
481
491
  }
482
492
  // Return the compressed and/or uncompressed blobs if available
483
493
  return {
@@ -497,6 +507,7 @@ export class WebRecorder {
497
507
  this.pendingCompressedChunk = null;
498
508
  this.pcmData = null;
499
509
  this.totalSampleCount = 0;
510
+ this.dataPointIdCounter = 0; // Reset counter
500
511
  }
501
512
  }
502
513
  /**
@@ -511,12 +522,10 @@ export class WebRecorder {
511
522
  }
512
523
  // Check if AudioContext is already closed before attempting to close it
513
524
  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
- }
525
+ this.audioContext.close().catch((e) => {
526
+ // Log closure errors but continue cleanup
527
+ this.logger?.warn('Error closing AudioContext:', e);
528
+ });
520
529
  }
521
530
  // Safely disconnect audioWorkletNode if it exists
522
531
  if (this.audioWorkletNode) {
@@ -524,7 +533,8 @@ export class WebRecorder {
524
533
  this.audioWorkletNode.disconnect();
525
534
  }
526
535
  catch (e) {
527
- // Ignore disconnection errors - node might be already disconnected
536
+ // Log disconnection errors but continue cleanup
537
+ this.logger?.warn('Error disconnecting audioWorkletNode:', e);
528
538
  }
529
539
  }
530
540
  // Safely disconnect source if it exists
@@ -533,7 +543,8 @@ export class WebRecorder {
533
543
  this.source.disconnect();
534
544
  }
535
545
  catch (e) {
536
- // Ignore disconnection errors - source might be already disconnected
546
+ // Log disconnection errors but continue cleanup
547
+ this.logger?.warn('Error disconnecting source:', e);
537
548
  }
538
549
  }
539
550
  // Always stop media stream tracks to release hardware resources
@@ -613,6 +624,8 @@ export class WebRecorder {
613
624
  }
614
625
  catch (error) {
615
626
  this.logger?.error('Error in resume(): ', error);
627
+ // Rethrow the error to inform callers
628
+ throw new Error(`Failed to resume recording: ${error instanceof Error ? error.message : 'unknown error'}`);
616
629
  }
617
630
  }
618
631
  /**
@@ -632,14 +645,18 @@ export class WebRecorder {
632
645
  });
633
646
  this.compressedMediaRecorder.ondataavailable = (event) => {
634
647
  if (event.data.size > 0) {
648
+ // Store the compressed chunk for final blob creation
635
649
  this.compressedChunks.push(event.data);
636
650
  this.compressedSize += event.data.size;
651
+ // Store the pending compressed chunk for the next PCM chunk to use
637
652
  this.pendingCompressedChunk = event.data;
638
653
  }
639
654
  };
640
655
  }
641
656
  catch (error) {
642
657
  this.logger?.error('Failed to initialize compressed recorder:', error);
658
+ // Setting to null to indicate initialization failed
659
+ this.compressedMediaRecorder = null;
643
660
  }
644
661
  }
645
662
  /**
@@ -694,6 +711,7 @@ export class WebRecorder {
694
711
  }
695
712
  catch (e) {
696
713
  // Ignore disconnection errors as the track might already be gone
714
+ this.logger?.warn('Error disconnecting audioWorkletNode:', e);
697
715
  }
698
716
  }
699
717
  };
@@ -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 { 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"]}
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 readonly 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 readonly mediaStream: MediaStream | null = null\n private readonly 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 // Simple position tracking for logging (no duplicate filtering)\n this.logger?.debug(\n `Audio chunk: position=${incomingPosition.toFixed(3)}s, size=${pcmBufferFloat.length}`\n )\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 // 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 - this is for the final WAV file\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 // 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 // Prepare compression data if available\n const 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 ?? 128000,\n }\n : undefined\n\n // Emit chunk immediately - whether compressed or not\n this.emitAudioEventCallback({\n data: chunk,\n position: chunkPosition,\n compression,\n })\n\n // Reset pending compressed chunk after we've used it\n this.pendingCompressedChunk = null\n }\n\n // Update our position based on the worklet's position if provided\n this.position = incomingPosition + duration\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') return\n\n const segmentResult = event.data.result\n const uniqueNewDataPoints = this.filterUniqueDataPoints(\n segmentResult.dataPoints\n )\n\n // Update counter based on the highest ID seen\n this.updateDataPointCounter(uniqueNewDataPoints)\n\n // Update analysis data with the new results\n this.updateAudioAnalysisData(segmentResult, uniqueNewDataPoints)\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 * Filters out data points with duplicate IDs\n */\n private filterUniqueDataPoints(dataPoints: any[]): any[] {\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 uniquePoints = dataPoints.filter((dp) => !existingIds.has(dp.id))\n\n // Log filtered duplicates if any\n if (uniquePoints.length < dataPoints.length && this.logger?.warn) {\n this.logger.warn(\n `Filtered ${dataPoints.length - uniquePoints.length} duplicate datapoints`\n )\n }\n\n return uniquePoints\n }\n\n /**\n * Updates the counter based on the highest ID in datapoints\n */\n private updateDataPointCounter(dataPoints: any[]): void {\n if (dataPoints.length === 0) return\n\n const lastDataPoint = dataPoints[dataPoints.length - 1]\n if (lastDataPoint && typeof lastDataPoint.id === 'number') {\n const nextIdValue = lastDataPoint.id + 1\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 /**\n * Updates audio analysis data with segment results\n */\n private updateAudioAnalysisData(\n segmentResult: AudioAnalysis,\n uniqueDataPoints: any[]\n ): void {\n // Add unique data points to our analysis data\n this.audioAnalysisData.dataPoints.push(...uniqueDataPoints)\n this.audioAnalysisData.durationMs += segmentResult.durationMs\n this.audioAnalysisData.sampleRate = segmentResult.sampleRate\n\n // Update amplitude range if present\n if (segmentResult.amplitudeRange) {\n this.audioAnalysisData.amplitudeRange = this.mergeRange(\n this.audioAnalysisData.amplitudeRange,\n segmentResult.amplitudeRange\n )\n }\n\n // Update RMS range if present\n if (segmentResult.rmsRange) {\n this.audioAnalysisData.rmsRange = this.mergeRange(\n this.audioAnalysisData.rmsRange,\n segmentResult.rmsRange\n )\n }\n }\n\n /**\n * Merges value ranges\n */\n private mergeRange(\n existing: { min: number; max: number } | undefined,\n newRange: { min: number; max: number }\n ): { min: number; max: number } {\n if (!existing) return { ...newRange }\n\n return {\n min: Math.min(existing.min, newRange.min),\n max: Math.max(existing.max, newRange.max),\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 = 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 = 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 this.dataPointIdCounter = 0 // Reset counter\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 this.audioContext.close().catch((e) => {\n // Log closure errors but continue cleanup\n this.logger?.warn('Error closing AudioContext:', e)\n })\n }\n\n // Safely disconnect audioWorkletNode if it exists\n if (this.audioWorkletNode) {\n try {\n this.audioWorkletNode.disconnect()\n } catch (e) {\n // Log disconnection errors but continue cleanup\n this.logger?.warn('Error disconnecting audioWorkletNode:', e)\n }\n }\n\n // Safely disconnect source if it exists\n if (this.source) {\n try {\n this.source.disconnect()\n } catch (e) {\n // Log disconnection errors but continue cleanup\n this.logger?.warn('Error disconnecting source:', e)\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: unknown) {\n this.logger?.error('Error in resume(): ', error)\n // Rethrow the error to inform callers\n throw new Error(\n `Failed to resume recording: ${error instanceof Error ? error.message : 'unknown error'}`\n )\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 // Store the compressed chunk for final blob creation\n this.compressedChunks.push(event.data)\n this.compressedSize += event.data.size\n\n // Store the pending compressed chunk for the next PCM chunk to use\n this.pendingCompressedChunk = event.data\n }\n }\n } catch (error) {\n this.logger?.error(\n 'Failed to initialize compressed recorder:',\n error\n )\n // Setting to null to indicate initialization failed\n this.compressedMediaRecorder = null\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 this.logger?.warn(\n 'Error disconnecting audioWorkletNode:',\n e\n )\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"]}
@@ -44,12 +44,14 @@ export interface AudioFeatures {
44
44
  }
45
45
  /**
46
46
  * Options to specify which audio features to extract.
47
+ * Note: Advanced features (spectral features, chromagram, pitch, etc.) are experimental,
48
+ * especially during live recording, due to high processing requirements.
47
49
  */
48
50
  export interface AudioFeaturesOptions {
49
51
  energy?: boolean;
50
- mfcc?: boolean;
51
52
  rms?: boolean;
52
53
  zcr?: boolean;
54
+ mfcc?: boolean;
53
55
  spectralCentroid?: boolean;
54
56
  spectralFlatness?: boolean;
55
57
  spectralRolloff?: boolean;