@siteed/expo-audio-stream 1.7.2 → 1.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/README.md +6 -1
  3. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +39 -0
  4. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +124 -12
  5. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +26 -2
  6. package/build/AudioRecorder.provider.d.ts.map +1 -1
  7. package/build/AudioRecorder.provider.js +1 -0
  8. package/build/AudioRecorder.provider.js.map +1 -1
  9. package/build/ExpoAudioStream.types.d.ts +22 -1
  10. package/build/ExpoAudioStream.types.d.ts.map +1 -1
  11. package/build/ExpoAudioStream.types.js.map +1 -1
  12. package/build/ExpoAudioStream.web.d.ts +15 -2
  13. package/build/ExpoAudioStream.web.d.ts.map +1 -1
  14. package/build/ExpoAudioStream.web.js +99 -40
  15. package/build/ExpoAudioStream.web.js.map +1 -1
  16. package/build/WebRecorder.web.d.ts +14 -3
  17. package/build/WebRecorder.web.d.ts.map +1 -1
  18. package/build/WebRecorder.web.js +188 -100
  19. package/build/WebRecorder.web.js.map +1 -1
  20. package/build/events.d.ts +6 -0
  21. package/build/events.d.ts.map +1 -1
  22. package/build/events.js.map +1 -1
  23. package/build/useAudioRecorder.d.ts +2 -1
  24. package/build/useAudioRecorder.d.ts.map +1 -1
  25. package/build/useAudioRecorder.js +46 -5
  26. package/build/useAudioRecorder.js.map +1 -1
  27. package/build/workers/inlineAudioWebWorker.web.d.ts +1 -1
  28. package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -1
  29. package/build/workers/inlineAudioWebWorker.web.js +65 -160
  30. package/build/workers/inlineAudioWebWorker.web.js.map +1 -1
  31. package/ios/AudioStreamManager.swift +127 -8
  32. package/ios/AudioStreamManagerDelegate.swift +8 -2
  33. package/ios/ExpoAudioStreamModule.swift +61 -46
  34. package/ios/RecordingResult.swift +2 -0
  35. package/ios/RecordingSettings.swift +63 -3
  36. package/package.json +1 -1
  37. package/src/AudioRecorder.provider.tsx +1 -0
  38. package/src/ExpoAudioStream.types.ts +24 -1
  39. package/src/ExpoAudioStream.web.ts +111 -38
  40. package/src/WebRecorder.web.ts +238 -138
  41. package/src/events.ts +7 -0
  42. package/src/useAudioRecorder.tsx +68 -7
  43. package/src/workers/inlineAudioWebWorker.web.tsx +65 -160
@@ -10,6 +10,13 @@ const DEFAULT_WEB_INTERVAL = 500;
10
10
  const DEFAULT_WEB_NUMBER_OF_CHANNELS = 1;
11
11
  const DEFAULT_ALGORITHM = 'rms';
12
12
  const TAG = 'WebRecorder';
