@siteed/expo-audio-stream 1.0.0 → 1.0.2

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 (85) hide show
  1. package/README.md +7 -18
  2. package/android/build.gradle +5 -0
  3. package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +120 -0
  4. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +34 -4
  5. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +635 -0
  6. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +194 -79
  7. package/android/src/main/java/net/siteed/audiostream/Constants.kt +1 -0
  8. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +48 -2
  9. package/android/src/main/java/net/siteed/audiostream/FFT.kt +44 -0
  10. package/android/src/main/java/net/siteed/audiostream/Features.kt +56 -0
  11. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +12 -0
  12. package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +56 -0
  13. package/app.plugin.js +1 -1
  14. package/build/AudioRecorder.provider.js +1 -1
  15. package/build/AudioRecorder.provider.js.map +1 -1
  16. package/build/ExpoAudioStream.native.d.ts +3 -0
  17. package/build/ExpoAudioStream.native.d.ts.map +1 -0
  18. package/build/ExpoAudioStream.native.js +6 -0
  19. package/build/ExpoAudioStream.native.js.map +1 -0
  20. package/build/ExpoAudioStream.types.d.ts +79 -6
  21. package/build/ExpoAudioStream.types.d.ts.map +1 -1
  22. package/build/ExpoAudioStream.types.js.map +1 -1
  23. package/build/ExpoAudioStream.web.d.ts +41 -0
  24. package/build/ExpoAudioStream.web.d.ts.map +1 -0
  25. package/build/ExpoAudioStream.web.js +184 -0
  26. package/build/ExpoAudioStream.web.js.map +1 -0
  27. package/build/ExpoAudioStreamModule.d.ts +2 -2
  28. package/build/ExpoAudioStreamModule.d.ts.map +1 -1
  29. package/build/ExpoAudioStreamModule.js +12 -3
  30. package/build/ExpoAudioStreamModule.js.map +1 -1
  31. package/build/WebRecorder.d.ts +47 -0
  32. package/build/WebRecorder.d.ts.map +1 -0
  33. package/build/WebRecorder.js +243 -0
  34. package/build/WebRecorder.js.map +1 -0
  35. package/build/index.d.ts +14 -5
  36. package/build/index.d.ts.map +1 -1
  37. package/build/index.js +106 -7
  38. package/build/index.js.map +1 -1
  39. package/build/inlineAudioWebWorker.d.ts +3 -0
  40. package/build/inlineAudioWebWorker.d.ts.map +1 -0
  41. package/build/inlineAudioWebWorker.js +340 -0
  42. package/build/inlineAudioWebWorker.js.map +1 -0
  43. package/build/useAudioRecording.d.ts +24 -9
  44. package/build/useAudioRecording.d.ts.map +1 -1
  45. package/build/useAudioRecording.js +107 -29
  46. package/build/useAudioRecording.js.map +1 -1
  47. package/build/utils.d.ts +31 -0
  48. package/build/utils.d.ts.map +1 -0
  49. package/build/utils.js +143 -0
  50. package/build/utils.js.map +1 -0
  51. package/expo-module.config.json +13 -4
  52. package/ios/AudioAnalysisData.swift +39 -0
  53. package/ios/AudioProcessingHelpers.swift +59 -0
  54. package/ios/AudioProcessor.swift +317 -0
  55. package/ios/AudioStreamError.swift +7 -0
  56. package/ios/AudioStreamManager.swift +204 -52
  57. package/ios/AudioStreamManagerDelegate.swift +4 -0
  58. package/ios/DataPoint.swift +41 -0
  59. package/ios/ExpoAudioStreamModule.swift +188 -6
  60. package/ios/Features.swift +44 -0
  61. package/ios/RecordingResult.swift +19 -0
  62. package/ios/RecordingSettings.swift +13 -0
  63. package/ios/WaveformExtractor.swift +105 -0
  64. package/package.json +9 -9
  65. package/plugin/tsconfig.json +13 -8
  66. package/publish.sh +8 -0
  67. package/src/AudioRecorder.provider.tsx +1 -1
  68. package/src/ExpoAudioStream.native.ts +6 -0
  69. package/src/ExpoAudioStream.types.ts +97 -11
  70. package/src/ExpoAudioStream.web.ts +228 -0
  71. package/src/ExpoAudioStreamModule.ts +17 -3
  72. package/src/WebRecorder.ts +364 -0
  73. package/src/index.ts +166 -20
  74. package/src/inlineAudioWebWorker.tsx +340 -0
  75. package/src/useAudioRecording.tsx +410 -0
  76. package/src/utils.ts +189 -0
  77. package/build/ExpoAudioStreamModule.web.d.ts +0 -37
  78. package/build/ExpoAudioStreamModule.web.d.ts.map +0 -1
  79. package/build/ExpoAudioStreamModule.web.js +0 -156
  80. package/build/ExpoAudioStreamModule.web.js.map +0 -1
  81. package/docs/demo.gif +0 -0
  82. package/release-it.js +0 -18
  83. package/src/ExpoAudioStreamModule.web.ts +0 -181
  84. package/src/useAudioRecording.ts +0 -268
  85. package/yarn-error.log +0 -7793
