@siteed/expo-audio-stream 1.1.7 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+ - For new features.
12
+
13
+ ### Changed
14
+ - For changes in existing functionality.
15
+
16
+ ### Deprecated
17
+ - For soon-to-be removed features.
18
+
19
+ ### Removed
20
+ - For now removed features.
21
+
22
+ ### Fixed
23
+ - For any bug fixes.
24
+
25
+ ### Security
26
+ - In case of vulnerabilities.
27
+
28
+ ## [1.0.0] - YYYY-MM-DD
29
+
30
+ ### Added
31
+ - Initial release of @siteed/expo-audio-stream.
32
+ - Feature: Real-time audio streaming across iOS, Android, and web.
33
+ - Feature: Configurable intervals for audio buffer receipt.
34
+ - Feature: Automated microphone permissions setup in managed Expo projects.
35
+ - Feature: Background audio recording on iOS.
36
+ - Feature: Audio features extraction during recording.
37
+ - Feature: Consistent WAV PCM recording format across all platforms.
38
+
39
+ [Unreleased]: https://github.com/deeeed/expo-audio-stream/compare/v1.0.0...HEAD
40
+ [1.0.0]: https://github.com/deeeed/expo-audio-stream/releases/tag/v1.0.0
@@ -20,9 +20,10 @@ export declare class WebRecorder {
20
20
  private numberOfChannels;
21
21
  private bitDepth;
22
22
  private exportBitDepth;
23
- private buffer;
24
- private bufferSize;
23
+ private audioBuffer;
24
+ private audioBufferSize;
25
25
  private audioAnalysisData;
26
+ private packetCount;
26
27
  constructor({ audioContext, source, recordingConfig, audioWorkletUrl, emitAudioEventCallback, emitAudioAnalysisCallback, }: {
27
28
  audioContext: AudioContext;
28
29
  source: MediaStreamAudioSourceNode;
@@ -1 +1 @@
1
- {"version":3,"file":"WebRecorder.web.d.ts","sourceRoot":"","sources":["../src/WebRecorder.web.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EACH,yBAAyB,EACzB,sBAAsB,EACzB,MAAM,uBAAuB,CAAA;AAe9B,UAAU,kBAAkB;IACxB,IAAI,EAAE;QACF,OAAO,EAAE,MAAM,CAAA;QACf,MAAM,EAAE,aAAa,CAAA;KACxB,CAAA;CACJ;AAWD,qBAAa,WAAW;IACpB,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,sBAAsB,CAAC,CAAQ;IACvC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,sBAAsB,CAAwB;IACtD,OAAO,CAAC,yBAAyB,CAA2B;IAC5D,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,iBAAiB,CAAe;gBAE5B,EACR,YAAY,EACZ,MAAM,EACN,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,yBAAyB,GAC5B,EAAE;QACC,YAAY,EAAE,YAAY,CAAA;QAC1B,MAAM,EAAE,0BAA0B,CAAA;QAClC,eAAe,EAAE,eAAe,CAAA;QAChC,eAAe,EAAE,MAAM,CAAA;QACvB,sBAAsB,EAAE,sBAAsB,CAAA;QAC9C,yBAAyB,EAAE,yBAAyB,CAAA;KACvD;IAgEK,IAAI;IA6FV,0BAA0B,CAAC,mBAAmB,CAAC,EAAE,MAAM;IA0BvD,kBAAkB;IAqBlB,iBAAiB,CAAC,KAAK,EAAE,UAAU;IAInC,6BAA6B,CAAC,KAAK,EAAE,kBAAkB;IAgCvD,KAAK;IAKL,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC;IAsE7B,KAAK;IAML,qBAAqB;IAMf,gBAAgB,CAAC,EACnB,YAAY,GACf,EAAE;QACC,YAAY,EAAE,WAAW,CAAA;QACzB,QAAQ,CAAC,EAAE,MAAM,CAAA;KACpB;IAsBD,OAAO,CAAC,uBAAuB;IAoB/B,MAAM;CAKT"}
1
+ {"version":3,"file":"WebRecorder.web.d.ts","sourceRoot":"","sources":["../src/WebRecorder.web.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EACH,yBAAyB,EACzB,sBAAsB,EACzB,MAAM,uBAAuB,CAAA;AAe9B,UAAU,kBAAkB;IACxB,IAAI,EAAE;QACF,OAAO,EAAE,MAAM,CAAA;QACf,MAAM,EAAE,aAAa,CAAA;KACxB,CAAA;CACJ;AAWD,qBAAa,WAAW;IACpB,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,sBAAsB,CAAC,CAAQ;IACvC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,sBAAsB,CAAwB;IACtD,OAAO,CAAC,yBAAyB,CAA2B;IAC5D,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,iBAAiB,CAAe;IACxC,OAAO,CAAC,WAAW,CAAY;gBAEnB,EACR,YAAY,EACZ,MAAM,EACN,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,yBAAyB,GAC5B,EAAE;QACC,YAAY,EAAE,YAAY,CAAA;QAC1B,MAAM,EAAE,0BAA0B,CAAA;QAClC,eAAe,EAAE,eAAe,CAAA;QAChC,eAAe,EAAE,MAAM,CAAA;QACvB,sBAAsB,EAAE,sBAAsB,CAAA;QAC9C,yBAAyB,EAAE,yBAAyB,CAAA;KACvD;IAoDK,IAAI;IAyHV,0BAA0B,CAAC,mBAAmB,CAAC,EAAE,MAAM;IA0BvD,kBAAkB;IAqBlB,iBAAiB,CAAC,KAAK,EAAE,UAAU;IAInC,6BAA6B,CAAC,KAAK,EAAE,kBAAkB;IAgCvD,KAAK;IAML,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC;IAsE7B,KAAK;IAML,qBAAqB;IAMf,gBAAgB,CAAC,EACnB,YAAY,GACf,EAAE;QACC,YAAY,EAAE,WAAW,CAAA;QACzB,QAAQ,CAAC,EAAE,MAAM,CAAA;KACpB;IAsBD,OAAO,CAAC,uBAAuB;IAoB/B,MAAM;CAKT"}
@@ -23,9 +23,10 @@ export class WebRecorder {
23
23
  numberOfChannels; // Number of audio channels
24
24
  bitDepth; // Bit depth of the audio
25
25
  exportBitDepth; // Bit depth of the audio
26
- buffer; // Single buffer to store the audio data
27
- bufferSize; // Keep track of the buffer size
26
+ audioBuffer; // Single buffer to store the audio data
27
+ audioBufferSize; // Keep track of the buffer size
28
28
  audioAnalysisData; // Keep updating the full audio analysis data with latest events
29
+ packetCount = 0;
29
30
  constructor({ audioContext, source, recordingConfig, audioWorkletUrl, emitAudioEventCallback, emitAudioAnalysisCallback, }) {
30
31
  this.audioContext = audioContext;
31
32
  this.source = source;
@@ -52,17 +53,9 @@ export class WebRecorder {
52
53
  }) ||
53
54
  audioContextFormat.bitDepth ||
54
55
  DEFAULT_WEB_BITDEPTH;
55
- // Initialize WAV header
56
- const wavHeader = writeWavHeader({
57
- sampleRate: this.audioContext.sampleRate,
58
- numChannels: this.numberOfChannels,
59
- bitDepth: this.exportBitDepth,
60
- });
61
- // Initialize the buffer with WAV header
62
- this.buffer = new Float32Array(wavHeader.byteLength / Float32Array.BYTES_PER_ELEMENT);
63
- this.bufferSize = this.buffer.byteLength;
64
- // Copy WAV header to Float32Array buffer
65
- new Uint8Array(this.buffer.buffer).set(new Uint8Array(wavHeader));
56
+ // Initialize the audio buffer separately
57
+ this.audioBuffer = new Float32Array(0);
58
+ this.audioBufferSize = 0;
66
59
  this.audioAnalysisData = {
67
60
  amplitudeRange: { min: 0, max: 0 },
68
61
  dataPoints: [],
@@ -97,22 +90,43 @@ export class WebRecorder {
97
90
  if (command !== 'newData') {
98
91
  return;
99
92
  }
100
- // Handle the audio blob (e.g., send it to the server or process it further)
101
- logger.debug('Received audio blob from processor', event);
102
93
  const pcmBufferFloat = event.data.recordedData;
103
94
  if (!pcmBufferFloat) {
95
+ logger.warn('Received empty audio buffer', event);
104
96
  return;
105
97
  }
98
+ // Handle the audio blob (e.g., send it to the server or process it further)
99
+ logger.debug(`Received audio blob from processor len:${pcmBufferFloat?.length}`, event);
106
100
  // Concatenate the incoming Float32Array to the existing buffer
107
- const newBuffer = new Float32Array(this.bufferSize + pcmBufferFloat.length);
108
- newBuffer.set(this.buffer, 0);
109
- newBuffer.set(pcmBufferFloat, this.bufferSize);
110
- this.buffer = newBuffer;
111
- this.bufferSize += pcmBufferFloat.length;
101
+ const newBuffer = new Float32Array(this.audioBufferSize + pcmBufferFloat.length);
102
+ newBuffer.set(this.audioBuffer, 0);
103
+ newBuffer.set(pcmBufferFloat, this.audioBufferSize);
104
+ this.audioBuffer = newBuffer;
105
+ this.audioBufferSize += pcmBufferFloat.length;
112
106
  const sampleRate = event.data.sampleRate ?? this.audioContext.sampleRate;
113
107
  const duration = pcmBufferFloat.length / sampleRate; // Calculate duration of the current buffer
108
+ let data;
109
+ if (this.packetCount === 0) {
110
+ // Initialize WAV header
111
+ const wavHeaderBuffer = writeWavHeader({
112
+ sampleRate: this.audioContext.sampleRate,
113
+ numChannels: this.numberOfChannels,
114
+ bitDepth: this.exportBitDepth,
115
+ });
116
+ // For the first packet, combine WAV header with audio data
117
+ const headerFloatArray = new Float32Array(wavHeaderBuffer);
118
+ data = new Float32Array(headerFloatArray.length + this.audioBuffer.length);
119
+ data.set(headerFloatArray, 0);
120
+ data.set(this.audioBuffer, headerFloatArray.length);
121
+ }
122
+ else {
123
+ // For subsequent packets, just send the new audio data
124
+ data = pcmBufferFloat;
125
+ }
126
+ // Track the number of packets
127
+ this.packetCount += 1;
114
128
  this.emitAudioEventCallback({
115
- data: pcmBufferFloat,
129
+ data,
116
130
  position: this.position,
117
131
  });
118
132
  this.position += duration; // Update position
@@ -212,6 +226,7 @@ export class WebRecorder {
212
226
  start() {
213
227
  this.source.connect(this.audioWorkletNode);
214
228
  this.audioWorkletNode.connect(this.audioContext.destination);
229
+ this.packetCount = 0;
215
230
  }
216
231
  stop() {
217
232
  return new Promise((resolve, reject) => {
@@ -241,10 +256,10 @@ export class WebRecorder {
241
256
  (this.exportBitDepth /
242
257
  this.numberOfChannels));
243
258
  logger.debug(`Received recorded data -- Duration: ${duration} vs ${rawPCMDataFull.byteLength / this.audioContext.sampleRate} seconds`);
244
- logger.debug(`recordedData.length=${rawPCMDataFull.byteLength} vs transmittedData.length=${this.bufferSize}`);
259
+ logger.debug(`recordedData.length=${rawPCMDataFull.byteLength} vs transmittedData.length=${this.audioBufferSize}`);
245
260
  // Remove the event listener after receiving the final data
246
261
  this.audioWorkletNode.port.removeEventListener('message', onMessage);
247
- resolve(this.buffer); // Resolve the promise with the collected buffers
262
+ resolve(this.audioBuffer); // Resolve the promise with the collected buffers
248
263
  }
249
264
  };
250
265
  this.audioWorkletNode.port.addEventListener('message', onMessage);
@@ -1 +1 @@
1
- {"version":3,"file":"WebRecorder.web.js","sourceRoot":"","sources":["../src/WebRecorder.web.ts"],"names":[],"mappings":"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","sourcesContent":["// src/WebRecorder.ts\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport { RecordingConfig } from './ExpoAudioStream.types'\nimport {\n EmitAudioAnalysisFunction,\n EmitAudioEventFunction,\n} from './ExpoAudioStream.web'\nimport { getLogger } from './logger'\nimport { encodingToBitDepth } from './utils/encodingToBitDepth'\nimport { WavHeaderOptions, 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 }\n}\n\ninterface AudioFeaturesEvent {\n data: {\n command: string\n result: AudioAnalysis\n }\n}\n\nconst DEFAULT_WEB_BITDEPTH = 32\nconst DEFAULT_WEB_POINTS_PER_SECOND = 10\nconst DEFAULT_WEB_INTERVAL = 500\nconst DEFAULT_WEB_NUMBER_OF_CHANNELS = 1\nconst DEFAULT_ALGORITHM = 'rms'\n\nconst TAG = 'WebRecorder'\nconst logger = getLogger(TAG)\n\nexport class WebRecorder {\n private audioContext: AudioContext\n private audioWorkletNode!: AudioWorkletNode\n private featureExtractorWorker?: Worker\n private source: MediaStreamAudioSourceNode\n private audioWorkletUrl: string\n private emitAudioEventCallback: EmitAudioEventFunction\n private emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n private config: RecordingConfig\n private position: number // Track the cumulative position\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 buffer: Float32Array // Single buffer to store the audio data\n private bufferSize: number // Keep track of the buffer size\n private audioAnalysisData: AudioAnalysis // Keep updating the full audio analysis data with latest events\n\n constructor({\n audioContext,\n source,\n recordingConfig,\n audioWorkletUrl,\n emitAudioEventCallback,\n emitAudioAnalysisCallback,\n }: {\n audioContext: AudioContext\n source: MediaStreamAudioSourceNode\n recordingConfig: RecordingConfig\n audioWorkletUrl: string\n emitAudioEventCallback: EmitAudioEventFunction\n emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n }) {\n this.audioContext = audioContext\n this.source = source\n this.audioWorkletUrl = audioWorkletUrl\n this.emitAudioEventCallback = emitAudioEventCallback\n this.emitAudioAnalysisCallback = emitAudioAnalysisCallback\n this.config = recordingConfig\n this.position = 0\n\n const audioContextFormat = this.checkAudioContextFormat({\n sampleRate: this.audioContext.sampleRate,\n })\n 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 // Initialize WAV header\n const wavHeader = writeWavHeader({\n sampleRate: this.audioContext.sampleRate,\n numChannels: this.numberOfChannels,\n bitDepth: this.exportBitDepth,\n })\n\n // Initialize the buffer with WAV header\n this.buffer = new Float32Array(\n wavHeader.byteLength / Float32Array.BYTES_PER_ELEMENT\n )\n this.bufferSize = this.buffer.byteLength\n\n // Copy WAV header to Float32Array buffer\n new Uint8Array(this.buffer.buffer).set(new Uint8Array(wavHeader))\n\n this.audioAnalysisData = {\n amplitudeRange: { min: 0, max: 0 },\n dataPoints: [],\n durationMs: 0,\n samples: 0,\n amplitudeAlgorithm: recordingConfig.algorithm || DEFAULT_ALGORITHM,\n bitDepth: this.bitDepth,\n numberOfChannels: this.numberOfChannels,\n sampleRate: this.config.sampleRate || this.audioContext.sampleRate,\n pointsPerSecond:\n this.config.pointsPerSecond || DEFAULT_WEB_POINTS_PER_SECOND,\n speakerChanges: [],\n }\n\n if (recordingConfig.enableProcessing) {\n this.initFeatureExtractorWorker()\n }\n }\n\n async init() {\n try {\n if (!this.audioWorkletUrl) {\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 } else {\n await this.audioContext.audioWorklet.addModule(\n this.audioWorkletUrl\n )\n }\n this.audioWorkletNode = new AudioWorkletNode(\n this.audioContext,\n 'recorder-processor'\n )\n\n this.audioWorkletNode.port.onmessage = async (\n event: AudioWorkletEvent\n ) => {\n const command = event.data.command\n if (command !== 'newData') {\n return\n }\n // Handle the audio blob (e.g., send it to the server or process it further)\n logger.debug('Received audio blob from processor', event)\n const pcmBufferFloat = event.data.recordedData\n\n if (!pcmBufferFloat) {\n return\n }\n\n // Concatenate the incoming Float32Array to the existing buffer\n const newBuffer = new Float32Array(\n this.bufferSize + pcmBufferFloat.length\n )\n newBuffer.set(this.buffer, 0)\n newBuffer.set(pcmBufferFloat, this.bufferSize)\n this.buffer = newBuffer\n this.bufferSize += pcmBufferFloat.length\n\n const sampleRate =\n event.data.sampleRate ?? this.audioContext.sampleRate\n const duration = pcmBufferFloat.length / sampleRate // Calculate duration of the current buffer\n\n this.emitAudioEventCallback({\n data: pcmBufferFloat,\n position: this.position,\n })\n this.position += duration // Update position\n\n this.featureExtractorWorker?.postMessage(\n {\n command: 'process',\n channelData: pcmBufferFloat,\n sampleRate,\n pointsPerSecond:\n this.config.pointsPerSecond ||\n DEFAULT_WEB_POINTS_PER_SECOND,\n algorithm: this.config.algorithm || 'rms',\n bitDepth: this.bitDepth,\n fullAudioDurationMs: this.position * 1000,\n numberOfChannels: this.numberOfChannels,\n features: this.config.features,\n },\n []\n )\n }\n\n logger.debug(\n `WebRecorder initialized -- recordSampleRate=${this.audioContext.sampleRate}`,\n this.config\n )\n this.audioWorkletNode.port.postMessage({\n command: 'init',\n recordSampleRate: this.audioContext.sampleRate, // Pass the original sample rate\n exportSampleRate:\n this.config.sampleRate ?? this.audioContext.sampleRate,\n bitDepth: this.bitDepth,\n exportBitDepth: this.exportBitDepth,\n channels: this.numberOfChannels,\n interval: this.config.interval ?? DEFAULT_WEB_INTERVAL,\n })\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 initFeatureExtractorWorker(featuresExtratorUrl?: string) {\n try {\n if (featuresExtratorUrl) {\n // Initialize the feature extractor worker\n //TODO: create audio feature extractor from a Blob instead of url since we cannot include the url directly in the library\n // We keep the url during dev and use the blob in production.\n this.featureExtractorWorker = new Worker(\n new URL(featuresExtratorUrl, window.location.href)\n )\n this.featureExtractorWorker.onmessage =\n this.handleFeatureExtractorMessage.bind(this)\n this.featureExtractorWorker.onerror =\n this.handleWorkerError.bind(this)\n } else {\n // Fallback to the inline worker if the URL is not provided\n this.initFallbackWorker()\n }\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize feature extractor worker`,\n error\n )\n this.initFallbackWorker()\n }\n }\n\n initFallbackWorker() {\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}] Default Inline worker failed`, error)\n }\n logger.log('Inline worker initialized successfully')\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize Inline Feature Extractor worker`,\n error\n )\n }\n }\n\n handleWorkerError(error: ErrorEvent) {\n console.error(`[${TAG}] Feature extractor worker error:`, error)\n }\n\n handleFeatureExtractorMessage(event: AudioFeaturesEvent) {\n if (event.data.command === 'features') {\n const segmentResult = event.data.result\n\n // Merge the segment result with the full audio analysis data\n this.audioAnalysisData.dataPoints.push(...segmentResult.dataPoints)\n this.audioAnalysisData.speakerChanges?.push(\n ...(segmentResult.speakerChanges ?? [])\n )\n this.audioAnalysisData.durationMs = segmentResult.durationMs\n if (segmentResult.amplitudeRange) {\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 // Handle the extracted features (e.g., emit an event or log them)\n logger.debug('features event segmentResult', segmentResult)\n logger.debug(\n 'features event audioAnalysisData',\n this.audioAnalysisData\n )\n this.emitAudioAnalysisCallback(segmentResult)\n }\n }\n\n start() {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n }\n\n stop(): Promise<Float32Array> {\n return new Promise((resolve, reject) => {\n try {\n if (this.audioWorkletNode) {\n // this.source.disconnect(this.audioWorkletNode);\n // this.audioWorkletNode.disconnect(this.audioContext.destination);\n this.audioWorkletNode.port.postMessage({ command: 'stop' })\n\n // Set a timeout to reject the promise if no message is received within 5 seconds\n const timeout = setTimeout(() => {\n this.audioWorkletNode.port.removeEventListener(\n 'message',\n onMessage\n )\n reject(\n new Error(\n \"Timeout error, audioWorkletNode didn't complete.\"\n )\n )\n }, 5000)\n\n // Listen for the recordedData message to confirm stopping\n const onMessage = async (event: AudioWorkletEvent) => {\n const command = event.data.command\n if (command === 'recordedData') {\n clearTimeout(timeout) // Clear the timeout\n\n const rawPCMDataFull =\n event.data.recordedData?.slice(0)\n\n if (!rawPCMDataFull) {\n reject(new Error('Failed to get recorded data'))\n return\n }\n\n // Compute duration of the recorded data\n const duration =\n rawPCMDataFull.byteLength /\n (this.audioContext.sampleRate *\n (this.exportBitDepth /\n this.numberOfChannels))\n logger.debug(\n `Received recorded data -- Duration: ${duration} vs ${rawPCMDataFull.byteLength / this.audioContext.sampleRate} seconds`\n )\n logger.debug(\n `recordedData.length=${rawPCMDataFull.byteLength} vs transmittedData.length=${this.bufferSize}`\n )\n\n // Remove the event listener after receiving the final data\n this.audioWorkletNode.port.removeEventListener(\n 'message',\n onMessage\n )\n resolve(this.buffer) // Resolve the promise with the collected buffers\n }\n }\n this.audioWorkletNode.port.addEventListener(\n 'message',\n onMessage\n )\n }\n\n // Stop all media stream tracks to stop the browser recording\n this.stopMediaStreamTracks()\n } catch (error) {\n reject(error)\n }\n })\n }\n\n pause() {\n this.source.disconnect(this.audioWorkletNode) // Disconnect the source from the AudioWorkletNode\n this.audioWorkletNode.disconnect(this.audioContext.destination) // Disconnect the AudioWorkletNode from the destination\n this.audioWorkletNode.port.postMessage({ command: 'pause' })\n }\n\n stopMediaStreamTracks() {\n // Stop all audio tracks to stop the recording icon\n const tracks = this.source.mediaStream.getTracks()\n tracks.forEach((track) => track.stop())\n }\n\n async playRecordedData({\n recordedData,\n }: {\n recordedData: ArrayBuffer\n mimeType?: string\n }) {\n try {\n const blob = new Blob([recordedData])\n const url = URL.createObjectURL(blob)\n const response = await fetch(url)\n const arrayBuffer = await response.arrayBuffer()\n\n // Decode the audio data\n const audioBuffer =\n await this.audioContext.decodeAudioData(arrayBuffer)\n\n // Create a buffer source node and play the audio\n const bufferSource = this.audioContext.createBufferSource()\n bufferSource.buffer = audioBuffer\n bufferSource.connect(this.audioContext.destination)\n bufferSource.start()\n logger.debug('Playing recorded data', recordedData)\n } catch (error) {\n console.error(`[${TAG}] Failed to play recorded data:`, error)\n }\n }\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 resume() {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.audioWorkletNode.port.postMessage({ command: 'resume' })\n }\n}\n"]}
1
+ {"version":3,"file":"WebRecorder.web.js","sourceRoot":"","sources":["../src/WebRecorder.web.ts"],"names":[],"mappings":"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","sourcesContent":["// src/WebRecorder.ts\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport { RecordingConfig } from './ExpoAudioStream.types'\nimport {\n EmitAudioAnalysisFunction,\n EmitAudioEventFunction,\n} from './ExpoAudioStream.web'\nimport { getLogger } from './logger'\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 }\n}\n\ninterface AudioFeaturesEvent {\n data: {\n command: string\n result: AudioAnalysis\n }\n}\n\nconst DEFAULT_WEB_BITDEPTH = 32\nconst DEFAULT_WEB_POINTS_PER_SECOND = 10\nconst DEFAULT_WEB_INTERVAL = 500\nconst DEFAULT_WEB_NUMBER_OF_CHANNELS = 1\nconst DEFAULT_ALGORITHM = 'rms'\n\nconst TAG = 'WebRecorder'\nconst logger = getLogger(TAG)\n\nexport class WebRecorder {\n private audioContext: AudioContext\n private audioWorkletNode!: AudioWorkletNode\n private featureExtractorWorker?: Worker\n private source: MediaStreamAudioSourceNode\n private audioWorkletUrl: string\n private emitAudioEventCallback: EmitAudioEventFunction\n private emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n private config: RecordingConfig\n private position: number // Track the cumulative position\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 audioBuffer: Float32Array // Single buffer to store the audio data\n private audioBufferSize: number // Keep track of the buffer size\n private audioAnalysisData: AudioAnalysis // Keep updating the full audio analysis data with latest events\n private packetCount: number = 0\n\n constructor({\n audioContext,\n source,\n recordingConfig,\n audioWorkletUrl,\n emitAudioEventCallback,\n emitAudioAnalysisCallback,\n }: {\n audioContext: AudioContext\n source: MediaStreamAudioSourceNode\n recordingConfig: RecordingConfig\n audioWorkletUrl: string\n emitAudioEventCallback: EmitAudioEventFunction\n emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n }) {\n this.audioContext = audioContext\n this.source = source\n this.audioWorkletUrl = audioWorkletUrl\n this.emitAudioEventCallback = emitAudioEventCallback\n this.emitAudioAnalysisCallback = emitAudioAnalysisCallback\n this.config = recordingConfig\n this.position = 0\n\n const audioContextFormat = this.checkAudioContextFormat({\n sampleRate: this.audioContext.sampleRate,\n })\n 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 // Initialize the audio buffer separately\n this.audioBuffer = new Float32Array(0)\n this.audioBufferSize = 0\n\n this.audioAnalysisData = {\n amplitudeRange: { min: 0, max: 0 },\n dataPoints: [],\n durationMs: 0,\n samples: 0,\n amplitudeAlgorithm: recordingConfig.algorithm || DEFAULT_ALGORITHM,\n bitDepth: this.bitDepth,\n numberOfChannels: this.numberOfChannels,\n sampleRate: this.config.sampleRate || this.audioContext.sampleRate,\n pointsPerSecond:\n this.config.pointsPerSecond || DEFAULT_WEB_POINTS_PER_SECOND,\n speakerChanges: [],\n }\n\n if (recordingConfig.enableProcessing) {\n this.initFeatureExtractorWorker()\n }\n }\n\n async init() {\n try {\n if (!this.audioWorkletUrl) {\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 } else {\n await this.audioContext.audioWorklet.addModule(\n this.audioWorkletUrl\n )\n }\n this.audioWorkletNode = new AudioWorkletNode(\n this.audioContext,\n 'recorder-processor'\n )\n\n this.audioWorkletNode.port.onmessage = async (\n event: AudioWorkletEvent\n ) => {\n const command = event.data.command\n if (command !== 'newData') {\n return\n }\n const pcmBufferFloat = event.data.recordedData\n\n if (!pcmBufferFloat) {\n logger.warn('Received empty audio buffer', event)\n return\n }\n\n // Handle the audio blob (e.g., send it to the server or process it further)\n logger.debug(\n `Received audio blob from processor len:${pcmBufferFloat?.length}`,\n event\n )\n // Concatenate the incoming Float32Array to the existing buffer\n const newBuffer = new Float32Array(\n this.audioBufferSize + pcmBufferFloat.length\n )\n newBuffer.set(this.audioBuffer, 0)\n newBuffer.set(pcmBufferFloat, this.audioBufferSize)\n this.audioBuffer = newBuffer\n this.audioBufferSize += pcmBufferFloat.length\n\n const sampleRate =\n event.data.sampleRate ?? this.audioContext.sampleRate\n const duration = pcmBufferFloat.length / sampleRate // Calculate duration of the current buffer\n\n let data: Float32Array\n if (this.packetCount === 0) {\n // Initialize WAV header\n const wavHeaderBuffer = writeWavHeader({\n sampleRate: this.audioContext.sampleRate,\n numChannels: this.numberOfChannels,\n bitDepth: this.exportBitDepth,\n })\n\n // For the first packet, combine WAV header with audio data\n const headerFloatArray = new Float32Array(wavHeaderBuffer)\n data = new Float32Array(\n headerFloatArray.length + this.audioBuffer.length\n )\n data.set(headerFloatArray, 0)\n data.set(this.audioBuffer, headerFloatArray.length)\n } else {\n // For subsequent packets, just send the new audio data\n data = pcmBufferFloat\n }\n\n // Track the number of packets\n this.packetCount += 1\n\n this.emitAudioEventCallback({\n data,\n position: this.position,\n })\n this.position += duration // Update position\n\n this.featureExtractorWorker?.postMessage(\n {\n command: 'process',\n channelData: pcmBufferFloat,\n sampleRate,\n pointsPerSecond:\n this.config.pointsPerSecond ||\n DEFAULT_WEB_POINTS_PER_SECOND,\n algorithm: this.config.algorithm || 'rms',\n bitDepth: this.bitDepth,\n fullAudioDurationMs: this.position * 1000,\n numberOfChannels: this.numberOfChannels,\n features: this.config.features,\n },\n []\n )\n }\n\n logger.debug(\n `WebRecorder initialized -- recordSampleRate=${this.audioContext.sampleRate}`,\n this.config\n )\n this.audioWorkletNode.port.postMessage({\n command: 'init',\n recordSampleRate: this.audioContext.sampleRate, // Pass the original sample rate\n exportSampleRate:\n this.config.sampleRate ?? this.audioContext.sampleRate,\n bitDepth: this.bitDepth,\n exportBitDepth: this.exportBitDepth,\n channels: this.numberOfChannels,\n interval: this.config.interval ?? DEFAULT_WEB_INTERVAL,\n })\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 initFeatureExtractorWorker(featuresExtratorUrl?: string) {\n try {\n if (featuresExtratorUrl) {\n // Initialize the feature extractor worker\n //TODO: create audio feature extractor from a Blob instead of url since we cannot include the url directly in the library\n // We keep the url during dev and use the blob in production.\n this.featureExtractorWorker = new Worker(\n new URL(featuresExtratorUrl, window.location.href)\n )\n this.featureExtractorWorker.onmessage =\n this.handleFeatureExtractorMessage.bind(this)\n this.featureExtractorWorker.onerror =\n this.handleWorkerError.bind(this)\n } else {\n // Fallback to the inline worker if the URL is not provided\n this.initFallbackWorker()\n }\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize feature extractor worker`,\n error\n )\n this.initFallbackWorker()\n }\n }\n\n initFallbackWorker() {\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}] Default Inline worker failed`, error)\n }\n logger.log('Inline worker initialized successfully')\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize Inline Feature Extractor worker`,\n error\n )\n }\n }\n\n handleWorkerError(error: ErrorEvent) {\n console.error(`[${TAG}] Feature extractor worker error:`, error)\n }\n\n handleFeatureExtractorMessage(event: AudioFeaturesEvent) {\n if (event.data.command === 'features') {\n const segmentResult = event.data.result\n\n // Merge the segment result with the full audio analysis data\n this.audioAnalysisData.dataPoints.push(...segmentResult.dataPoints)\n this.audioAnalysisData.speakerChanges?.push(\n ...(segmentResult.speakerChanges ?? [])\n )\n this.audioAnalysisData.durationMs = segmentResult.durationMs\n if (segmentResult.amplitudeRange) {\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 // Handle the extracted features (e.g., emit an event or log them)\n logger.debug('features event segmentResult', segmentResult)\n logger.debug(\n 'features event audioAnalysisData',\n this.audioAnalysisData\n )\n this.emitAudioAnalysisCallback(segmentResult)\n }\n }\n\n start() {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.packetCount = 0\n }\n\n stop(): Promise<Float32Array> {\n return new Promise((resolve, reject) => {\n try {\n if (this.audioWorkletNode) {\n // this.source.disconnect(this.audioWorkletNode);\n // this.audioWorkletNode.disconnect(this.audioContext.destination);\n this.audioWorkletNode.port.postMessage({ command: 'stop' })\n\n // Set a timeout to reject the promise if no message is received within 5 seconds\n const timeout = setTimeout(() => {\n this.audioWorkletNode.port.removeEventListener(\n 'message',\n onMessage\n )\n reject(\n new Error(\n \"Timeout error, audioWorkletNode didn't complete.\"\n )\n )\n }, 5000)\n\n // Listen for the recordedData message to confirm stopping\n const onMessage = async (event: AudioWorkletEvent) => {\n const command = event.data.command\n if (command === 'recordedData') {\n clearTimeout(timeout) // Clear the timeout\n\n const rawPCMDataFull =\n event.data.recordedData?.slice(0)\n\n if (!rawPCMDataFull) {\n reject(new Error('Failed to get recorded data'))\n return\n }\n\n // Compute duration of the recorded data\n const duration =\n rawPCMDataFull.byteLength /\n (this.audioContext.sampleRate *\n (this.exportBitDepth /\n this.numberOfChannels))\n logger.debug(\n `Received recorded data -- Duration: ${duration} vs ${rawPCMDataFull.byteLength / this.audioContext.sampleRate} seconds`\n )\n logger.debug(\n `recordedData.length=${rawPCMDataFull.byteLength} vs transmittedData.length=${this.audioBufferSize}`\n )\n\n // Remove the event listener after receiving the final data\n this.audioWorkletNode.port.removeEventListener(\n 'message',\n onMessage\n )\n resolve(this.audioBuffer) // Resolve the promise with the collected buffers\n }\n }\n this.audioWorkletNode.port.addEventListener(\n 'message',\n onMessage\n )\n }\n\n // Stop all media stream tracks to stop the browser recording\n this.stopMediaStreamTracks()\n } catch (error) {\n reject(error)\n }\n })\n }\n\n pause() {\n this.source.disconnect(this.audioWorkletNode) // Disconnect the source from the AudioWorkletNode\n this.audioWorkletNode.disconnect(this.audioContext.destination) // Disconnect the AudioWorkletNode from the destination\n this.audioWorkletNode.port.postMessage({ command: 'pause' })\n }\n\n stopMediaStreamTracks() {\n // Stop all audio tracks to stop the recording icon\n const tracks = this.source.mediaStream.getTracks()\n tracks.forEach((track) => track.stop())\n }\n\n async playRecordedData({\n recordedData,\n }: {\n recordedData: ArrayBuffer\n mimeType?: string\n }) {\n try {\n const blob = new Blob([recordedData])\n const url = URL.createObjectURL(blob)\n const response = await fetch(url)\n const arrayBuffer = await response.arrayBuffer()\n\n // Decode the audio data\n const audioBuffer =\n await this.audioContext.decodeAudioData(arrayBuffer)\n\n // Create a buffer source node and play the audio\n const bufferSource = this.audioContext.createBufferSource()\n bufferSource.buffer = audioBuffer\n bufferSource.connect(this.audioContext.destination)\n bufferSource.start()\n logger.debug('Playing recorded data', recordedData)\n } catch (error) {\n console.error(`[${TAG}] Failed to play recorded data:`, error)\n }\n }\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 resume() {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.audioWorkletNode.port.postMessage({ command: 'resume' })\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siteed/expo-audio-stream",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "description": "stream audio crossplatform",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",
@@ -38,6 +38,7 @@
38
38
  },
39
39
  "devDependencies": {
40
40
  "@expo/config-plugins": "^7.9.1",
41
+ "@siteed/react-native-logger": "^0.10.0",
41
42
  "@size-limit/preset-big-lib": "^11.1.4",
42
43
  "@types/jest": "^29.5.12",
43
44
  "@types/node": "^20.12.7",
@@ -63,6 +64,7 @@
63
64
  "typescript": "^5.5.4"
64
65
  },
65
66
  "peerDependencies": {
67
+ "@siteed/react-native-logger": "*",
66
68
  "expo": "*",
67
69
  "react": "*",
68
70
  "react-native": "*"
@@ -70,8 +72,5 @@
70
72
  "publishConfig": {
71
73
  "access": "public",
72
74
  "registry": "https://registry.npmjs.org"
73
- },
74
- "dependencies": {
75
- "@siteed/react-native-logger": "^0.9.3"
76
75
  }
77
76
  }
package/publish.sh CHANGED
@@ -5,5 +5,7 @@ yarn version patch
5
5
  version=$(node -p "require('./package.json').version")
6
6
  git add .
7
7
  git commit -m "feat: bump version to $version"
8
+ echo "Generated updated documentation for version $version"
9
+ yarn docgen
8
10
  yarn clean && yarn prepare && yarn npm publish
9
11
  rm package-lock.json
@@ -7,7 +7,7 @@ import {
7
7
  } from './ExpoAudioStream.web'
8
8
  import { getLogger } from './logger'
9
9
  import { encodingToBitDepth } from './utils/encodingToBitDepth'
10
- import { WavHeaderOptions, writeWavHeader } from './utils/writeWavHeader'
10
+ import { writeWavHeader } from './utils/writeWavHeader'
11
11
  import { InlineFeaturesExtractor } from './workers/InlineFeaturesExtractor.web'
12
12
  import { InlineAudioWebWorker } from './workers/inlineAudioWebWorker.web'
13
13
 
@@ -48,9 +48,10 @@ export class WebRecorder {
48
48
  private numberOfChannels: number // Number of audio channels
49
49
  private bitDepth: number // Bit depth of the audio
50
50
  private exportBitDepth: number // Bit depth of the audio
51
- private buffer: Float32Array // Single buffer to store the audio data
52
- private bufferSize: number // Keep track of the buffer size
51
+ private audioBuffer: Float32Array // Single buffer to store the audio data
52
+ private audioBufferSize: number // Keep track of the buffer size
53
53
  private audioAnalysisData: AudioAnalysis // Keep updating the full audio analysis data with latest events
54
+ private packetCount: number = 0
54
55
 
55
56
  constructor({
56
57
  audioContext,
@@ -95,21 +96,9 @@ export class WebRecorder {
95
96
  audioContextFormat.bitDepth ||
96
97
  DEFAULT_WEB_BITDEPTH
97
98
 
98
- // Initialize WAV header
99
- const wavHeader = writeWavHeader({
100
- sampleRate: this.audioContext.sampleRate,
101
- numChannels: this.numberOfChannels,
102
- bitDepth: this.exportBitDepth,
103
- })
104
-
105
- // Initialize the buffer with WAV header
106
- this.buffer = new Float32Array(
107
- wavHeader.byteLength / Float32Array.BYTES_PER_ELEMENT
108
- )
109
- this.bufferSize = this.buffer.byteLength
110
-
111
- // Copy WAV header to Float32Array buffer
112
- new Uint8Array(this.buffer.buffer).set(new Uint8Array(wavHeader))
99
+ // Initialize the audio buffer separately
100
+ this.audioBuffer = new Float32Array(0)
101
+ this.audioBufferSize = 0
113
102
 
114
103
  this.audioAnalysisData = {
115
104
  amplitudeRange: { min: 0, max: 0 },
@@ -155,29 +144,57 @@ export class WebRecorder {
155
144
  if (command !== 'newData') {
156
145
  return
157
146
  }
158
- // Handle the audio blob (e.g., send it to the server or process it further)
159
- logger.debug('Received audio blob from processor', event)
160
147
  const pcmBufferFloat = event.data.recordedData
161
148
 
162
149
  if (!pcmBufferFloat) {
150
+ logger.warn('Received empty audio buffer', event)
163
151
  return
164
152
  }
165
153
 
154
+ // Handle the audio blob (e.g., send it to the server or process it further)
155
+ logger.debug(
156
+ `Received audio blob from processor len:${pcmBufferFloat?.length}`,
157
+ event
158
+ )
166
159
  // Concatenate the incoming Float32Array to the existing buffer
167
160
  const newBuffer = new Float32Array(
168
- this.bufferSize + pcmBufferFloat.length
161
+ this.audioBufferSize + pcmBufferFloat.length
169
162
  )
170
- newBuffer.set(this.buffer, 0)
171
- newBuffer.set(pcmBufferFloat, this.bufferSize)
172
- this.buffer = newBuffer
173
- this.bufferSize += pcmBufferFloat.length
163
+ newBuffer.set(this.audioBuffer, 0)
164
+ newBuffer.set(pcmBufferFloat, this.audioBufferSize)
165
+ this.audioBuffer = newBuffer
166
+ this.audioBufferSize += pcmBufferFloat.length
174
167
 
175
168
  const sampleRate =
176
169
  event.data.sampleRate ?? this.audioContext.sampleRate
177
170
  const duration = pcmBufferFloat.length / sampleRate // Calculate duration of the current buffer
178
171
 
172
+ let data: Float32Array
173
+ if (this.packetCount === 0) {
174
+ // Initialize WAV header
175
+ const wavHeaderBuffer = writeWavHeader({
176
+ sampleRate: this.audioContext.sampleRate,
177
+ numChannels: this.numberOfChannels,
178
+ bitDepth: this.exportBitDepth,
179
+ })
180
+
181
+ // For the first packet, combine WAV header with audio data
182
+ const headerFloatArray = new Float32Array(wavHeaderBuffer)
183
+ data = new Float32Array(
184
+ headerFloatArray.length + this.audioBuffer.length
185
+ )
186
+ data.set(headerFloatArray, 0)
187
+ data.set(this.audioBuffer, headerFloatArray.length)
188
+ } else {
189
+ // For subsequent packets, just send the new audio data
190
+ data = pcmBufferFloat
191
+ }
192
+
193
+ // Track the number of packets
194
+ this.packetCount += 1
195
+
179
196
  this.emitAudioEventCallback({
180
- data: pcmBufferFloat,
197
+ data,
181
198
  position: this.position,
182
199
  })
183
200
  this.position += duration // Update position
@@ -309,6 +326,7 @@ export class WebRecorder {
309
326
  start() {
310
327
  this.source.connect(this.audioWorkletNode)
311
328
  this.audioWorkletNode.connect(this.audioContext.destination)
329
+ this.packetCount = 0
312
330
  }
313
331
 
314
332
  stop(): Promise<Float32Array> {
@@ -356,7 +374,7 @@ export class WebRecorder {
356
374
  `Received recorded data -- Duration: ${duration} vs ${rawPCMDataFull.byteLength / this.audioContext.sampleRate} seconds`
357
375
  )
358
376
  logger.debug(
359
- `recordedData.length=${rawPCMDataFull.byteLength} vs transmittedData.length=${this.bufferSize}`
377
+ `recordedData.length=${rawPCMDataFull.byteLength} vs transmittedData.length=${this.audioBufferSize}`
360
378
  )
361
379
 
362
380
  // Remove the event listener after receiving the final data
@@ -364,7 +382,7 @@ export class WebRecorder {
364
382
  'message',
365
383
  onMessage
366
384
  )
367
- resolve(this.buffer) // Resolve the promise with the collected buffers
385
+ resolve(this.audioBuffer) // Resolve the promise with the collected buffers
368
386
  }
369
387
  }
370
388
  this.audioWorkletNode.port.addEventListener(