13
+ const STOP_PERFORMANCE_MARKS = {
14
+ STOP_INITIATED: 'stopInitiated',
15
+ COMPRESSED_RECORDING_STOP: 'compressedRecordingStop',
16
+ AUDIO_WORKLET_STOP: 'audioWorkletStop',
17
+ CLEANUP: 'cleanup',
18
+ TOTAL_STOP_TIME: 'totalStopTime',
19
+ };
13
20
  export class WebRecorder {
14
21
  audioContext;
15
22
  audioWorkletNode;
@@ -19,15 +26,18 @@ export class WebRecorder {
19
26
  emitAudioEventCallback;
20
27
  emitAudioAnalysisCallback;
21
28
  config;
22
- position; // Track the cumulative position
29
+ position = 0;
23
30
  numberOfChannels; // Number of audio channels
24
31
  bitDepth; // Bit depth of the audio
25
32
  exportBitDepth; // Bit depth of the audio
26
- audioBuffer; // Single buffer to store the audio data
27
- audioBufferSize; // Keep track of the buffer size
28
33
  audioAnalysisData; // Keep updating the full audio analysis data with latest events
29
34
  packetCount = 0;
30
35
  logger;
36
+ compressedMediaRecorder = null;
37
+ compressedChunks = [];
38
+ compressedSize = 0;
39
+ pendingCompressedChunk = null;
40
+ wavMimeType = 'audio/wav';
31
41
  constructor({ audioContext, source, recordingConfig, audioWorkletUrl, emitAudioEventCallback, emitAudioAnalysisCallback, logger, }) {
32
42
  this.audioContext = audioContext;
33
43
  this.source = source;
@@ -35,7 +45,6 @@ export class WebRecorder {
35
45
  this.emitAudioEventCallback = emitAudioEventCallback;
36
46
  this.emitAudioAnalysisCallback = emitAudioAnalysisCallback;
37
47
  this.config = recordingConfig;
38
- this.position = 0;
39
48
  this.logger = logger;
40
49
  const audioContextFormat = this.checkAudioContextFormat({
41
50
  sampleRate: this.audioContext.sampleRate,
@@ -55,9 +64,6 @@ export class WebRecorder {
55
64
  }) ||
56
65
  audioContextFormat.bitDepth ||
57
66
  DEFAULT_WEB_BITDEPTH;
58
- // Initialize the audio buffer separately
59
- this.audioBuffer = new Float32Array(0);
60
- this.audioBufferSize = 0;
61
67
  this.audioAnalysisData = {
62
68
  amplitudeRange: { min: 0, max: 0 },
63
69
  dataPoints: [],
@@ -73,6 +79,10 @@ export class WebRecorder {
73
79
  if (recordingConfig.enableProcessing) {
74
80
  this.initFeatureExtractorWorker();
75
81
  }
82
+ // Initialize compressed recording if enabled
83
+ if (recordingConfig.compression?.enabled) {
84
+ this.initializeCompressedRecorder();
85
+ }
76
86
  }
77
87
  async init() {
78
88
  try {
@@ -89,66 +99,61 @@ export class WebRecorder {
89
99
  this.audioWorkletNode = new AudioWorkletNode(this.audioContext, 'recorder-processor');
90
100
  this.audioWorkletNode.port.onmessage = async (event) => {
91
101
  const command = event.data.command;
92
- if (command !== 'newData') {
102
+ if (command !== 'newData')
93
103
  return;
94
- }
95
104
  const pcmBufferFloat = event.data.recordedData;
96
105
  if (!pcmBufferFloat) {
97
106
  this.logger?.warn('Received empty audio buffer', event);
98
107
  return;
99
108
  }
100
- // Handle the audio blob (e.g., send it to the server or process it further)
101
- this.logger?.debug(`Received audio blob from processor len:${pcmBufferFloat?.length}`, event);
102
- // Concatenate the incoming Float32Array to the existing buffer
103
- const newBuffer = new Float32Array(this.audioBufferSize + pcmBufferFloat.length);
104
- newBuffer.set(this.audioBuffer, 0);
105
- newBuffer.set(pcmBufferFloat, this.audioBufferSize);
106
- this.audioBuffer = newBuffer;
107
- this.audioBufferSize += pcmBufferFloat.length;
109
+ // Process data in smaller chunks and emit immediately
110
+ const chunkSize = this.audioContext.sampleRate * 2; // Reduce to 2 seconds chunks
108
111
  const sampleRate = event.data.sampleRate ?? this.audioContext.sampleRate;
109
- const duration = pcmBufferFloat.length / sampleRate; // Calculate duration of the current buffer
110
- let data;
111
- if (this.packetCount === 0) {
112
- // Initialize WAV header
113
- const wavHeaderBuffer = writeWavHeader({
114
- sampleRate: this.audioContext.sampleRate,
115
- numChannels: this.numberOfChannels,
116
- bitDepth: this.exportBitDepth,
112
+ const duration = pcmBufferFloat.length / sampleRate;
113
+ // Emit chunks without storing them
114
+ for (let i = 0; i < pcmBufferFloat.length; i += chunkSize) {
115
+ const chunk = pcmBufferFloat.slice(i, i + chunkSize);
116
+ const chunkPosition = this.position + i / sampleRate;
117
+ // Process features if enabled
118
+ if (this.config.enableProcessing &&
119
+ this.featureExtractorWorker) {
120
+ this.featureExtractorWorker.postMessage({
121
+ command: 'process',
122
+ channelData: chunk,
123
+ sampleRate,
124
+ pointsPerSecond: this.config.pointsPerSecond ||
125
+ DEFAULT_WEB_POINTS_PER_SECOND,
126
+ algorithm: this.config.algorithm || 'rms',
127
+ bitDepth: this.bitDepth,
128
+ fullAudioDurationMs: this.position * 1000,
129
+ numberOfChannels: this.numberOfChannels,
130
+ features: this.config.features,
131
+ }, []);
132
+ }
133
+ // Emit chunk immediately
134
+ this.emitAudioEventCallback({
135
+ data: chunk,
136
+ position: chunkPosition,
137
+ compression: this.pendingCompressedChunk
138
+ ? {
139
+ data: this.pendingCompressedChunk,
140
+ size: this.pendingCompressedChunk.size,
141
+ totalSize: this.compressedSize,
142
+ mimeType: 'audio/webm',
143
+ format: 'opus',
144
+ bitrate: this.config.compression?.bitrate ??
145
+ 128000,
146
+ }
147
+ : undefined,
117
148
  });
118
- // For the first packet, combine WAV header with audio data
119
- const headerFloatArray = new Float32Array(wavHeaderBuffer);
120
- data = new Float32Array(headerFloatArray.length + this.audioBuffer.length);
121
- data.set(headerFloatArray, 0);
122
- data.set(this.audioBuffer, headerFloatArray.length);
123
149
  }
124
- else {
125
- // For subsequent packets, just send the new audio data
126
- data = pcmBufferFloat;
127
- }
128
- // Track the number of packets
129
- this.packetCount += 1;
130
- this.emitAudioEventCallback({
131
- data,
132
- position: this.position,
133
- });
134
- this.position += duration; // Update position
135
- this.featureExtractorWorker?.postMessage({
136
- command: 'process',
137
- channelData: pcmBufferFloat,
138
- sampleRate,
139
- pointsPerSecond: this.config.pointsPerSecond ||
140
- DEFAULT_WEB_POINTS_PER_SECOND,
141
- algorithm: this.config.algorithm || 'rms',
142
- bitDepth: this.bitDepth,
143
- fullAudioDurationMs: this.position * 1000,
144
- numberOfChannels: this.numberOfChannels,
145
- features: this.config.features,
146
- }, []);
150
+ this.position += duration;
151
+ this.pendingCompressedChunk = null;
147
152
  };
148
153
  this.logger?.debug(`WebRecorder initialized -- recordSampleRate=${this.audioContext.sampleRate}`, this.config);
149
154
  this.audioWorkletNode.port.postMessage({
150
155
  command: 'init',
151
- recordSampleRate: this.audioContext.sampleRate, // Pass the original sample rate
156
+ recordSampleRate: this.audioContext.sampleRate,
152
157
  exportSampleRate: this.config.sampleRate ?? this.audioContext.sampleRate,
153
158
  bitDepth: this.bitDepth,
154
159
  exportBitDepth: this.exportBitDepth,
@@ -229,60 +234,119 @@ export class WebRecorder {
229
234
  this.source.connect(this.audioWorkletNode);
230
235
  this.audioWorkletNode.connect(this.audioContext.destination);
231
236
  this.packetCount = 0;
237
+ if (this.compressedMediaRecorder) {
238
+ this.compressedMediaRecorder.start(this.config.interval ?? 1000);
239
+ }
232
240
  }
233
- stop() {
234
- return new Promise((resolve, reject) => {
235
- try {
236
- if (this.audioWorkletNode) {
237
- // this.source.disconnect(this.audioWorkletNode);
238
- // this.audioWorkletNode.disconnect(this.audioContext.destination);
239
- this.audioWorkletNode.port.postMessage({ command: 'stop' });
240
- // Set a timeout to reject the promise if no message is received within 5 seconds
241
- const timeout = setTimeout(() => {
242
- this.audioWorkletNode.port.removeEventListener('message', onMessage);
243
- reject(new Error("Timeout error, audioWorkletNode didn't complete."));
244
- }, 5000);
245
- // Listen for the recordedData message to confirm stopping
246
- const onMessage = async (event) => {
247
- const command = event.data.command;
248
- if (command === 'recordedData') {
249
- clearTimeout(timeout); // Clear the timeout
250
- const rawPCMDataFull = event.data.recordedData?.slice(0);
251
- if (!rawPCMDataFull) {
252
- reject(new Error('Failed to get recorded data'));
253
- return;
254
- }
255
- // Compute duration of the recorded data
256
- const duration = rawPCMDataFull.byteLength /
257
- (this.audioContext.sampleRate *
258
- (this.exportBitDepth /
259
- this.numberOfChannels));
260
- this.logger?.debug(`Received recorded data -- Duration: ${duration} vs ${rawPCMDataFull.byteLength / this.audioContext.sampleRate} seconds`);
261
- this.logger?.debug(`recordedData.length=${rawPCMDataFull.byteLength} vs transmittedData.length=${this.audioBufferSize}`);
262
- // Remove the event listener after receiving the final data
263
- this.audioWorkletNode.port.removeEventListener('message', onMessage);
264
- const convertedPCM = await convertPCMToFloat32({
265
- buffer: rawPCMDataFull.buffer,
266
- skipWavHeader: true,
267
- bitDepth: this.exportBitDepth,
268
- });
269
- resolve(convertedPCM.pcmValues); // Resolve the promise with the collected buffers
270
- }
271
- };
272
- this.audioWorkletNode.port.addEventListener('message', onMessage);
273
- }
274
- // Stop all media stream tracks to stop the browser recording
275
- this.stopMediaStreamTracks();
276
- }
277
- catch (error) {
278
- reject(error);
241
+ async stop() {
242
+ try {
243
+ if (this.compressedMediaRecorder) {
244
+ this.compressedMediaRecorder.stop();
245
+ return {
246
+ pcmData: new Float32Array(), // Return empty array since we're streaming
247
+ compressedBlob: new Blob(this.compressedChunks, {
248
+ type: 'audio/webm;codecs=opus',
249
+ }),
250
+ };
279
251
  }
252
+ return { pcmData: new Float32Array() };
253
+ }
254
+ finally {
255
+ this.cleanup();
256
+ }
257
+ }
258
+ cleanup() {
259
+ if (this.audioContext) {
260
+ this.audioContext.close();
261
+ }
262
+ if (this.audioWorkletNode) {
263
+ this.audioWorkletNode.disconnect();
264
+ }
265
+ if (this.source) {
266
+ this.source.disconnect();
267
+ }
268
+ this.stopMediaStreamTracks();
269
+ }
270
+ // Helper method to process recording stop
271
+ async processRecordingStop() {
272
+ const processStartTime = performance.now();
273
+ this.logger?.debug('[Performance] Starting recording stop process');
274
+ const [compressedData, workletData] = await Promise.all([
275
+ this.stopCompressedRecording(),
276
+ this.stopAudioWorklet(),
277
+ ]);
278
+ this.logger?.debug(`[Performance] Recording stop process completed in ${performance.now() - processStartTime}ms`);
279
+ return {
280
+ pcmData: workletData ??
281
+ new Float32Array(this.audioAnalysisData.dataPoints.length),
282
+ compressedBlob: compressedData,
283
+ };
284
+ }
285
+ // Helper method to stop compressed recording
286
+ stopCompressedRecording() {
287
+ const startTime = performance.now();
288
+ this.logger?.debug(`[Performance][${STOP_PERFORMANCE_MARKS.COMPRESSED_RECORDING_STOP}] Starting compressed recording stop`);
289
+ if (!this.compressedMediaRecorder) {
290
+ this.logger?.debug('[Performance] No compressed recorder to stop');
291
+ return Promise.resolve(undefined);
292
+ }
293
+ return new Promise((resolve) => {
294
+ this.compressedMediaRecorder.onstop = () => {
295
+ const blob = new Blob(this.compressedChunks, {
296
+ type: 'audio/webm;codecs=opus',
297
+ });
298
+ this.logger?.debug(`[Performance][${STOP_PERFORMANCE_MARKS.COMPRESSED_RECORDING_STOP}] Compressed recording stopped in ${performance.now() - startTime}ms, size: ${blob.size}`);
299
+ resolve(blob);
300
+ };
301
+ this.compressedMediaRecorder.stop();
302
+ });
303
+ }
304
+ // Helper method to stop audio worklet
305
+ stopAudioWorklet() {
306
+ const startTime = performance.now();
307
+ this.logger?.debug(`[Performance][${STOP_PERFORMANCE_MARKS.AUDIO_WORKLET_STOP}] Starting audio worklet stop`);
308
+ if (!this.audioWorkletNode) {
309
+ this.logger?.debug('[Performance] No audio worklet to stop');
310
+ return Promise.resolve(undefined);
311
+ }
312
+ return new Promise((resolve) => {
313
+ const onMessage = (event) => {
314
+ if (event.data.command === 'recordedData') {
315
+ this.audioWorkletNode?.port.removeEventListener('message', onMessage);
316
+ const rawPCMDataFull = event.data.recordedData?.slice(0);
317
+ if (!rawPCMDataFull) {
318
+ this.logger?.debug('[Performance] No PCM data received');
319
+ resolve(undefined);
320
+ return;
321
+ }
322
+ if (this.exportBitDepth !== this.bitDepth) {
323
+ const conversionStart = performance.now();
324
+ convertPCMToFloat32({
325
+ buffer: rawPCMDataFull.buffer,
326
+ bitDepth: this.exportBitDepth,
327
+ skipWavHeader: true,
328
+ logger: this.logger,
329
+ }).then(({ pcmValues }) => {
330
+ this.logger?.debug(`[Performance] PCM conversion completed in ${performance.now() - conversionStart}ms`);
331
+ this.logger?.debug(`[Performance][${STOP_PERFORMANCE_MARKS.AUDIO_WORKLET_STOP}] Audio worklet stopped in ${performance.now() - startTime}ms`);
332
+ resolve(pcmValues);
333
+ });
334
+ }
335
+ else {
336
+ this.logger?.debug(`[Performance][${STOP_PERFORMANCE_MARKS.AUDIO_WORKLET_STOP}] Audio worklet stopped in ${performance.now() - startTime}ms`);
337
+ resolve(rawPCMDataFull);
338
+ }
339
+ }
340
+ };
341
+ this.audioWorkletNode.port.addEventListener('message', onMessage);
342
+ this.audioWorkletNode.port.postMessage({ command: 'stop' });
280
343
  });
281
344
  }
282
345
  pause() {
283
346
  this.source.disconnect(this.audioWorkletNode); // Disconnect the source from the AudioWorkletNode
284
347
  this.audioWorkletNode.disconnect(this.audioContext.destination); // Disconnect the AudioWorkletNode from the destination
285
348
  this.audioWorkletNode.port.postMessage({ command: 'pause' });
349
+ this.compressedMediaRecorder?.pause();
286
350
  }
287
351
  stopMediaStreamTracks() {
288
352
  // Stop all audio tracks to stop the recording icon
@@ -334,6 +398,30 @@ export class WebRecorder {
334
398
  this.source.connect(this.audioWorkletNode);
335
399
  this.audioWorkletNode.connect(this.audioContext.destination);
336
400
  this.audioWorkletNode.port.postMessage({ command: 'resume' });
401
+ this.compressedMediaRecorder?.resume();
402
+ }
403
+ initializeCompressedRecorder() {
404
+ try {
405
+ const mimeType = 'audio/webm;codecs=opus';
406
+ if (!MediaRecorder.isTypeSupported(mimeType)) {
407
+ this.logger?.warn('Opus compression not supported in this browser');
408
+ return;
409
+ }
410
+ this.compressedMediaRecorder = new MediaRecorder(this.source.mediaStream, {
411
+ mimeType,
412
+ audioBitsPerSecond: this.config.compression?.bitrate ?? 128000,
413
+ });
414
+ this.compressedMediaRecorder.ondataavailable = (event) => {
415
+ if (event.data.size > 0) {
416
+ this.compressedChunks.push(event.data);
417
+ this.compressedSize += event.data.size;
418
+ this.pendingCompressedChunk = event.data;
419
+ }
420
+ };
421
+ }
422
+ catch (error) {
423
+ this.logger?.error('Failed to initialize compressed recorder:', error);
424
+ }
337
425
  }
338
426
  }
339
427
  //# sourceMappingURL=WebRecorder.web.js.map
@@ -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\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport { ConsoleLike, RecordingConfig } from './ExpoAudioStream.types'\nimport {\n EmitAudioAnalysisFunction,\n EmitAudioEventFunction,\n} from './ExpoAudioStream.web'\nimport { convertPCMToFloat32 } from './utils/convertPCMToFloat32'\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'\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 private logger?: ConsoleLike\n\n constructor({\n audioContext,\n source,\n recordingConfig,\n audioWorkletUrl,\n emitAudioEventCallback,\n emitAudioAnalysisCallback,\n logger,\n }: {\n audioContext: AudioContext\n source: MediaStreamAudioSourceNode\n recordingConfig: RecordingConfig\n audioWorkletUrl: string\n emitAudioEventCallback: EmitAudioEventFunction\n emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n logger?: ConsoleLike\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 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 // 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 this.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 this.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 this.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 this.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 this.logger?.debug('features event segmentResult', segmentResult)\n this.logger?.debug(\n `features event audioAnalysisData duration=${this.audioAnalysisData.durationMs}`,\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 this.logger?.debug(\n `Received recorded data -- Duration: ${duration} vs ${rawPCMDataFull.byteLength / this.audioContext.sampleRate} seconds`\n )\n this.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\n const convertedPCM = await convertPCMToFloat32({\n buffer: rawPCMDataFull.buffer,\n skipWavHeader: true,\n bitDepth: this.exportBitDepth,\n })\n\n resolve(convertedPCM.pcmValues) // 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 // Create a WAV blob with proper headers\n const wavHeaderBuffer = writeWavHeader({\n buffer: recordedData,\n sampleRate: this.audioContext.sampleRate,\n numChannels: this.numberOfChannels,\n bitDepth: this.exportBitDepth,\n })\n\n const blob = new Blob([wavHeaderBuffer], { type: 'audio/wav' })\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 this.logger?.debug('Playing recorded data', recordedData)\n\n // Clean up\n URL.revokeObjectURL(url)\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":"AAAA,qBAAqB;AAQrB,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAA;AAC/E,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA;AAiBzE,MAAM,oBAAoB,GAAG,EAAE,CAAA;AAC/B,MAAM,6BAA6B,GAAG,EAAE,CAAA;AACxC,MAAM,oBAAoB,GAAG,GAAG,CAAA;AAChC,MAAM,8BAA8B,GAAG,CAAC,CAAA;AACxC,MAAM,iBAAiB,GAAG,KAAK,CAAA;AAE/B,MAAM,GAAG,GAAG,aAAa,CAAA;AAEzB,MAAM,sBAAsB,GAAG;IAC3B,cAAc,EAAE,eAAe;IAC/B,yBAAyB,EAAE,yBAAyB;IACpD,kBAAkB,EAAE,kBAAkB;IACtC,OAAO,EAAE,SAAS;IAClB,eAAe,EAAE,eAAe;CAC1B,CAAA;AAEV,MAAM,OAAO,WAAW;IACZ,YAAY,CAAc;IAC1B,gBAAgB,CAAmB;IACnC,sBAAsB,CAAS;IAC/B,MAAM,CAA4B;IAClC,eAAe,CAAQ;IACvB,sBAAsB,CAAwB;IAC9C,yBAAyB,CAA2B;IACpD,MAAM,CAAiB;IACvB,QAAQ,GAAW,CAAC,CAAA;IACpB,gBAAgB,CAAQ,CAAC,2BAA2B;IACpD,QAAQ,CAAQ,CAAC,yBAAyB;IAC1C,cAAc,CAAQ,CAAC,yBAAyB;IAChD,iBAAiB,CAAe,CAAC,gEAAgE;IACjG,WAAW,GAAW,CAAC,CAAA;IACvB,MAAM,CAAc;IACpB,uBAAuB,GAAyB,IAAI,CAAA;IACpD,gBAAgB,GAAW,EAAE,CAAA;IAC7B,cAAc,GAAW,CAAC,CAAA;IAC1B,sBAAsB,GAAgB,IAAI,CAAA;IACjC,WAAW,GAAG,WAAW,CAAA;IAE1C,YAAY,EACR,YAAY,EACZ,MAAM,EACN,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,yBAAyB,EACzB,MAAM,GAST;QACG,IAAI,CAAC,YAAY,GAAG,YAAY,CAAA;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,eAAe,GAAG,eAAe,CAAA;QACtC,IAAI,CAAC,sBAAsB,GAAG,sBAAsB,CAAA;QACpD,IAAI,CAAC,yBAAyB,GAAG,yBAAyB,CAAA;QAC1D,IAAI,CAAC,MAAM,GAAG,eAAe,CAAA;QAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QAEpB,MAAM,kBAAkB,GAAG,IAAI,CAAC,uBAAuB,CAAC;YACpD,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,UAAU;SAC3C,CAAC,CAAA;QACF,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,sCAAsC,EAAE;YACvD,UAAU,EAAE,kBAAkB,CAAC,UAAU;YACzC,QAAQ,EAAE,kBAAkB,CAAC,QAAQ;YACrC,gBAAgB,EAAE,kBAAkB,CAAC,gBAAgB;SACxD,CAAC,CAAA;QAEF,IAAI,CAAC,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,CAAA;QAC3C,IAAI,CAAC,gBAAgB;YACjB,kBAAkB,CAAC,gBAAgB;gBACnC,8BAA8B,CAAA,CAAC,gCAAgC;QACnE,IAAI,CAAC,cAAc;YACf,kBAAkB,CAAC;gBACf,QAAQ,EAAE,eAAe,CAAC,QAAQ,IAAI,WAAW;aACpD,CAAC;gBACF,kBAAkB,CAAC,QAAQ;gBAC3B,oBAAoB,CAAA;QAExB,IAAI,CAAC,iBAAiB,GAAG;YACrB,cAAc,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;YAClC,UAAU,EAAE,EAAE;YACd,UAAU,EAAE,CAAC;YACb,OAAO,EAAE,CAAC;YACV,kBAAkB,EAAE,eAAe,CAAC,SAAS,IAAI,iBAAiB;YAClE,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU;YAClE,eAAe,EACX,IAAI,CAAC,MAAM,CAAC,eAAe,IAAI,6BAA6B;YAChE,cAAc,EAAE,EAAE;SACrB,CAAA;QAED,IAAI,eAAe,CAAC,gBAAgB,EAAE,CAAC;YACnC,IAAI,CAAC,0BAA0B,EAAE,CAAA;QACrC,CAAC;QAED,6CAA6C;QAC7C,IAAI,eAAe,CAAC,WAAW,EAAE,OAAO,EAAE,CAAC;YACvC,IAAI,CAAC,4BAA4B,EAAE,CAAA;QACvC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI;QACN,IAAI,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;gBACxB,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,oBAAoB,CAAC,EAAE;oBAC1C,IAAI,EAAE,wBAAwB;iBACjC,CAAC,CAAA;gBACF,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;gBACrC,MAAM,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;YACvD,CAAC;iBAAM,CAAC;gBACJ,MAAM,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,SAAS,CAC1C,IAAI,CAAC,eAAe,CACvB,CAAA;YACL,CAAC;YACD,IAAI,CAAC,gBAAgB,GAAG,IAAI,gBAAgB,CACxC,IAAI,CAAC,YAAY,EACjB,oBAAoB,CACvB,CAAA;YAED,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,GAAG,KAAK,EACxC,KAAwB,EAC1B,EAAE;gBACA,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAA;gBAClC,IAAI,OAAO,KAAK,SAAS;oBAAE,OAAM;gBAEjC,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,YAAY,CAAA;gBAC9C,IAAI,CAAC,cAAc,EAAE,CAAC;oBAClB,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAA;oBACvD,OAAM;gBACV,CAAC;gBAED,sDAAsD;gBACtD,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,GAAG,CAAC,CAAA,CAAC,6BAA6B;gBAChF,MAAM,UAAU,GACZ,KAAK,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU,CAAA;gBACzD,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,GAAG,UAAU,CAAA;gBAEnD,mCAAmC;gBACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;oBACxD,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAA;oBACpD,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG,UAAU,CAAA;oBAEpD,8BAA8B;oBAC9B,IACI,IAAI,CAAC,MAAM,CAAC,gBAAgB;wBAC5B,IAAI,CAAC,sBAAsB,EAC7B,CAAC;wBACC,IAAI,CAAC,sBAAsB,CAAC,WAAW,CACnC;4BACI,OAAO,EAAE,SAAS;4BAClB,WAAW,EAAE,KAAK;4BAClB,UAAU;4BACV,eAAe,EACX,IAAI,CAAC,MAAM,CAAC,eAAe;gCAC3B,6BAA6B;4BACjC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,KAAK;4BACzC,QAAQ,EAAE,IAAI,CAAC,QAAQ;4BACvB,mBAAmB,EAAE,IAAI,CAAC,QAAQ,GAAG,IAAI;4BACzC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;4BACvC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;yBACjC,EACD,EAAE,CACL,CAAA;oBACL,CAAC;oBAED,yBAAyB;oBACzB,IAAI,CAAC,sBAAsB,CAAC;wBACxB,IAAI,EAAE,KAAK;wBACX,QAAQ,EAAE,aAAa;wBACvB,WAAW,EAAE,IAAI,CAAC,sBAAsB;4BACpC,CAAC,CAAC;gCACI,IAAI,EAAE,IAAI,CAAC,sBAAsB;gCACjC,IAAI,EAAE,IAAI,CAAC,sBAAsB,CAAC,IAAI;gCACtC,SAAS,EAAE,IAAI,CAAC,cAAc;gCAC9B,QAAQ,EAAE,YAAY;gCACtB,MAAM,EAAE,MAAM;gCACd,OAAO,EACH,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO;oCAChC,MAAM;6BACb;4BACH,CAAC,CAAC,SAAS;qBAClB,CAAC,CAAA;gBACN,CAAC;gBAED,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAA;gBACzB,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAA;YACtC,CAAC,CAAA;YAED,IAAI,CAAC,MAAM,EAAE,KAAK,CACd,+CAA+C,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,EAC7E,IAAI,CAAC,MAAM,CACd,CAAA;YACD,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC;gBACnC,OAAO,EAAE,MAAM;gBACf,gBAAgB,EAAE,IAAI,CAAC,YAAY,CAAC,UAAU;gBAC9C,gBAAgB,EACZ,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU;gBAC1D,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,QAAQ,EAAE,IAAI,CAAC,gBAAgB;gBAC/B,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,oBAAoB;aACzD,CAAC,CAAA;YAEF,iEAAiE;YACjE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;YAC1C,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAA;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,oCAAoC,EAAE,KAAK,CAAC,CAAA;QACrE,CAAC;IACL,CAAC;IAED,0BAA0B,CAAC,mBAA4B;QACnD,IAAI,CAAC;YACD,IAAI,mBAAmB,EAAE,CAAC;gBACtB,0CAA0C;gBAC1C,yHAAyH;gBACzH,6DAA6D;gBAC7D,IAAI,CAAC,sBAAsB,GAAG,IAAI,MAAM,CACpC,IAAI,GAAG,CAAC,mBAAmB,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CACrD,CAAA;gBACD,IAAI,CAAC,sBAAsB,CAAC,SAAS;oBACjC,IAAI,CAAC,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBACjD,IAAI,CAAC,sBAAsB,CAAC,OAAO;oBAC/B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACzC,CAAC;iBAAM,CAAC;gBACJ,2DAA2D;gBAC3D,IAAI,CAAC,kBAAkB,EAAE,CAAA;YAC7B,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CACT,IAAI,GAAG,iDAAiD,EACxD,KAAK,CACR,CAAA;YACD,IAAI,CAAC,kBAAkB,EAAE,CAAA;QAC7B,CAAC;IACL,CAAC;IAED,kBAAkB;QACd,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,uBAAuB,CAAC,EAAE;gBAC7C,IAAI,EAAE,wBAAwB;aACjC,CAAC,CAAA;YACF,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;YACrC,IAAI,CAAC,sBAAsB,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,CAAA;YAC7C,IAAI,CAAC,sBAAsB,CAAC,SAAS;gBACjC,IAAI,CAAC,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACjD,IAAI,CAAC,sBAAsB,CAAC,OAAO,GAAG,CAAC,KAAK,EAAE,EAAE;gBAC5C,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,gCAAgC,EAAE,KAAK,CAAC,CAAA;YACjE,CAAC,CAAA;YACD,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,wCAAwC,CAAC,CAAA;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CACT,IAAI,GAAG,wDAAwD,EAC/D,KAAK,CACR,CAAA;QACL,CAAC;IACL,CAAC;IAED,iBAAiB,CAAC,KAAiB;QAC/B,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,mCAAmC,EAAE,KAAK,CAAC,CAAA;IACpE,CAAC;IAED,6BAA6B,CAAC,KAAyB;QACnD,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YACpC,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAA;YAEvC,6DAA6D;YAC7D,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,CAAA;YACnE,IAAI,CAAC,iBAAiB,CAAC,cAAc,EAAE,IAAI,CACvC,GAAG,CAAC,aAAa,CAAC,cAAc,IAAI,EAAE,CAAC,CAC1C,CAAA;YACD,IAAI,CAAC,iBAAiB,CAAC,UAAU,GAAG,aAAa,CAAC,UAAU,CAAA;YAC5D,IAAI,aAAa,CAAC,cAAc,EAAE,CAAC;gBAC/B,IAAI,CAAC,iBAAiB,CAAC,cAAc,GAAG;oBACpC,GAAG,EAAE,IAAI,CAAC,GAAG,CACT,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,GAAG,EACzC,aAAa,CAAC,cAAc,CAAC,GAAG,CACnC;oBACD,GAAG,EAAE,IAAI,CAAC,GAAG,CACT,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,GAAG,EACzC,aAAa,CAAC,cAAc,CAAC,GAAG,CACnC;iBACJ,CAAA;YACL,CAAC;YACD,kEAAkE;YAClE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,8BAA8B,EAAE,aAAa,CAAC,CAAA;YACjE,IAAI,CAAC,MAAM,EAAE,KAAK,CACd,6CAA6C,IAAI,CAAC,iBAAiB,CAAC,UAAU,EAAE,EAChF,IAAI,CAAC,iBAAiB,CACzB,CAAA;YACD,IAAI,CAAC,yBAAyB,CAAC,aAAa,CAAC,CAAA;QACjD,CAAC;IACL,CAAC;IAED,KAAK;QACD,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;QAC1C,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAA;QAC5D,IAAI,CAAC,WAAW,GAAG,CAAC,CAAA;QAEpB,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC/B,IAAI,CAAC,uBAAuB,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAA;QACpE,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI;QACN,IAAI,CAAC;YACD,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC;gBAC/B,IAAI,CAAC,uBAAuB,CAAC,IAAI,EAAE,CAAA;gBACnC,OAAO;oBACH,OAAO,EAAE,IAAI,YAAY,EAAE,EAAE,2CAA2C;oBACxE,cAAc,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE;wBAC5C,IAAI,EAAE,wBAAwB;qBACjC,CAAC;iBACL,CAAA;YACL,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,IAAI,YAAY,EAAE,EAAE,CAAA;QAC1C,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,OAAO,EAAE,CAAA;QAClB,CAAC;IACL,CAAC;IAEO,OAAO;QACX,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAA;QAC7B,CAAC;QACD,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACxB,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,CAAA;QACtC,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAA;QAC5B,CAAC;QACD,IAAI,CAAC,qBAAqB,EAAE,CAAA;IAChC,CAAC;IAED,0CAA0C;IAClC,KAAK,CAAC,oBAAoB;QAI9B,MAAM,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;QAC1C,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,+CAA+C,CAAC,CAAA;QAEnE,MAAM,CAAC,cAAc,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACpD,IAAI,CAAC,uBAAuB,EAAE;YAC9B,IAAI,CAAC,gBAAgB,EAAE;SAC1B,CAAC,CAAA;QAEF,IAAI,CAAC,MAAM,EAAE,KAAK,CACd,qDAAqD,WAAW,CAAC,GAAG,EAAE,GAAG,gBAAgB,IAAI,CAChG,CAAA;QACD,OAAO;YACH,OAAO,EACH,WAAW;gBACX,IAAI,YAAY,CAAC,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,MAAM,CAAC;YAC9D,cAAc,EAAE,cAAc;SACjC,CAAA;IACL,CAAC;IAED,6CAA6C;IACrC,uBAAuB;QAC3B,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;QACnC,IAAI,CAAC,MAAM,EAAE,KAAK,CACd,iBAAiB,sBAAsB,CAAC,yBAAyB,sCAAsC,CAC1G,CAAA;QAED,IAAI,CAAC,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAChC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,8CAA8C,CAAC,CAAA;YAClE,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;QACrC,CAAC;QAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC3B,IAAI,CAAC,uBAAwB,CAAC,MAAM,GAAG,GAAG,EAAE;gBACxC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE;oBACzC,IAAI,EAAE,wBAAwB;iBACjC,CAAC,CAAA;gBACF,IAAI,CAAC,MAAM,EAAE,KAAK,CACd,iBAAiB,sBAAsB,CAAC,yBAAyB,qCAAqC,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS,aAAa,IAAI,CAAC,IAAI,EAAE,CAC9J,CAAA;gBACD,OAAO,CAAC,IAAI,CAAC,CAAA;YACjB,CAAC,CAAA;YACD,IAAI,CAAC,uBAAwB,CAAC,IAAI,EAAE,CAAA;QACxC,CAAC,CAAC,CAAA;IACN,CAAC;IAED,sCAAsC;IAC9B,gBAAgB;QACpB,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;QACnC,IAAI,CAAC,MAAM,EAAE,KAAK,CACd,iBAAiB,sBAAsB,CAAC,kBAAkB,+BAA+B,CAC5F,CAAA;QAED,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,wCAAwC,CAAC,CAAA;YAC5D,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;QACrC,CAAC;QAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC3B,MAAM,SAAS,GAAG,CAAC,KAAwB,EAAE,EAAE;gBAC3C,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,cAAc,EAAE,CAAC;oBACxC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,mBAAmB,CAC3C,SAAS,EACT,SAAS,CACZ,CAAA;oBACD,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;oBAExD,IAAI,CAAC,cAAc,EAAE,CAAC;wBAClB,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,oCAAoC,CAAC,CAAA;wBACxD,OAAO,CAAC,SAAS,CAAC,CAAA;wBAClB,OAAM;oBACV,CAAC;oBAED,IAAI,IAAI,CAAC,cAAc,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACxC,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;wBACzC,mBAAmB,CAAC;4BAChB,MAAM,EAAE,cAAc,CAAC,MAAM;4BAC7B,QAAQ,EAAE,IAAI,CAAC,cAAc;4BAC7B,aAAa,EAAE,IAAI;4BACnB,MAAM,EAAE,IAAI,CAAC,MAAM;yBACtB,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE;4BACtB,IAAI,CAAC,MAAM,EAAE,KAAK,CACd,6CAA6C,WAAW,CAAC,GAAG,EAAE,GAAG,eAAe,IAAI,CACvF,CAAA;4BACD,IAAI,CAAC,MAAM,EAAE,KAAK,CACd,iBAAiB,sBAAsB,CAAC,kBAAkB,8BAA8B,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS,IAAI,CAC5H,CAAA;4BACD,OAAO,CAAC,SAAS,CAAC,CAAA;wBACtB,CAAC,CAAC,CAAA;oBACN,CAAC;yBAAM,CAAC;wBACJ,IAAI,CAAC,MAAM,EAAE,KAAK,CACd,iBAAiB,sBAAsB,CAAC,kBAAkB,8BAA8B,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS,IAAI,CAC5H,CAAA;wBACD,OAAO,CAAC,cAAc,CAAC,CAAA;oBAC3B,CAAC;gBACL,CAAC;YACL,CAAC,CAAA;YAED,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;YACjE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAA;QAC/D,CAAC,CAAC,CAAA;IACN,CAAC;IAED,KAAK;QACD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA,CAAC,kDAAkD;QAChG,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAA,CAAC,uDAAuD;QACvH,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAA;QAC5D,IAAI,CAAC,uBAAuB,EAAE,KAAK,EAAE,CAAA;IACzC,CAAC;IAED,qBAAqB;QACjB,mDAAmD;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,SAAS,EAAE,CAAA;QAClD,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;IAC3C,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,EACnB,YAAY,GAIf;QACG,IAAI,CAAC;YACD,wCAAwC;YACxC,MAAM,eAAe,GAAG,cAAc,CAAC;gBACnC,MAAM,EAAE,YAAY;gBACpB,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,UAAU;gBACxC,WAAW,EAAE,IAAI,CAAC,gBAAgB;gBAClC,QAAQ,EAAE,IAAI,CAAC,cAAc;aAChC,CAAC,CAAA;YAEF,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,eAAe,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA;YAC/D,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;YACrC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;YACjC,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAA;YAEhD,wBAAwB;YACxB,MAAM,WAAW,GACb,MAAM,IAAI,CAAC,YAAY,CAAC,eAAe,CAAC,WAAW,CAAC,CAAA;YAExD,iDAAiD;YACjD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,CAAA;YAC3D,YAAY,CAAC,MAAM,GAAG,WAAW,CAAA;YACjC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAA;YACnD,YAAY,CAAC,KAAK,EAAE,CAAA;YACpB,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,uBAAuB,EAAE,YAAY,CAAC,CAAA;YAEzD,WAAW;YACX,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAA;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,iCAAiC,EAAE,KAAK,CAAC,CAAA;QAClE,CAAC;IACL,CAAC;IAEO,uBAAuB,CAAC,EAAE,UAAU,EAA0B;QAClE,8BAA8B;QAC9B,MAAM,UAAU,GAAG,UAAU,GAAG,GAAG,CAAA,CAAC,kBAAkB;QACtD,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAC9C,CAAC,EACD,UAAU,EACV,UAAU,CACb,CAAA;QAED,mBAAmB;QACnB,MAAM,WAAW,GAAG,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC,CAAA;QACjD,MAAM,QAAQ,GAAG,WAAW,CAAC,iBAAiB,GAAG,CAAC,CAAA,CAAC,mCAAmC;QAEtF,OAAO;YACH,UAAU,EAAE,WAAW,CAAC,UAAU;YAClC,QAAQ;YACR,gBAAgB,EAAE,WAAW,CAAC,gBAAgB;SACjD,CAAA;IACL,CAAC;IAED,MAAM;QACF,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;QAC1C,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAA;QAC5D,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAA;QAC7D,IAAI,CAAC,uBAAuB,EAAE,MAAM,EAAE,CAAA;IAC1C,CAAC;IAEO,4BAA4B;QAChC,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,wBAAwB,CAAA;YACzC,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC3C,IAAI,CAAC,MAAM,EAAE,IAAI,CACb,gDAAgD,CACnD,CAAA;gBACD,OAAM;YACV,CAAC;YAED,IAAI,CAAC,uBAAuB,GAAG,IAAI,aAAa,CAC5C,IAAI,CAAC,MAAM,CAAC,WAAW,EACvB;gBACI,QAAQ;gBACR,kBAAkB,EACd,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,IAAI,MAAM;aACjD,CACJ,CAAA;YAED,IAAI,CAAC,uBAAuB,CAAC,eAAe,GAAG,CAAC,KAAK,EAAE,EAAE;gBACrD,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;oBACtB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;oBACtC,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAA;oBACtC,IAAI,CAAC,sBAAsB,GAAG,KAAK,CAAC,IAAI,CAAA;gBAC5C,CAAC;YACL,CAAC,CAAA;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,EAAE,KAAK,CACd,2CAA2C,EAC3C,KAAK,CACR,CAAA;QACL,CAAC;IACL,CAAC;CACJ","sourcesContent":["// src/WebRecorder.ts\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport { ConsoleLike, RecordingConfig } from './ExpoAudioStream.types'\nimport {\n EmitAudioAnalysisFunction,\n EmitAudioEventFunction,\n} from './ExpoAudioStream.web'\nimport { convertPCMToFloat32 } from './utils/convertPCMToFloat32'\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'\n\nconst STOP_PERFORMANCE_MARKS = {\n STOP_INITIATED: 'stopInitiated',\n COMPRESSED_RECORDING_STOP: 'compressedRecordingStop',\n AUDIO_WORKLET_STOP: 'audioWorkletStop',\n CLEANUP: 'cleanup',\n TOTAL_STOP_TIME: 'totalStopTime',\n} as const\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 = 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 packetCount: number = 0\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 readonly wavMimeType = 'audio/wav'\n\n constructor({\n audioContext,\n source,\n recordingConfig,\n audioWorkletUrl,\n emitAudioEventCallback,\n emitAudioAnalysisCallback,\n logger,\n }: {\n audioContext: AudioContext\n source: MediaStreamAudioSourceNode\n recordingConfig: RecordingConfig\n audioWorkletUrl: string\n emitAudioEventCallback: EmitAudioEventFunction\n emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n logger?: ConsoleLike\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.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 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 // Initialize compressed recording if enabled\n if (recordingConfig.compression?.enabled) {\n this.initializeCompressedRecorder()\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') return\n\n const pcmBufferFloat = event.data.recordedData\n if (!pcmBufferFloat) {\n this.logger?.warn('Received empty audio buffer', event)\n return\n }\n\n // Process data in smaller chunks and emit immediately\n const chunkSize = this.audioContext.sampleRate * 2 // Reduce to 2 seconds chunks\n const sampleRate =\n event.data.sampleRate ?? this.audioContext.sampleRate\n const duration = pcmBufferFloat.length / sampleRate\n\n // 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 = this.position + i / sampleRate\n\n // Process features if enabled\n if (\n this.config.enableProcessing &&\n this.featureExtractorWorker\n ) {\n this.featureExtractorWorker.postMessage(\n {\n command: 'process',\n channelData: chunk,\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 // 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 this.position += duration\n this.pendingCompressedChunk = null\n }\n\n this.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,\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 this.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 this.logger?.debug('features event segmentResult', segmentResult)\n this.logger?.debug(\n `features event audioAnalysisData duration=${this.audioAnalysisData.durationMs}`,\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 if (this.compressedMediaRecorder) {\n this.compressedMediaRecorder.start(this.config.interval ?? 1000)\n }\n }\n\n async stop(): Promise<{ pcmData: Float32Array; compressedBlob?: Blob }> {\n try {\n if (this.compressedMediaRecorder) {\n this.compressedMediaRecorder.stop()\n return {\n pcmData: new Float32Array(), // Return empty array since we're streaming\n compressedBlob: new Blob(this.compressedChunks, {\n type: 'audio/webm;codecs=opus',\n }),\n }\n }\n return { pcmData: new Float32Array() }\n } finally {\n this.cleanup()\n }\n }\n\n private cleanup() {\n if (this.audioContext) {\n this.audioContext.close()\n }\n if (this.audioWorkletNode) {\n this.audioWorkletNode.disconnect()\n }\n if (this.source) {\n this.source.disconnect()\n }\n this.stopMediaStreamTracks()\n }\n\n // Helper method to process recording stop\n private async processRecordingStop(): Promise<{\n pcmData: Float32Array\n compressedBlob?: Blob\n }> {\n const processStartTime = performance.now()\n this.logger?.debug('[Performance] Starting recording stop process')\n\n const [compressedData, workletData] = await Promise.all([\n this.stopCompressedRecording(),\n this.stopAudioWorklet(),\n ])\n\n this.logger?.debug(\n `[Performance] Recording stop process completed in ${performance.now() - processStartTime}ms`\n )\n return {\n pcmData:\n workletData ??\n new Float32Array(this.audioAnalysisData.dataPoints.length),\n compressedBlob: compressedData,\n }\n }\n\n // Helper method to stop compressed recording\n private stopCompressedRecording(): Promise<Blob | undefined> {\n const startTime = performance.now()\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.COMPRESSED_RECORDING_STOP}] Starting compressed recording stop`\n )\n\n if (!this.compressedMediaRecorder) {\n this.logger?.debug('[Performance] No compressed recorder to stop')\n return Promise.resolve(undefined)\n }\n\n return new Promise((resolve) => {\n this.compressedMediaRecorder!.onstop = () => {\n const blob = new Blob(this.compressedChunks, {\n type: 'audio/webm;codecs=opus',\n })\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.COMPRESSED_RECORDING_STOP}] Compressed recording stopped in ${performance.now() - startTime}ms, size: ${blob.size}`\n )\n resolve(blob)\n }\n this.compressedMediaRecorder!.stop()\n })\n }\n\n // Helper method to stop audio worklet\n private stopAudioWorklet(): Promise<Float32Array | undefined> {\n const startTime = performance.now()\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.AUDIO_WORKLET_STOP}] Starting audio worklet stop`\n )\n\n if (!this.audioWorkletNode) {\n this.logger?.debug('[Performance] No audio worklet to stop')\n return Promise.resolve(undefined)\n }\n\n return new Promise((resolve) => {\n const onMessage = (event: AudioWorkletEvent) => {\n if (event.data.command === 'recordedData') {\n this.audioWorkletNode?.port.removeEventListener(\n 'message',\n onMessage\n )\n const rawPCMDataFull = event.data.recordedData?.slice(0)\n\n if (!rawPCMDataFull) {\n this.logger?.debug('[Performance] No PCM data received')\n resolve(undefined)\n return\n }\n\n if (this.exportBitDepth !== this.bitDepth) {\n const conversionStart = performance.now()\n convertPCMToFloat32({\n buffer: rawPCMDataFull.buffer,\n bitDepth: this.exportBitDepth,\n skipWavHeader: true,\n logger: this.logger,\n }).then(({ pcmValues }) => {\n this.logger?.debug(\n `[Performance] PCM conversion completed in ${performance.now() - conversionStart}ms`\n )\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.AUDIO_WORKLET_STOP}] Audio worklet stopped in ${performance.now() - startTime}ms`\n )\n resolve(pcmValues)\n })\n } else {\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.AUDIO_WORKLET_STOP}] Audio worklet stopped in ${performance.now() - startTime}ms`\n )\n resolve(rawPCMDataFull)\n }\n }\n }\n\n this.audioWorkletNode.port.addEventListener('message', onMessage)\n this.audioWorkletNode.port.postMessage({ command: 'stop' })\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 this.compressedMediaRecorder?.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 // Create a WAV blob with proper headers\n const wavHeaderBuffer = writeWavHeader({\n buffer: recordedData,\n sampleRate: this.audioContext.sampleRate,\n numChannels: this.numberOfChannels,\n bitDepth: this.exportBitDepth,\n })\n\n const blob = new Blob([wavHeaderBuffer], { type: 'audio/wav' })\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 this.logger?.debug('Playing recorded data', recordedData)\n\n // Clean up\n URL.revokeObjectURL(url)\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 this.compressedMediaRecorder?.resume()\n }\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"]}
package/build/events.d.ts CHANGED
@@ -10,6 +10,12 @@ export interface AudioEventPayload {
10
10
  totalSize: number;
11
11
  mimeType: string;
12
12
  streamUuid: string;
13
+ compression?: {
14
+ data?: string | Blob;
15
+ position: number;
16
+ eventDataSize: number;
17
+ totalSize: number;
18
+ };
13
19
  }
14
20
  export declare function addAudioEventListener(listener: (event: AudioEventPayload) => Promise<void>): EventSubscription;
15
21
  export interface AudioAnalysisEvent extends AudioAnalysis {
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAE9E,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AAKnE,MAAM,WAAW,iBAAiB;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,qBAAqB,CACjC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GACtD,iBAAiB,CAEnB;AAGD,MAAM,WAAW,kBAAmB,SAAQ,aAAa;CAAG;AAE5D,wBAAgB,wBAAwB,CACpC,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,GACvD,iBAAiB,CAEnB"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAE9E,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AAMnE,MAAM,WAAW,iBAAiB;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE;QACV,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACpB,QAAQ,EAAE,MAAM,CAAA;QAChB,aAAa,EAAE,MAAM,CAAA;QACrB,SAAS,EAAE,MAAM,CAAA;KACpB,CAAA;CACJ;AAED,wBAAgB,qBAAqB,CACjC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GACtD,iBAAiB,CAEnB;AAGD,MAAM,WAAW,kBAAmB,SAAQ,aAAa;CAAG;AAE5D,wBAAgB,wBAAwB,CACpC,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,GACvD,iBAAiB,CAEnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAE3C,OAAO,EAAE,kBAAkB,EAA0B,MAAM,mBAAmB,CAAA;AAG9E,OAAO,qBAAqB,MAAM,yBAAyB,CAAA;AAE3D,MAAM,OAAO,GAAG,IAAI,kBAAkB,CAAC,qBAAqB,CAAC,CAAA;AAc7D,MAAM,UAAU,qBAAqB,CACjC,QAAqD;IAErD,OAAO,OAAO,CAAC,WAAW,CAAoB,WAAW,EAAE,QAAQ,CAAC,CAAA;AACxE,CAAC;AAKD,MAAM,UAAU,wBAAwB,CACpC,QAAsD;IAEtD,OAAO,OAAO,CAAC,WAAW,CAAqB,eAAe,EAAE,QAAQ,CAAC,CAAA;AAC7E,CAAC","sourcesContent":["// packages/expo-audio-stream/src/events.ts\n\nimport { LegacyEventEmitter, type EventSubscription } from 'expo-modules-core'\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport ExpoAudioStreamModule from './ExpoAudioStreamModule'\n\nconst emitter = new LegacyEventEmitter(ExpoAudioStreamModule)\n\nexport interface AudioEventPayload {\n encoded?: string\n buffer?: Float32Array\n fileUri: string\n lastEmittedSize: number\n position: number\n deltaSize: number\n totalSize: number\n mimeType: string\n streamUuid: string\n}\n\nexport function addAudioEventListener(\n listener: (event: AudioEventPayload) => Promise<void>\n): EventSubscription {\n return emitter.addListener<AudioEventPayload>('AudioData', listener)\n}\n\n// Only aliasing the AudioAnalysis type for the event payload\nexport interface AudioAnalysisEvent extends AudioAnalysis {}\n\nexport function addAudioAnalysisListener(\n listener: (event: AudioAnalysisEvent) => Promise<void>\n): EventSubscription {\n return emitter.addListener<AudioAnalysisEvent>('AudioAnalysis', listener)\n}\n"]}
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAE3C,OAAO,EAAE,kBAAkB,EAA0B,MAAM,mBAAmB,CAAA;AAG9E,OAAO,qBAAqB,MAAM,yBAAyB,CAAA;AAE3D,MAAM,OAAO,GAAG,IAAI,kBAAkB,CAAC,qBAAqB,CAAC,CAAA;AAqB7D,MAAM,UAAU,qBAAqB,CACjC,QAAqD;IAErD,OAAO,OAAO,CAAC,WAAW,CAAoB,WAAW,EAAE,QAAQ,CAAC,CAAA;AACxE,CAAC;AAKD,MAAM,UAAU,wBAAwB,CACpC,QAAsD;IAEtD,OAAO,OAAO,CAAC,WAAW,CAAqB,eAAe,EAAE,QAAQ,CAAC,CAAA;AAC7E,CAAC","sourcesContent":["// packages/expo-audio-stream/src/events.ts\n\nimport { LegacyEventEmitter, type EventSubscription } from 'expo-modules-core'\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport ExpoAudioStreamModule from './ExpoAudioStreamModule'\n\nconst emitter = new LegacyEventEmitter(ExpoAudioStreamModule)\n\n// Internal event payload from native module\nexport interface AudioEventPayload {\n encoded?: string\n buffer?: Float32Array\n fileUri: string\n lastEmittedSize: number\n position: number\n deltaSize: number\n totalSize: number\n mimeType: string\n streamUuid: string\n compression?: {\n data?: string | Blob // Base64 (native) or Float32Array (web) encoded compressed data chunk\n position: number\n eventDataSize: number\n totalSize: number\n }\n}\n\nexport function addAudioEventListener(\n listener: (event: AudioEventPayload) => Promise<void>\n): EventSubscription {\n return emitter.addListener<AudioEventPayload>('AudioData', listener)\n}\n\n// Only aliasing the AudioAnalysis type for the event payload\nexport interface AudioAnalysisEvent extends AudioAnalysis {}\n\nexport function addAudioAnalysisListener(\n listener: (event: AudioAnalysisEvent) => Promise<void>\n): EventSubscription {\n return emitter.addListener<AudioAnalysisEvent>('AudioAnalysis', listener)\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types';
2
- import { AudioRecording, ConsoleLike, RecordingConfig, StartRecordingResult } from './ExpoAudioStream.types';
2
+ import { AudioRecording, CompressionInfo, ConsoleLike, RecordingConfig, StartRecordingResult } from './ExpoAudioStream.types';
3
3
  export interface UseAudioRecorderProps {
4
4
  logger?: ConsoleLike;
5
5
  audioWorkletUrl?: string;
@@ -14,6 +14,7 @@ export interface UseAudioRecorderState {
14
14
  isPaused: boolean;
15
15
  durationMs: number;
16
16
  size: number;
17
+ compression?: CompressionInfo;
17
18
  analysisData?: AudioAnalysis;
18
19
  }
19
20
  export declare function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl, }?: UseAudioRecorderProps): UseAudioRecorderState;
@@ -1 +1 @@
1
- {"version":3,"file":"useAudioRecorder.d.ts","sourceRoot":"","sources":["../src/useAudioRecorder.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AACnE,OAAO,EAEH,cAAc,EAEd,WAAW,EACX,eAAe,EACf,oBAAoB,EACvB,MAAM,yBAAyB,CAAA;AAQhC,MAAM,WAAW,qBAAqB;IAClC,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED,MAAM,WAAW,qBAAqB;IAClC,cAAc,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAA;IACrE,aAAa,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAA;IAC5C,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACpC,WAAW,EAAE,OAAO,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,CAAC,EAAE,aAAa,CAAA;CAC/B;AAqFD,wBAAgB,gBAAgB,CAAC,EAC7B,MAAM,EACN,eAAe,EACf,mBAAmB,GACtB,GAAE,qBAA0B,GAAG,qBAAqB,CAyVpD"}
1
+ {"version":3,"file":"useAudioRecorder.d.ts","sourceRoot":"","sources":["../src/useAudioRecorder.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AACnE,OAAO,EAEH,cAAc,EAEd,eAAe,EACf,WAAW,EACX,eAAe,EACf,oBAAoB,EACvB,MAAM,yBAAyB,CAAA;AAQhC,MAAM,WAAW,qBAAqB;IAClC,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED,MAAM,WAAW,qBAAqB;IAClC,cAAc,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAA;IACrE,aAAa,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAA;IAC5C,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACpC,WAAW,EAAE,OAAO,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,eAAe,CAAA;IAC7B,YAAY,CAAC,EAAE,aAAa,CAAA;CAC/B;AAwGD,wBAAgB,gBAAgB,CAAC,EAC7B,MAAM,EACN,eAAe,EACf,mBAAmB,GACtB,GAAE,qBAA0B,GAAG,qBAAqB,CAiYpD"}