@@ -0,0 +1,340 @@
1
+ export const SimpleInlineProcessorScript = `
2
+ class RecorderProcessor extends AudioWorkletProcessor {
3
+ constructor() {
4
+ super();
5
+ this.recordedBuffers = []; // Float32Array
6
+ this.newRecBuffer = []; // Float32Array
7
+ this.exportIntervalSamples = 0;
8
+ this.samplesSinceLastExport = 0;
9
+ this.recordSampleRate = 44100; // To be overwritten
10
+ this.exportSampleRate = 44100; // To be overwritten
11
+ this.channels = 1; // Default to 1 channel (mono)
12
+ this.bitDepth = 32; // Default to 32-bit depth
13
+ this.isRecording = true;
14
+ this.port.onmessage = this.handleMessage.bind(this);
15
+ }
16
+
17
+ handleMessage(event) {
18
+ switch (event.data.command) {
19
+ case 'init':
20
+ this.recordSampleRate = event.data.recordSampleRate;
21
+ this.exportSampleRate = event.data.exportSampleRate || event.data.recordSampleRate;
22
+ this.exportIntervalSamples = this.recordSampleRate * (event.data.interval / 1000);
23
+ break;
24
+ case 'stop':
25
+ this.isRecording = false;
26
+ const fullRecordedData = this.getAllRecordedData();
27
+ this.port.postMessage({ command: 'recordedData', recordedData: fullRecordedData });
28
+ break;
29
+ }
30
+ }
31
+
32
+ process(inputs, outputs, parameters) {
33
+ if (!this.isRecording) return true;
34
+ const input = inputs[0];
35
+ if (input.length > 0) {
36
+ const newBuffer = new Float32Array(input[0]);
37
+ this.newRecBuffer.push(newBuffer);
38
+ this.recordedBuffers.push(newBuffer);
39
+ this.samplesSinceLastExport += newBuffer.length;
40
+
41
+ if (this.samplesSinceLastExport >= this.exportIntervalSamples) {
42
+ this.exportNewData();
43
+ this.samplesSinceLastExport = 0;
44
+ }
45
+ }
46
+ return true;
47
+ }
48
+
49
+ mergeBuffers(bufferArray, recLength) {
50
+ const result = new Float32Array(recLength);
51
+ let offset = 0;
52
+ for (let i = 0; i < bufferArray.length; i++) {
53
+ result.set(bufferArray[i], offset);
54
+ offset += bufferArray[i].length;
55
+ }
56
+ return result;
57
+ }
58
+
59
+ floatTo16BitPCM(output, offset, input) {
60
+ for (let i = 0; i < input.length; i++, offset += 2) {
61
+ const s = Math.max(-1, Math.min(1, input[i]));
62
+ output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
63
+ }
64
+ console.debug('Float to 16-bit PCM conversion complete. Output byte length:', offset);
65
+ }
66
+
67
+ floatTo32BitPCM(output, offset, input) {
68
+ for (let i = 0; i < input.length; i++, offset += 4) {
69
+ output.setFloat32(offset, input[i], true);
70
+ }
71
+ console.debug('Float to 32-bit PCM (no conversion) complete. Output byte length:', offset);
72
+ }
73
+
74
+ writeString(view, offset, string) {
75
+ for (let i = 0; i < string.length; i++) {
76
+ view.setUint8(offset + i, string.charCodeAt(i));
77
+ }
78
+ }
79
+
80
+ encodeWAV(samples, includeHeader = true) {
81
+ const sampleCount = samples.length;
82
+ const buffer = new ArrayBuffer((includeHeader ? 44 : 0) + sampleCount * 4);
83
+ const view = new DataView(buffer);
84
+
85
+ if (includeHeader) {
86
+ this.writeString(view, 0, 'RIFF');
87
+ view.setUint32(4, 36 + sampleCount * 4, true); // File size - 8 bytes
88
+ this.writeString(view, 8, 'WAVE');
89
+ this.writeString(view, 12, 'fmt ');
90
+ view.setUint32(16, 16, true); // PCM format
91
+ view.setUint16(20, 3, true); // Format code 3 for float
92
+ view.setUint16(22, 1, true); // Mono channel
93
+ view.setUint32(24, this.recordSampleRate, true); // Sample rate
94
+ view.setUint32(28, this.recordSampleRate * 4, true); // Byte rate
95
+ view.setUint16(32, 4, true); // Block align (4 bytes for 32-bit float)
96
+ view.setUint16(34, 32, true); // Bits per sample (32-bit float)
97
+ this.writeString(view, 36, 'data');
98
+ view.setUint32(40, sampleCount * 4, true); // Data chunk size
99
+ }
100
+
101
+ console.debug('Writing PCM samples to DataView. Offset:', includeHeader ? 44 : 0, 'Samples length:', sampleCount);
102
+ this.floatTo32BitPCM(view, includeHeader ? 44 : 0, samples);
103
+ // this.floatTo16BitPCM(view, includeHeader ? 44 : 0, samples);
104
+
105
+ console.debug('Encoded WAV DataView:', view);
106
+ console.debug('Encoded WAV length:', view.byteLength);
107
+
108
+ return view;
109
+ }
110
+
111
+
112
+ exportNewData() {
113
+ // Calculate the total length of the new recorded buffers
114
+ const length = this.newRecBuffer.reduce((acc, buffer) => acc + buffer.length, 0);
115
+
116
+ // Merge all new recorded buffers into a single buffer
117
+ const mergedBuffer = this.mergeBuffers(this.newRecBuffer, length);
118
+
119
+ // Encode the merged buffer into a WAV format
120
+ const encodedWav = this.encodeWAV(mergedBuffer, false);
121
+
122
+ // Clear the new recorded buffers after they have been processed
123
+ this.newRecBuffer.length = 0;
124
+
125
+ // Post the message to the main thread
126
+ // The first argument is the message data, containing the encoded WAV buffer
127
+ // The second argument is the transfer list, which transfers ownership of the ArrayBuffer
128
+ // to the main thread, avoiding the need to copy the buffer and improving performance
129
+ this.port.postMessage({ recordedData: encodedWav.buffer }, [encodedWav.buffer]);
130
+ }
131
+
132
+ getAllRecordedData() {
133
+ const length = this.recordedBuffers.reduce((acc, buffer) => acc + buffer.length, 0);
134
+ const mergedBuffer = this.mergeBuffers(this.recordedBuffers, length);
135
+
136
+
137
+ // Calculate the duration based on the sample count and sample rate
138
+ const sampleCount = mergedBuffer.length;
139
+ const mergedBufferDuration = sampleCount / this.recordSampleRate;
140
+
141
+ const encodedWav = this.encodeWAV(mergedBuffer, true);
142
+
143
+ // Calculate and log the duration for encodedWav.buffer based on sample count
144
+ const encodedWavBufferDuration = sampleCount / this.recordSampleRate;
145
+
146
+ this.recordedBuffers.length = 0; // Clear the buffers after extraction
147
+
148
+ // Returning both for testing, comment one of the returns based on your test
149
+ console.debug('mergedBuffer:', mergedBuffer);
150
+ console.debug('encodedWav.buffer:', encodedWav.buffer);
151
+
152
+ // Uncomment the appropriate return for testing
153
+ // return mergedBuffer; // This works when played
154
+ return encodedWav.buffer; // This doesn't work when played
155
+ }
156
+ }
157
+
158
+ registerProcessor('recorder-processor', RecorderProcessor);
159
+ `;
160
+ // Because we use expo and needs to include the worker script in the shared library, better inline it in the module.
161
+ export const InlineProcessorScrippt = `
162
+ class RecorderProcessor extends AudioWorkletProcessor {
163
+ constructor() {
164
+ super();
165
+ this.recLength = 0;
166
+ this.recBuffer = [];
167
+ this.headerSent = false;
168
+ this.newRecBuffer = [];
169
+ this.exportIntervalSamples = 0;
170
+ this.samplesSinceLastExport = 0;
171
+ this.recordSampleRate = 44100; // To be overwrited
172
+ this.exportSampleRate = 44100; // To be overwrited
173
+ this.isRecording = true;
174
+
175
+ this.port.onmessage = this.handleMessage.bind(this);
176
+ }
177
+
178
+ handleMessage(event) {
179
+ switch (event.data.command) {
180
+ case 'init':
181
+ this.recordSampleRate = event.data.recordSampleRate;
182
+ this.exportSampleRate = event.data.exportSampleRate || event.data.recordSampleRate;
183
+ this.exportIntervalSamples = this.recordSampleRate * (event.data.interval / 1000);
184
+ break;
185
+ case 'stop':
186
+ this.isRecording = false;
187
+ break;
188
+ case 'getRecordedData':
189
+ const recordedData = this.getRecordedData();
190
+ this.port.postMessage({ command: 'recordedData', recordedData });
191
+ break;
192
+ }
193
+ }
194
+
195
+ process(inputs) {
196
+ if (!this.isRecording) {
197
+ return true; // Exit early if not recording
198
+ }
199
+
200
+ if (inputs.length === 0 || inputs[0].length === 0) {
201
+ console.warn('RecorderProcessor -- No input received.');
202
+ return true; // Exit early if no input
203
+ }
204
+
205
+ const buffer = inputs[0];
206
+ if (!buffer) {
207
+ console.error('Input buffer is null.');
208
+ return true; // Exit early if buffer is null
209
+ }
210
+
211
+ try {
212
+ this.record(buffer);
213
+
214
+ // this.samplesSinceLastExport += buffer.length;
215
+ // if (this.samplesSinceLastExport >= this.exportIntervalSamples) {
216
+ // this.exportBuffer();
217
+ // this.samplesSinceLastExport = 0;
218
+ // }
219
+ } catch (error) {
220
+ console.error('Error during processing:', error);
221
+ }
222
+ return true;
223
+ }
224
+
225
+ record(inputBuffer) {
226
+ this.recBuffer.push(inputBuffer);
227
+ this.newRecBuffer.push(inputBuffer);
228
+ this.recLength += inputBuffer.length;
229
+ }
230
+
231
+ exportBuffer() {
232
+ const mergedBuffers = this.mergeBuffers(this.newRecBuffer, this.newRecBuffer.reduce((len, buf) => len + buf.length, 0));
233
+ console.log('Merged buffer length:', mergedBuffers.length); // Debug log
234
+
235
+ const downsampledBuffer = this.downsampleBuffer(mergedBuffers, this.exportSampleRate);
236
+ console.log('Downsampled buffer length:', downsampledBuffer.length); // Debug log
237
+
238
+ const encodedWav = downsampledBuffer;
239
+ // const encodedWav = this.encodeWAV(downsampledBuffer);
240
+ // console.log('Encoded WAV length:', encodedWav.byteLength); // Debug log
241
+
242
+ this.port.postMessage({ encodedWav, sampleRate: this.exportSampleRate });
243
+ this.newRecBuffer = []; // Clear the new data buffer after export
244
+ this.headerSent = true; // Indicate that the header has been sent
245
+ }
246
+
247
+ downsampleBuffer(buffer, exportSampleRate) {
248
+ console.log('Original buffer length:', buffer.length); // Debug log
249
+ console.log('Record sample rate:', this.recordSampleRate); // Debug log
250
+ console.log('Export sample rate:', exportSampleRate); // Debug log
251
+ if (exportSampleRate === this.recordSampleRate) {
252
+ return buffer;
253
+ }
254
+
255
+ const sampleRateRatio = this.recordSampleRate / exportSampleRate;
256
+ const newLength = Math.round(buffer.length / sampleRateRatio);
257
+ console.log('Sample rate ratio:', sampleRateRatio); // Debug log
258
+ console.log('New length after downsampling:', newLength); // Debug log
259
+
260
+ if (newLength <= 0) {
261
+ console.error('New length is zero or negative, returning empty buffer.'); // Debug log
262
+ return new Float32Array(0);
263
+ }
264
+ const result = new Float32Array(newLength);
265
+ let offsetResult = 0;
266
+ let offsetBuffer = 0;
267
+ while (offsetResult < result.length) {
268
+ const nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);
269
+ let accum = 0, count = 0;
270
+ for (let i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) {
271
+ accum += buffer[i];
272
+ count++;
273
+ }
274
+ result[offsetResult] = accum / count;
275
+ offsetResult++;
276
+ offsetBuffer = nextOffsetBuffer;
277
+ }
278
+ return result;
279
+ }
280
+
281
+ mergeBuffers(bufferArray, recLength) {
282
+ const result = new Float32Array(recLength);
283
+ let offset = 0;
284
+ for (let i = 0; i < bufferArray.length; i++) {
285
+ result.set(bufferArray[i], offset);
286
+ offset += bufferArray[i].length;
287
+ }
288
+ return result;
289
+ }
290
+
291
+ floatTo16BitPCM(output, offset, input) {
292
+ for (let i = 0; i < input.length; i++, offset += 2) {
293
+ const s = Math.max(-1, Math.min(1, input[i]));
294
+ output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
295
+ }
296
+ }
297
+
298
+ writeString(view, offset, string) {
299
+ for (let i = 0; i < string.length; i++) {
300
+ view.setUint8(offset + i, string.charCodeAt(i));
301
+ }
302
+ }
303
+
304
+ getRecordedData() {
305
+ const length = this.recBuffer.reduce((acc, buffer) => acc + buffer.length, 0);
306
+ const result = new Float32Array(length);
307
+ let offset = 0;
308
+ for (const buffer of this.recBuffer) {
309
+ result.set(buffer, offset);
310
+ offset += buffer.length;
311
+ }
312
+ this.recBuffer.length = 0; // Clear the buffers after extraction
313
+
314
+ return result;
315
+ }
316
+
317
+ encodeWAV(samples) {
318
+ const buffer = new ArrayBuffer(44 + samples.length * 2);
319
+ const view = new DataView(buffer);
320
+ this.writeString(view, 0, 'RIFF');
321
+ view.setUint32(4, 32 + samples.length * 2, true);
322
+ this.writeString(view, 8, 'WAVE');
323
+ this.writeString(view, 12, 'fmt ');
324
+ view.setUint32(16, 16, true);
325
+ view.setUint16(20, 1, true);
326
+ view.setUint16(22, 1, true);
327
+ view.setUint32(24, sampleRate, true);
328
+ view.setUint32(28, sampleRate * 2, true);
329
+ view.setUint16(32, 2, true);
330
+ view.setUint16(34, 16, true);
331
+ this.writeString(view, 36, 'data');
332
+ view.setUint32(40, samples.length * 2, true);
333
+ this.floatTo16BitPCM(view, 44, samples);
334
+ return view;
335
+ }
336
+ }
337
+
338
+ registerProcessor('recorder-processor', RecorderProcessor);
339
+ `;
340
+ //# sourceMappingURL=inlineAudioWebWorker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inlineAudioWebWorker.js","sourceRoot":"","sources":["../src/inlineAudioWebWorker.tsx"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,2BAA2B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8J1C,CAAC;AAEF,oHAAoH;AACpH,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkLrC,CAAC","sourcesContent":["export const SimpleInlineProcessorScript = `\nclass RecorderProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.recordedBuffers = []; // Float32Array\n this.newRecBuffer = []; // Float32Array\n this.exportIntervalSamples = 0;\n this.samplesSinceLastExport = 0;\n this.recordSampleRate = 44100; // To be overwritten\n this.exportSampleRate = 44100; // To be overwritten\n this.channels = 1; // Default to 1 channel (mono)\n this.bitDepth = 32; // Default to 32-bit depth\n this.isRecording = true;\n this.port.onmessage = this.handleMessage.bind(this);\n }\n\n handleMessage(event) {\n switch (event.data.command) {\n case 'init':\n this.recordSampleRate = event.data.recordSampleRate;\n this.exportSampleRate = event.data.exportSampleRate || event.data.recordSampleRate;\n this.exportIntervalSamples = this.recordSampleRate * (event.data.interval / 1000);\n break;\n case 'stop':\n this.isRecording = false;\n const fullRecordedData = this.getAllRecordedData();\n this.port.postMessage({ command: 'recordedData', recordedData: fullRecordedData });\n break;\n }\n }\n\n process(inputs, outputs, parameters) {\n if (!this.isRecording) return true;\n const input = inputs[0];\n if (input.length > 0) {\n const newBuffer = new Float32Array(input[0]);\n this.newRecBuffer.push(newBuffer);\n this.recordedBuffers.push(newBuffer);\n this.samplesSinceLastExport += newBuffer.length;\n\n if (this.samplesSinceLastExport >= this.exportIntervalSamples) {\n this.exportNewData();\n this.samplesSinceLastExport = 0;\n }\n }\n return true;\n }\n\n mergeBuffers(bufferArray, recLength) {\n const result = new Float32Array(recLength);\n let offset = 0;\n for (let i = 0; i < bufferArray.length; i++) {\n result.set(bufferArray[i], offset);\n offset += bufferArray[i].length;\n }\n return result;\n }\n\n floatTo16BitPCM(output, offset, input) {\n for (let i = 0; i < input.length; i++, offset += 2) {\n const s = Math.max(-1, Math.min(1, input[i]));\n output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);\n }\n console.debug('Float to 16-bit PCM conversion complete. Output byte length:', offset);\n }\n\n floatTo32BitPCM(output, offset, input) {\n for (let i = 0; i < input.length; i++, offset += 4) {\n output.setFloat32(offset, input[i], true);\n }\n console.debug('Float to 32-bit PCM (no conversion) complete. Output byte length:', offset);\n }\n\n writeString(view, offset, string) {\n for (let i = 0; i < string.length; i++) {\n view.setUint8(offset + i, string.charCodeAt(i));\n }\n }\n\n encodeWAV(samples, includeHeader = true) {\n const sampleCount = samples.length;\n const buffer = new ArrayBuffer((includeHeader ? 44 : 0) + sampleCount * 4);\n const view = new DataView(buffer);\n\n if (includeHeader) {\n this.writeString(view, 0, 'RIFF');\n view.setUint32(4, 36 + sampleCount * 4, true); // File size - 8 bytes\n this.writeString(view, 8, 'WAVE');\n this.writeString(view, 12, 'fmt ');\n view.setUint32(16, 16, true); // PCM format\n view.setUint16(20, 3, true); // Format code 3 for float\n view.setUint16(22, 1, true); // Mono channel\n view.setUint32(24, this.recordSampleRate, true); // Sample rate\n view.setUint32(28, this.recordSampleRate * 4, true); // Byte rate\n view.setUint16(32, 4, true); // Block align (4 bytes for 32-bit float)\n view.setUint16(34, 32, true); // Bits per sample (32-bit float)\n this.writeString(view, 36, 'data');\n view.setUint32(40, sampleCount * 4, true); // Data chunk size\n }\n\n console.debug('Writing PCM samples to DataView. Offset:', includeHeader ? 44 : 0, 'Samples length:', sampleCount);\n this.floatTo32BitPCM(view, includeHeader ? 44 : 0, samples);\n // this.floatTo16BitPCM(view, includeHeader ? 44 : 0, samples);\n\n console.debug('Encoded WAV DataView:', view);\n console.debug('Encoded WAV length:', view.byteLength);\n\n return view;\n }\n\n\n exportNewData() {\n // Calculate the total length of the new recorded buffers\n const length = this.newRecBuffer.reduce((acc, buffer) => acc + buffer.length, 0);\n\n // Merge all new recorded buffers into a single buffer\n const mergedBuffer = this.mergeBuffers(this.newRecBuffer, length);\n\n // Encode the merged buffer into a WAV format\n const encodedWav = this.encodeWAV(mergedBuffer, false);\n\n // Clear the new recorded buffers after they have been processed\n this.newRecBuffer.length = 0;\n\n // Post the message to the main thread\n // The first argument is the message data, containing the encoded WAV buffer\n // The second argument is the transfer list, which transfers ownership of the ArrayBuffer\n // to the main thread, avoiding the need to copy the buffer and improving performance\n this.port.postMessage({ recordedData: encodedWav.buffer }, [encodedWav.buffer]);\n }\n\n getAllRecordedData() {\n const length = this.recordedBuffers.reduce((acc, buffer) => acc + buffer.length, 0);\n const mergedBuffer = this.mergeBuffers(this.recordedBuffers, length);\n\n\n // Calculate the duration based on the sample count and sample rate\n const sampleCount = mergedBuffer.length;\n const mergedBufferDuration = sampleCount / this.recordSampleRate;\n\n const encodedWav = this.encodeWAV(mergedBuffer, true);\n\n // Calculate and log the duration for encodedWav.buffer based on sample count\n const encodedWavBufferDuration = sampleCount / this.recordSampleRate;\n\n this.recordedBuffers.length = 0; // Clear the buffers after extraction\n\n // Returning both for testing, comment one of the returns based on your test\n console.debug('mergedBuffer:', mergedBuffer);\n console.debug('encodedWav.buffer:', encodedWav.buffer);\n\n // Uncomment the appropriate return for testing\n // return mergedBuffer; // This works when played\n return encodedWav.buffer; // This doesn't work when played\n }\n}\n\nregisterProcessor('recorder-processor', RecorderProcessor);\n`;\n\n// Because we use expo and needs to include the worker script in the shared library, better inline it in the module.\nexport const InlineProcessorScrippt = `\nclass RecorderProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.recLength = 0;\n this.recBuffer = [];\n this.headerSent = false;\n this.newRecBuffer = [];\n this.exportIntervalSamples = 0;\n this.samplesSinceLastExport = 0;\n this.recordSampleRate = 44100; // To be overwrited\n this.exportSampleRate = 44100; // To be overwrited\n this.isRecording = true;\n\n this.port.onmessage = this.handleMessage.bind(this);\n }\n\n handleMessage(event) {\n switch (event.data.command) {\n case 'init':\n this.recordSampleRate = event.data.recordSampleRate;\n this.exportSampleRate = event.data.exportSampleRate || event.data.recordSampleRate;\n this.exportIntervalSamples = this.recordSampleRate * (event.data.interval / 1000);\n break;\n case 'stop':\n this.isRecording = false;\n break;\n case 'getRecordedData':\n const recordedData = this.getRecordedData();\n this.port.postMessage({ command: 'recordedData', recordedData });\n break;\n }\n }\n\n process(inputs) {\n if (!this.isRecording) {\n return true; // Exit early if not recording\n }\n\n if (inputs.length === 0 || inputs[0].length === 0) {\n console.warn('RecorderProcessor -- No input received.');\n return true; // Exit early if no input\n }\n\n const buffer = inputs[0];\n if (!buffer) {\n console.error('Input buffer is null.');\n return true; // Exit early if buffer is null\n }\n\n try {\n this.record(buffer);\n\n // this.samplesSinceLastExport += buffer.length;\n // if (this.samplesSinceLastExport >= this.exportIntervalSamples) {\n // this.exportBuffer();\n // this.samplesSinceLastExport = 0;\n // }\n } catch (error) {\n console.error('Error during processing:', error);\n }\n return true;\n }\n\n record(inputBuffer) {\n this.recBuffer.push(inputBuffer);\n this.newRecBuffer.push(inputBuffer);\n this.recLength += inputBuffer.length;\n }\n\n exportBuffer() {\n const mergedBuffers = this.mergeBuffers(this.newRecBuffer, this.newRecBuffer.reduce((len, buf) => len + buf.length, 0));\n console.log('Merged buffer length:', mergedBuffers.length); // Debug log\n\n const downsampledBuffer = this.downsampleBuffer(mergedBuffers, this.exportSampleRate);\n console.log('Downsampled buffer length:', downsampledBuffer.length); // Debug log\n\n const encodedWav = downsampledBuffer;\n // const encodedWav = this.encodeWAV(downsampledBuffer);\n // console.log('Encoded WAV length:', encodedWav.byteLength); // Debug log\n\n this.port.postMessage({ encodedWav, sampleRate: this.exportSampleRate });\n this.newRecBuffer = []; // Clear the new data buffer after export\n this.headerSent = true; // Indicate that the header has been sent\n }\n\n downsampleBuffer(buffer, exportSampleRate) {\n console.log('Original buffer length:', buffer.length); // Debug log\n console.log('Record sample rate:', this.recordSampleRate); // Debug log\n console.log('Export sample rate:', exportSampleRate); // Debug log\n if (exportSampleRate === this.recordSampleRate) {\n return buffer;\n }\n\n const sampleRateRatio = this.recordSampleRate / exportSampleRate;\n const newLength = Math.round(buffer.length / sampleRateRatio);\n console.log('Sample rate ratio:', sampleRateRatio); // Debug log\n console.log('New length after downsampling:', newLength); // Debug log\n \n if (newLength <= 0) {\n console.error('New length is zero or negative, returning empty buffer.'); // Debug log\n return new Float32Array(0);\n }\n const result = new Float32Array(newLength);\n let offsetResult = 0;\n let offsetBuffer = 0;\n while (offsetResult < result.length) {\n const nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);\n let accum = 0, count = 0;\n for (let i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) {\n accum += buffer[i];\n count++;\n }\n result[offsetResult] = accum / count;\n offsetResult++;\n offsetBuffer = nextOffsetBuffer;\n }\n return result;\n }\n\n mergeBuffers(bufferArray, recLength) {\n const result = new Float32Array(recLength);\n let offset = 0;\n for (let i = 0; i < bufferArray.length; i++) {\n result.set(bufferArray[i], offset);\n offset += bufferArray[i].length;\n }\n return result;\n }\n\n floatTo16BitPCM(output, offset, input) {\n for (let i = 0; i < input.length; i++, offset += 2) {\n const s = Math.max(-1, Math.min(1, input[i]));\n output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);\n }\n }\n\n writeString(view, offset, string) {\n for (let i = 0; i < string.length; i++) {\n view.setUint8(offset + i, string.charCodeAt(i));\n }\n }\n\n getRecordedData() {\n const length = this.recBuffer.reduce((acc, buffer) => acc + buffer.length, 0);\n const result = new Float32Array(length);\n let offset = 0;\n for (const buffer of this.recBuffer) {\n result.set(buffer, offset);\n offset += buffer.length;\n }\n this.recBuffer.length = 0; // Clear the buffers after extraction\n\n return result;\n }\n\n encodeWAV(samples) {\n const buffer = new ArrayBuffer(44 + samples.length * 2);\n const view = new DataView(buffer);\n this.writeString(view, 0, 'RIFF');\n view.setUint32(4, 32 + samples.length * 2, true);\n this.writeString(view, 8, 'WAVE');\n this.writeString(view, 12, 'fmt ');\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, 1, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * 2, true);\n view.setUint16(32, 2, true);\n view.setUint16(34, 16, true);\n this.writeString(view, 36, 'data');\n view.setUint32(40, samples.length * 2, true);\n this.floatTo16BitPCM(view, 44, samples);\n return view;\n }\n}\n\nregisterProcessor('recorder-processor', RecorderProcessor);\n`;\n"]}
@@ -1,13 +1,25 @@
1
- import { AudioStreamResult, RecordingConfig, StartAudioStreamResult } from "./ExpoAudioStream.types";
2
- export interface AudioDataEvent {
3
- data: string | Blob;
4
- position: number;
5
- fileUri: string;
6
- eventDataSize: number;
7
- totalSize: number;
1
+ import { AudioAnalysisData, AudioFeaturesOptions, AudioStreamResult, RecordingConfig, StartAudioStreamResult } from "./ExpoAudioStream.types";
2
+ import { WavFileInfo } from "./utils";
3
+ export interface ExtractMetadataProps {
4
+ fileUri?: string;
5
+ wavMetadata?: WavFileInfo;
6
+ arrayBuffer?: ArrayBuffer;
7
+ bitDepth?: number;
8
+ skipWavHeader?: boolean;
9
+ durationMs?: number;
10
+ sampleRate?: number;
11
+ numberOfChannels?: number;
12
+ algorithm?: "peak" | "rms";
13
+ position?: number;
14
+ length?: number;
15
+ pointsPerSecond?: number;
16
+ features?: AudioFeaturesOptions;
17
+ featuresExtratorUrl?: string;
8
18
  }
9
19
  export interface UseAudioRecorderProps {
10
20
  debug?: boolean;
21
+ audioWorkletUrl?: string;
22
+ featuresExtratorUrl?: string;
11
23
  }
12
24
  export interface UseAudioRecorderState {
13
25
  startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;
@@ -16,8 +28,11 @@ export interface UseAudioRecorderState {
16
28
  resumeRecording: () => void;
17
29
  isRecording: boolean;
18
30
  isPaused: boolean;
19
- duration: number;
31
+ durationMs: number;
20
32
  size: number;
33
+ analysisData?: AudioAnalysisData;
34
+ audioWorkletUrl?: string;
35
+ featuresExtratorUrl?: string;
21
36
  }
22
- export declare function useAudioRecorder({ debug, }?: UseAudioRecorderProps): UseAudioRecorderState;
37
+ export declare function useAudioRecorder({ debug, audioWorkletUrl, featuresExtratorUrl, }?: UseAudioRecorderProps): UseAudioRecorderState;
23
38
  //# sourceMappingURL=useAudioRecording.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAudioRecording.d.ts","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,iBAAiB,EAEjB,eAAe,EACf,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AAGjC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AACD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACxE,aAAa,EAAE,MAAM,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACvD,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AA4CD,wBAAgB,gBAAgB,CAAC,EAC/B,KAAa,GACd,GAAE,qBAA0B,GAAG,qBAAqB,CA4LpD"}
1
+ {"version":3,"file":"useAudioRecording.d.ts","sourceRoot":"","sources":["../src/useAudioRecording.tsx"],"names":[],"mappings":"AAKA,OAAO,EACL,iBAAiB,EAGjB,oBAAoB,EACpB,iBAAiB,EAEjB,eAAe,EACf,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACxE,aAAa,EAAE,MAAM,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACvD,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAkED,wBAAgB,gBAAgB,CAAC,EAC/B,KAAa,EACb,eAAe,EACf,mBAAmB,GACpB,GAAE,qBAA0B,GAAG,qBAAqB,CA8RpD"}
@@ -1,7 +1,21 @@
1
+ // src/useAudioRecording.ts
1
2
  import { Platform } from "expo-modules-core";
2
3
  import { useCallback, useEffect, useReducer, useRef } from "react";
3
- import { addAudioEventListener } from ".";
4
+ import { addAudioAnalysisListener, addAudioEventListener } from ".";
4
5
  import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
6
+ const defaultAnalysis = {
7
+ pointsPerSecond: 20,
8
+ bitDepth: 32,
9
+ numberOfChannels: 1,
10
+ durationMs: 0,
11
+ sampleRate: 44100,
12
+ samples: 0,
13
+ dataPoints: [],
14
+ amplitudeRange: {
15
+ min: Number.POSITIVE_INFINITY,
16
+ max: Number.NEGATIVE_INFINITY,
17
+ },
18
+ };
5
19
  function recorderReducer(state, action) {
6
20
  switch (action.type) {
7
21
  case "START":
@@ -9,8 +23,9 @@ function recorderReducer(state, action) {
9
23
  ...state,
10
24
  isRecording: true,
11
25
  isPaused: false,
12
- duration: 0,
26
+ durationMs: 0,
13
27
  size: 0,
28
+ analysisData: defaultAnalysis, // Reset analysis data
14
29
  };
15
30
  case "STOP":
16
31
  return { ...state, isRecording: false, isPaused: false };
@@ -21,21 +36,33 @@ function recorderReducer(state, action) {
21
36
  case "UPDATE_STATUS":
22
37
  return {
23
38
  ...state,
24
- duration: action.payload.duration,
39
+ durationMs: action.payload.durationMs,
25
40
  size: action.payload.size,
26
41
  };
42
+ case "UPDATE_ANALYSIS":
43
+ return {
44
+ ...state,
45
+ analysisData: action.payload,
46
+ };
27
47
  default:
28
48
  return state;
29
49
  }
30
50
  }
31
51
  const TAG = "[ useAudioRecorder ] ";
32
- export function useAudioRecorder({ debug = false, } = {}) {
52
+ export function useAudioRecorder({ debug = false, audioWorkletUrl, featuresExtratorUrl, } = {}) {
33
53
  const [state, dispatch] = useReducer(recorderReducer, {
34
54
  isRecording: false,
35
55
  isPaused: false,
36
- duration: 0,
56
+ durationMs: 0,
37
57
  size: 0,
58
+ analysisData: undefined,
38
59
  });
60
+ const analysisListenerRef = useRef(null);
61
+ const analysisRef = useRef({ ...defaultAnalysis });
62
+ // Instantiate the module for web with URLs
63
+ const ExpoAudioStream = Platform.OS === "web"
64
+ ? ExpoAudioStreamModule({ audioWorkletUrl, featuresExtratorUrl })
65
+ : ExpoAudioStreamModule;
39
66
  const onAudioStreamRef = useRef(null);
40
67
  const logDebug = useCallback((message, data) => {
41
68
  if (debug) {
@@ -47,9 +74,45 @@ export function useAudioRecorder({ debug = false, } = {}) {
47
74
  }
48
75
  }
49
76
  }, [debug]);
77
+ const handleAudioAnalysis = useCallback(async (analysis, visualizationDuration) => {
78
+ const savedAnalysisData = analysisRef.current || { ...defaultAnalysis };
79
+ const maxDuration = visualizationDuration;
80
+ logDebug(`[handleAudioAnalysis] Received audio analysis: maxDuration=${maxDuration} analysis.dataPoints=${analysis.dataPoints.length} analysisData.dataPoints=${savedAnalysisData.dataPoints.length}`, analysis);
81
+ // Combine data points
82
+ const combinedDataPoints = [
83
+ ...savedAnalysisData.dataPoints,
84
+ ...analysis.dataPoints,
85
+ ];
86
+ // Calculate the new duration
87
+ const pointsPerSecond = analysis.pointsPerSecond || savedAnalysisData.pointsPerSecond;
88
+ const maxDataPoints = (pointsPerSecond * visualizationDuration) / 1000;
89
+ logDebug(`[handleAudioAnalysis] Combined data points before trimming: pointsPerSecond=${pointsPerSecond} visualizationDuration=${visualizationDuration} combinedDataPointsLength=${combinedDataPoints.length} vs maxDataPoints=${maxDataPoints}`);
90
+ // Trim data points to keep within the maximum number of data points
91
+ if (combinedDataPoints.length > maxDataPoints) {
92
+ combinedDataPoints.splice(0, combinedDataPoints.length - maxDataPoints);
93
+ }
94
+ savedAnalysisData.dataPoints = combinedDataPoints;
95
+ savedAnalysisData.bitDepth =
96
+ analysis.bitDepth || savedAnalysisData.bitDepth;
97
+ savedAnalysisData.durationMs =
98
+ combinedDataPoints.length * (1000 / pointsPerSecond);
99
+ // Update amplitude range
100
+ const newMin = Math.min(savedAnalysisData.amplitudeRange.min, analysis.amplitudeRange.min);
101
+ const newMax = Math.max(savedAnalysisData.amplitudeRange.max, analysis.amplitudeRange.max);
102
+ savedAnalysisData.amplitudeRange = {
103
+ min: newMin,
104
+ max: newMax,
105
+ };
106
+ logDebug(`[handleAudioAnalysis] Updated analysis data: durationMs=${savedAnalysisData.durationMs}`, savedAnalysisData);
107
+ // Update the ref
108
+ analysisRef.current = savedAnalysisData;
109
+ // Dispatch the updated analysis data to state to trigger re-render
110
+ // need to use spread operator otherwise it doesnt trigger update.
111
+ dispatch({ type: "UPDATE_ANALYSIS", payload: { ...savedAnalysisData } });
112
+ }, [logDebug]);
50
113
  const handleAudioEvent = useCallback(async (eventData) => {
51
114
  const { fileUri, deltaSize, totalSize, lastEmittedSize, position, streamUuid, encoded, mimeType, buffer, } = eventData;
52
- logDebug(`useAudioRecorder] Received audio event:`, {
115
+ logDebug(`[handleAudioEvent] Received audio event:`, {
53
116
  fileUri,
54
117
  deltaSize,
55
118
  totalSize,
@@ -100,19 +163,14 @@ export function useAudioRecorder({ debug = false, } = {}) {
100
163
  logDebug(`${TAG} Not recording, exiting status check.`);
101
164
  return;
102
165
  }
103
- const status = ExpoAudioStreamModule.status();
166
+ const status = ExpoAudioStream.status();
104
167
  if (debug) {
105
168
  logDebug(`${TAG} Status:`, status);
106
169
  }
107
- if (!status.isRecording) {
108
- dispatch({ type: "STOP" });
109
- }
110
- else {
111
- dispatch({
112
- type: "UPDATE_STATUS",
113
- payload: { duration: status.duration, size: status.size },
114
- });
115
- }
170
+ dispatch({
171
+ type: "UPDATE_STATUS",
172
+ payload: { durationMs: status.durationMs, size: status.size },
173
+ });
116
174
  }
117
175
  catch (error) {
118
176
  console.error(`${TAG} Error getting status:`, error);
@@ -130,20 +188,23 @@ export function useAudioRecorder({ debug = false, } = {}) {
130
188
  };
131
189
  }, [checkStatus, state.isRecording]);
132
190
  useEffect(() => {
133
- logDebug(`${TAG} Registering audio event listener`);
134
- const subscribe = addAudioEventListener(handleAudioEvent);
135
- logDebug(`${TAG} Subscribed to audio event listener`, subscribe);
191
+ logDebug(`Registering audio event listener`);
192
+ const subscribeAudio = addAudioEventListener(handleAudioEvent);
193
+ logDebug(`Subscribed to audio event listener and analysis listener`, {
194
+ subscribeAudio,
195
+ });
136
196
  return () => {
137
- logDebug(`${TAG} Removing audio event listener`);
138
- subscribe.remove();
197
+ logDebug(`Removing audio event listener`);
198
+ subscribeAudio.remove();
139
199
  };
140
- }, [handleAudioEvent, logDebug]);
200
+ }, [handleAudioEvent, handleAudioAnalysis, logDebug]);
141
201
  const startRecording = useCallback(async (recordingOptions) => {
142
202
  if (debug) {
143
- logDebug(`${TAG} start recoding`, recordingOptions);
203
+ logDebug(`start recoding`, recordingOptions);
144
204
  }
145
- // remove onAudioStream from recordingOptions
205
+ analysisRef.current = { ...defaultAnalysis }; // Reset analysis data
146
206
  const { onAudioStream, ...options } = recordingOptions;
207
+ const { maxRecentDataDuration = 10000, enableProcessing } = options;
147
208
  if (typeof onAudioStream === "function") {
148
209
  onAudioStreamRef.current = onAudioStream;
149
210
  }
@@ -151,13 +212,29 @@ export function useAudioRecorder({ debug = false, } = {}) {
151
212
  console.warn(`${TAG} onAudioStream is not a function`, onAudioStream);
152
213
  onAudioStreamRef.current = null;
153
214
  }
154
- const startResult = await ExpoAudioStreamModule.startRecording(options);
215
+ const startResult = await ExpoAudioStream.startRecording(options);
155
216
  dispatch({ type: "START" });
217
+ if (enableProcessing) {
218
+ logDebug(`Enabling audio analysis listener`);
219
+ const listener = addAudioAnalysisListener(async (analysisData) => {
220
+ try {
221
+ await handleAudioAnalysis(analysisData, maxRecentDataDuration);
222
+ }
223
+ catch (error) {
224
+ console.warn(`${TAG} Error processing audio analysis:`, error);
225
+ }
226
+ });
227
+ analysisListenerRef.current = listener;
228
+ }
156
229
  return startResult;
157
230
  }, [logDebug]);
158
231
  const stopRecording = useCallback(async () => {
159
232
  logDebug(`${TAG} stoping recording`);
160
- const stopResult = await ExpoAudioStreamModule.stopRecording();
233
+ if (analysisListenerRef.current) {
234
+ analysisListenerRef.current.remove();
235
+ analysisListenerRef.current = null;
236
+ }
237
+ const stopResult = await ExpoAudioStream.stopRecording();
161
238
  onAudioStreamRef.current = null;
162
239
  logDebug(`${TAG} recording stopped`, stopResult);
163
240
  dispatch({ type: "STOP" });
@@ -165,13 +242,13 @@ export function useAudioRecorder({ debug = false, } = {}) {
165
242
  }, [logDebug]);
166
243
  const pauseRecording = useCallback(async () => {
167
244
  logDebug(`${TAG} pause recording`);
168
- const pauseResult = await ExpoAudioStreamModule.pauseRecording();
245
+ const pauseResult = await ExpoAudioStream.pauseRecording();
169
246
  dispatch({ type: "PAUSE" });
170
247
  return pauseResult;
171
248
  }, [logDebug]);
172
249
  const resumeRecording = useCallback(async () => {
173
250
  logDebug(`${TAG} resume recording`);
174
- const resumeResult = await ExpoAudioStreamModule.resumeRecording();
251
+ const resumeResult = await ExpoAudioStream.resumeRecording();
175
252
  dispatch({ type: "RESUME" });
176
253
  return resumeResult;
177
254
  }, [logDebug]);
@@ -182,8 +259,9 @@ export function useAudioRecorder({ debug = false, } = {}) {
182
259
  resumeRecording,
183
260
  isPaused: state.isPaused,
184
261
  isRecording: state.isRecording,
185
- duration: state.duration,
262
+ durationMs: state.durationMs,
186
263
  size: state.size,
264
+ analysisData: state.analysisData,
187
265
  };
188
266
  }
189
267
  //# sourceMappingURL=useAudioRecording.js.map