@siteed/expo-audio-stream 1.5.1 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -24
- package/app.plugin.js +1 -0
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts +74 -0
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -0
- package/build/AudioAnalysis/AudioAnalysis.types.js +3 -0
- package/build/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts +22 -0
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -0
- package/build/AudioAnalysis/extractAudioAnalysis.js +86 -0
- package/build/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
- package/build/AudioAnalysis/extractWaveform.d.ts +8 -0
- package/build/AudioAnalysis/extractWaveform.d.ts.map +1 -0
- package/build/AudioAnalysis/extractWaveform.js +11 -0
- package/build/AudioAnalysis/extractWaveform.js.map +1 -0
- package/build/AudioRecorder.provider.d.ts +11 -0
- package/build/AudioRecorder.provider.d.ts.map +1 -0
- package/build/AudioRecorder.provider.js +36 -0
- package/build/AudioRecorder.provider.js.map +1 -0
- package/build/ExpoAudioStream.native.d.ts +3 -0
- package/build/ExpoAudioStream.native.d.ts.map +1 -0
- package/build/ExpoAudioStream.native.js +6 -0
- package/build/ExpoAudioStream.native.js.map +1 -0
- package/build/ExpoAudioStream.types.d.ts +127 -0
- package/build/ExpoAudioStream.types.d.ts.map +1 -0
- package/build/ExpoAudioStream.types.js +2 -0
- package/build/ExpoAudioStream.types.js.map +1 -0
- package/build/ExpoAudioStream.web.d.ts +44 -0
- package/build/ExpoAudioStream.web.d.ts.map +1 -0
- package/build/ExpoAudioStream.web.js +206 -0
- package/build/ExpoAudioStream.web.js.map +1 -0
- package/build/ExpoAudioStreamModule.d.ts +3 -0
- package/build/ExpoAudioStreamModule.d.ts.map +1 -0
- package/build/ExpoAudioStreamModule.js +35 -0
- package/build/ExpoAudioStreamModule.js.map +1 -0
- package/build/WebRecorder.web.d.ts +54 -0
- package/build/WebRecorder.web.d.ts.map +1 -0
- package/build/WebRecorder.web.js +336 -0
- package/build/WebRecorder.web.js.map +1 -0
- package/build/constants.d.ts +11 -0
- package/build/constants.d.ts.map +1 -0
- package/build/constants.js +14 -0
- package/build/constants.js.map +1 -0
- package/build/events.d.ts +18 -0
- package/build/events.d.ts.map +1 -0
- package/build/events.js +11 -0
- package/build/events.js.map +1 -0
- package/build/index.d.ts +11 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js.map +1 -0
- package/build/useAudioRecorder.d.ts +20 -0
- package/build/useAudioRecorder.d.ts.map +1 -0
- package/build/useAudioRecorder.js +311 -0
- package/build/useAudioRecorder.js.map +1 -0
- package/build/utils/BlobFix.d.ts +9 -0
- package/build/utils/BlobFix.d.ts.map +1 -0
- package/build/utils/BlobFix.js +498 -0
- package/build/utils/BlobFix.js.map +1 -0
- package/build/utils/concatenateBuffers.d.ts +8 -0
- package/build/utils/concatenateBuffers.d.ts.map +1 -0
- package/build/utils/concatenateBuffers.js +21 -0
- package/build/utils/concatenateBuffers.js.map +1 -0
- package/build/utils/convertPCMToFloat32.d.ts +13 -0
- package/build/utils/convertPCMToFloat32.d.ts.map +1 -0
- package/build/utils/convertPCMToFloat32.js +120 -0
- package/build/utils/convertPCMToFloat32.js.map +1 -0
- package/build/utils/encodingToBitDepth.d.ts +5 -0
- package/build/utils/encodingToBitDepth.d.ts.map +1 -0
- package/build/utils/encodingToBitDepth.js +13 -0
- package/build/utils/encodingToBitDepth.js.map +1 -0
- package/build/utils/getWavFileInfo.d.ts +26 -0
- package/build/utils/getWavFileInfo.d.ts.map +1 -0
- package/build/utils/getWavFileInfo.js +92 -0
- package/build/utils/getWavFileInfo.js.map +1 -0
- package/build/utils/writeWavHeader.d.ts +49 -0
- package/build/utils/writeWavHeader.d.ts.map +1 -0
- package/build/utils/writeWavHeader.js +91 -0
- package/build/utils/writeWavHeader.js.map +1 -0
- package/build/workers/InlineFeaturesExtractor.web.d.ts +2 -0
- package/build/workers/InlineFeaturesExtractor.web.d.ts.map +1 -0
- package/build/workers/InlineFeaturesExtractor.web.js +311 -0
- package/build/workers/InlineFeaturesExtractor.web.js.map +1 -0
- package/build/workers/inlineAudioWebWorker.web.d.ts +2 -0
- package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -0
- package/build/workers/inlineAudioWebWorker.web.js +251 -0
- package/build/workers/inlineAudioWebWorker.web.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/package.json +7 -1
- package/plugin/build/index.d.ts +3 -0
- package/plugin/build/index.js +132 -0
- package/plugin/src/index.ts +176 -0
- package/plugin/tsconfig.json +10 -0
- package/plugin/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// src/WebRecorder.ts
|
|
2
|
+
import { convertPCMToFloat32 } from './utils/convertPCMToFloat32';
|
|
3
|
+
import { encodingToBitDepth } from './utils/encodingToBitDepth';
|
|
4
|
+
import { writeWavHeader } from './utils/writeWavHeader';
|
|
5
|
+
import { InlineFeaturesExtractor } from './workers/InlineFeaturesExtractor.web';
|
|
6
|
+
import { InlineAudioWebWorker } from './workers/inlineAudioWebWorker.web';
|
|
7
|
+
const DEFAULT_WEB_BITDEPTH = 32;
|
|
8
|
+
const DEFAULT_WEB_POINTS_PER_SECOND = 10;
|
|
9
|
+
const DEFAULT_WEB_INTERVAL = 500;
|
|
10
|
+
const DEFAULT_WEB_NUMBER_OF_CHANNELS = 1;
|
|
11
|
+
const DEFAULT_ALGORITHM = 'rms';
|
|
12
|
+
const TAG = 'WebRecorder';
|
|
13
|
+
export class WebRecorder {
|
|
14
|
+
audioContext;
|
|
15
|
+
audioWorkletNode;
|
|
16
|
+
featureExtractorWorker;
|
|
17
|
+
source;
|
|
18
|
+
audioWorkletUrl;
|
|
19
|
+
emitAudioEventCallback;
|
|
20
|
+
emitAudioAnalysisCallback;
|
|
21
|
+
config;
|
|
22
|
+
position; // Track the cumulative position
|
|
23
|
+
numberOfChannels; // Number of audio channels
|
|
24
|
+
bitDepth; // Bit depth of the audio
|
|
25
|
+
exportBitDepth; // Bit depth of the audio
|
|
26
|
+
audioBuffer; // Single buffer to store the audio data
|
|
27
|
+
audioBufferSize; // Keep track of the buffer size
|
|
28
|
+
audioAnalysisData; // Keep updating the full audio analysis data with latest events
|
|
29
|
+
packetCount = 0;
|
|
30
|
+
logger;
|
|
31
|
+
constructor({ audioContext, source, recordingConfig, audioWorkletUrl, emitAudioEventCallback, emitAudioAnalysisCallback, logger, }) {
|
|
32
|
+
this.audioContext = audioContext;
|
|
33
|
+
this.source = source;
|
|
34
|
+
this.audioWorkletUrl = audioWorkletUrl;
|
|
35
|
+
this.emitAudioEventCallback = emitAudioEventCallback;
|
|
36
|
+
this.emitAudioAnalysisCallback = emitAudioAnalysisCallback;
|
|
37
|
+
this.config = recordingConfig;
|
|
38
|
+
this.position = 0;
|
|
39
|
+
this.logger = logger;
|
|
40
|
+
const audioContextFormat = this.checkAudioContextFormat({
|
|
41
|
+
sampleRate: this.audioContext.sampleRate,
|
|
42
|
+
});
|
|
43
|
+
this.logger?.debug('Initialized WebRecorder with config:', {
|
|
44
|
+
sampleRate: audioContextFormat.sampleRate,
|
|
45
|
+
bitDepth: audioContextFormat.bitDepth,
|
|
46
|
+
numberOfChannels: audioContextFormat.numberOfChannels,
|
|
47
|
+
});
|
|
48
|
+
this.bitDepth = audioContextFormat.bitDepth;
|
|
49
|
+
this.numberOfChannels =
|
|
50
|
+
audioContextFormat.numberOfChannels ||
|
|
51
|
+
DEFAULT_WEB_NUMBER_OF_CHANNELS; // Default to 1 if not available
|
|
52
|
+
this.exportBitDepth =
|
|
53
|
+
encodingToBitDepth({
|
|
54
|
+
encoding: recordingConfig.encoding ?? 'pcm_32bit',
|
|
55
|
+
}) ||
|
|
56
|
+
audioContextFormat.bitDepth ||
|
|
57
|
+
DEFAULT_WEB_BITDEPTH;
|
|
58
|
+
// Initialize the audio buffer separately
|
|
59
|
+
this.audioBuffer = new Float32Array(0);
|
|
60
|
+
this.audioBufferSize = 0;
|
|
61
|
+
this.audioAnalysisData = {
|
|
62
|
+
amplitudeRange: { min: 0, max: 0 },
|
|
63
|
+
dataPoints: [],
|
|
64
|
+
durationMs: 0,
|
|
65
|
+
samples: 0,
|
|
66
|
+
amplitudeAlgorithm: recordingConfig.algorithm || DEFAULT_ALGORITHM,
|
|
67
|
+
bitDepth: this.bitDepth,
|
|
68
|
+
numberOfChannels: this.numberOfChannels,
|
|
69
|
+
sampleRate: this.config.sampleRate || this.audioContext.sampleRate,
|
|
70
|
+
pointsPerSecond: this.config.pointsPerSecond || DEFAULT_WEB_POINTS_PER_SECOND,
|
|
71
|
+
speakerChanges: [],
|
|
72
|
+
};
|
|
73
|
+
if (recordingConfig.enableProcessing) {
|
|
74
|
+
this.initFeatureExtractorWorker();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async init() {
|
|
78
|
+
try {
|
|
79
|
+
if (!this.audioWorkletUrl) {
|
|
80
|
+
const blob = new Blob([InlineAudioWebWorker], {
|
|
81
|
+
type: 'application/javascript',
|
|
82
|
+
});
|
|
83
|
+
const url = URL.createObjectURL(blob);
|
|
84
|
+
await this.audioContext.audioWorklet.addModule(url);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
await this.audioContext.audioWorklet.addModule(this.audioWorkletUrl);
|
|
88
|
+
}
|
|
89
|
+
this.audioWorkletNode = new AudioWorkletNode(this.audioContext, 'recorder-processor');
|
|
90
|
+
this.audioWorkletNode.port.onmessage = async (event) => {
|
|
91
|
+
const command = event.data.command;
|
|
92
|
+
if (command !== 'newData') {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const pcmBufferFloat = event.data.recordedData;
|
|
96
|
+
if (!pcmBufferFloat) {
|
|
97
|
+
this.logger?.warn('Received empty audio buffer', event);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
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;
|
|
108
|
+
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,
|
|
117
|
+
});
|
|
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
|
+
}
|
|
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
|
+
}, []);
|
|
147
|
+
};
|
|
148
|
+
this.logger?.debug(`WebRecorder initialized -- recordSampleRate=${this.audioContext.sampleRate}`, this.config);
|
|
149
|
+
this.audioWorkletNode.port.postMessage({
|
|
150
|
+
command: 'init',
|
|
151
|
+
recordSampleRate: this.audioContext.sampleRate, // Pass the original sample rate
|
|
152
|
+
exportSampleRate: this.config.sampleRate ?? this.audioContext.sampleRate,
|
|
153
|
+
bitDepth: this.bitDepth,
|
|
154
|
+
exportBitDepth: this.exportBitDepth,
|
|
155
|
+
channels: this.numberOfChannels,
|
|
156
|
+
interval: this.config.interval ?? DEFAULT_WEB_INTERVAL,
|
|
157
|
+
});
|
|
158
|
+
// Connect the source to the AudioWorkletNode and start recording
|
|
159
|
+
this.source.connect(this.audioWorkletNode);
|
|
160
|
+
this.audioWorkletNode.connect(this.audioContext.destination);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
console.error(`[${TAG}] Failed to initialize WebRecorder`, error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
initFeatureExtractorWorker(featuresExtratorUrl) {
|
|
167
|
+
try {
|
|
168
|
+
if (featuresExtratorUrl) {
|
|
169
|
+
// Initialize the feature extractor worker
|
|
170
|
+
//TODO: create audio feature extractor from a Blob instead of url since we cannot include the url directly in the library
|
|
171
|
+
// We keep the url during dev and use the blob in production.
|
|
172
|
+
this.featureExtractorWorker = new Worker(new URL(featuresExtratorUrl, window.location.href));
|
|
173
|
+
this.featureExtractorWorker.onmessage =
|
|
174
|
+
this.handleFeatureExtractorMessage.bind(this);
|
|
175
|
+
this.featureExtractorWorker.onerror =
|
|
176
|
+
this.handleWorkerError.bind(this);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// Fallback to the inline worker if the URL is not provided
|
|
180
|
+
this.initFallbackWorker();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
console.error(`[${TAG}] Failed to initialize feature extractor worker`, error);
|
|
185
|
+
this.initFallbackWorker();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
initFallbackWorker() {
|
|
189
|
+
try {
|
|
190
|
+
const blob = new Blob([InlineFeaturesExtractor], {
|
|
191
|
+
type: 'application/javascript',
|
|
192
|
+
});
|
|
193
|
+
const url = URL.createObjectURL(blob);
|
|
194
|
+
this.featureExtractorWorker = new Worker(url);
|
|
195
|
+
this.featureExtractorWorker.onmessage =
|
|
196
|
+
this.handleFeatureExtractorMessage.bind(this);
|
|
197
|
+
this.featureExtractorWorker.onerror = (error) => {
|
|
198
|
+
console.error(`[${TAG}] Default Inline worker failed`, error);
|
|
199
|
+
};
|
|
200
|
+
this.logger?.log('Inline worker initialized successfully');
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
console.error(`[${TAG}] Failed to initialize Inline Feature Extractor worker`, error);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
handleWorkerError(error) {
|
|
207
|
+
console.error(`[${TAG}] Feature extractor worker error:`, error);
|
|
208
|
+
}
|
|
209
|
+
handleFeatureExtractorMessage(event) {
|
|
210
|
+
if (event.data.command === 'features') {
|
|
211
|
+
const segmentResult = event.data.result;
|
|
212
|
+
// Merge the segment result with the full audio analysis data
|
|
213
|
+
this.audioAnalysisData.dataPoints.push(...segmentResult.dataPoints);
|
|
214
|
+
this.audioAnalysisData.speakerChanges?.push(...(segmentResult.speakerChanges ?? []));
|
|
215
|
+
this.audioAnalysisData.durationMs = segmentResult.durationMs;
|
|
216
|
+
if (segmentResult.amplitudeRange) {
|
|
217
|
+
this.audioAnalysisData.amplitudeRange = {
|
|
218
|
+
min: Math.min(this.audioAnalysisData.amplitudeRange.min, segmentResult.amplitudeRange.min),
|
|
219
|
+
max: Math.max(this.audioAnalysisData.amplitudeRange.max, segmentResult.amplitudeRange.max),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// Handle the extracted features (e.g., emit an event or log them)
|
|
223
|
+
this.logger?.debug('features event segmentResult', segmentResult);
|
|
224
|
+
this.logger?.debug(`features event audioAnalysisData duration=${this.audioAnalysisData.durationMs}`, this.audioAnalysisData);
|
|
225
|
+
this.emitAudioAnalysisCallback(segmentResult);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
start() {
|
|
229
|
+
this.source.connect(this.audioWorkletNode);
|
|
230
|
+
this.audioWorkletNode.connect(this.audioContext.destination);
|
|
231
|
+
this.packetCount = 0;
|
|
232
|
+
}
|
|
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
|
+
// Add wav header to the raw PCM data
|
|
265
|
+
const wavHeaderBuffer = writeWavHeader({
|
|
266
|
+
buffer: rawPCMDataFull.buffer,
|
|
267
|
+
sampleRate: this.audioContext.sampleRate,
|
|
268
|
+
numChannels: this.numberOfChannels,
|
|
269
|
+
bitDepth: this.exportBitDepth,
|
|
270
|
+
});
|
|
271
|
+
const convertedPCM = await convertPCMToFloat32({
|
|
272
|
+
buffer: wavHeaderBuffer,
|
|
273
|
+
bitDepth: this.exportBitDepth,
|
|
274
|
+
});
|
|
275
|
+
resolve(convertedPCM.pcmValues); // Resolve the promise with the collected buffers
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
this.audioWorkletNode.port.addEventListener('message', onMessage);
|
|
279
|
+
}
|
|
280
|
+
// Stop all media stream tracks to stop the browser recording
|
|
281
|
+
this.stopMediaStreamTracks();
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
reject(error);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
pause() {
|
|
289
|
+
this.source.disconnect(this.audioWorkletNode); // Disconnect the source from the AudioWorkletNode
|
|
290
|
+
this.audioWorkletNode.disconnect(this.audioContext.destination); // Disconnect the AudioWorkletNode from the destination
|
|
291
|
+
this.audioWorkletNode.port.postMessage({ command: 'pause' });
|
|
292
|
+
}
|
|
293
|
+
stopMediaStreamTracks() {
|
|
294
|
+
// Stop all audio tracks to stop the recording icon
|
|
295
|
+
const tracks = this.source.mediaStream.getTracks();
|
|
296
|
+
tracks.forEach((track) => track.stop());
|
|
297
|
+
}
|
|
298
|
+
async playRecordedData({ recordedData, }) {
|
|
299
|
+
try {
|
|
300
|
+
const blob = new Blob([recordedData]);
|
|
301
|
+
const url = URL.createObjectURL(blob);
|
|
302
|
+
const response = await fetch(url);
|
|
303
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
304
|
+
// Decode the audio data
|
|
305
|
+
const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
|
|
306
|
+
// Create a buffer source node and play the audio
|
|
307
|
+
const bufferSource = this.audioContext.createBufferSource();
|
|
308
|
+
bufferSource.buffer = audioBuffer;
|
|
309
|
+
bufferSource.connect(this.audioContext.destination);
|
|
310
|
+
bufferSource.start();
|
|
311
|
+
this.logger?.debug('Playing recorded data', recordedData);
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
console.error(`[${TAG}] Failed to play recorded data:`, error);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
checkAudioContextFormat({ sampleRate }) {
|
|
318
|
+
// Create a silent AudioBuffer
|
|
319
|
+
const frameCount = sampleRate * 1.0; // 1 second buffer
|
|
320
|
+
const audioBuffer = this.audioContext.createBuffer(1, frameCount, sampleRate);
|
|
321
|
+
// Check the format
|
|
322
|
+
const channelData = audioBuffer.getChannelData(0);
|
|
323
|
+
const bitDepth = channelData.BYTES_PER_ELEMENT * 8; // 4 bytes per element means 32-bit
|
|
324
|
+
return {
|
|
325
|
+
sampleRate: audioBuffer.sampleRate,
|
|
326
|
+
bitDepth,
|
|
327
|
+
numberOfChannels: audioBuffer.numberOfChannels,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
resume() {
|
|
331
|
+
this.source.connect(this.audioWorkletNode);
|
|
332
|
+
this.audioWorkletNode.connect(this.audioContext.destination);
|
|
333
|
+
this.audioWorkletNode.port.postMessage({ command: 'resume' });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
//# sourceMappingURL=WebRecorder.web.js.map
|
|
@@ -0,0 +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 // Add wav header to the raw PCM data\n const wavHeaderBuffer = writeWavHeader({\n buffer: rawPCMDataFull.buffer,\n sampleRate: this.audioContext.sampleRate,\n numChannels: this.numberOfChannels,\n bitDepth: this.exportBitDepth,\n })\n const convertedPCM = await convertPCMToFloat32({\n buffer: wavHeaderBuffer,\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 const blob = new Blob([recordedData])\n const url = URL.createObjectURL(blob)\n const response = await fetch(url)\n const arrayBuffer = await response.arrayBuffer()\n\n // Decode the audio data\n const audioBuffer =\n await this.audioContext.decodeAudioData(arrayBuffer)\n\n // Create a buffer source node and play the audio\n const bufferSource = this.audioContext.createBufferSource()\n bufferSource.buffer = audioBuffer\n bufferSource.connect(this.audioContext.destination)\n bufferSource.start()\n this.logger?.debug('Playing recorded data', recordedData)\n } catch (error) {\n console.error(`[${TAG}] Failed to play recorded data:`, error)\n }\n }\n\n private checkAudioContextFormat({ sampleRate }: { sampleRate: number }) {\n // Create a silent AudioBuffer\n const frameCount = sampleRate * 1.0 // 1 second buffer\n const audioBuffer = this.audioContext.createBuffer(\n 1,\n frameCount,\n sampleRate\n )\n\n // Check the format\n const channelData = audioBuffer.getChannelData(0)\n const bitDepth = channelData.BYTES_PER_ELEMENT * 8 // 4 bytes per element means 32-bit\n\n return {\n sampleRate: audioBuffer.sampleRate,\n bitDepth,\n numberOfChannels: audioBuffer.numberOfChannels,\n }\n }\n\n resume() {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.audioWorkletNode.port.postMessage({ command: 'resume' })\n }\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BitDepth, SampleRate } from './ExpoAudioStream.types';
|
|
2
|
+
export declare const isWeb: boolean;
|
|
3
|
+
export declare const DEBUG_NAMESPACE = "expo-audio-stream";
|
|
4
|
+
export declare const RIFF_HEADER = 1380533830;
|
|
5
|
+
export declare const WAVE_HEADER = 1463899717;
|
|
6
|
+
export declare const FMT_CHUNK_ID = 1718449184;
|
|
7
|
+
export declare const DATA_CHUNK_ID = 1684108385;
|
|
8
|
+
export declare const INFO_CHUNK_ID = 1229866575;
|
|
9
|
+
export declare const DEFAULT_SAMPLE_RATE: SampleRate;
|
|
10
|
+
export declare const DEFAULT_BIT_DEPTH: BitDepth;
|
|
11
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAE9D,eAAO,MAAM,KAAK,SAAwB,CAAA;AAC1C,eAAO,MAAM,eAAe,sBAAsB,CAAA;AAGlD,eAAO,MAAM,WAAW,aAAa,CAAA;AACrC,eAAO,MAAM,WAAW,aAAa,CAAA;AACrC,eAAO,MAAM,YAAY,aAAa,CAAA;AACtC,eAAO,MAAM,aAAa,aAAa,CAAA;AACvC,eAAO,MAAM,aAAa,aAAa,CAAA;AAGvC,eAAO,MAAM,mBAAmB,EAAE,UAAkB,CAAA;AACpD,eAAO,MAAM,iBAAiB,EAAE,QAAa,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// packages/expo-audio-stream/src/constants.ts
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
export const isWeb = Platform.OS === 'web';
|
|
4
|
+
export const DEBUG_NAMESPACE = 'expo-audio-stream';
|
|
5
|
+
// Constants for identifying chunks in a WAV file
|
|
6
|
+
export const RIFF_HEADER = 0x52494646; // "RIFF"
|
|
7
|
+
export const WAVE_HEADER = 0x57415645; // "WAVE"
|
|
8
|
+
export const FMT_CHUNK_ID = 0x666d7420; // "fmt "
|
|
9
|
+
export const DATA_CHUNK_ID = 0x64617461; // "data"
|
|
10
|
+
export const INFO_CHUNK_ID = 0x494e464f; // "INFO"
|
|
11
|
+
// Default values
|
|
12
|
+
export const DEFAULT_SAMPLE_RATE = 16000;
|
|
13
|
+
export const DEFAULT_BIT_DEPTH = 32;
|
|
14
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAIvC,MAAM,CAAC,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,KAAK,KAAK,CAAA;AAC1C,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAAA;AAElD,iDAAiD;AACjD,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAA,CAAC,SAAS;AAC/C,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAA,CAAC,SAAS;AAC/C,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAA,CAAC,SAAS;AAChD,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAA,CAAC,SAAS;AACjD,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAA,CAAC,SAAS;AAEjD,iBAAiB;AACjB,MAAM,CAAC,MAAM,mBAAmB,GAAe,KAAK,CAAA;AACpD,MAAM,CAAC,MAAM,iBAAiB,GAAa,EAAE,CAAA","sourcesContent":["// packages/expo-audio-stream/src/constants.ts\nimport { Platform } from 'react-native'\n\nimport { BitDepth, SampleRate } from './ExpoAudioStream.types'\n\nexport const isWeb = Platform.OS === 'web'\nexport const DEBUG_NAMESPACE = 'expo-audio-stream'\n\n// Constants for identifying chunks in a WAV file\nexport const RIFF_HEADER = 0x52494646 // \"RIFF\"\nexport const WAVE_HEADER = 0x57415645 // \"WAVE\"\nexport const FMT_CHUNK_ID = 0x666d7420 // \"fmt \"\nexport const DATA_CHUNK_ID = 0x64617461 // \"data\"\nexport const INFO_CHUNK_ID = 0x494e464f // \"INFO\"\n\n// Default values\nexport const DEFAULT_SAMPLE_RATE: SampleRate = 16000\nexport const DEFAULT_BIT_DEPTH: BitDepth = 32\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type EventSubscription } from 'expo-modules-core';
|
|
2
|
+
import { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types';
|
|
3
|
+
export interface AudioEventPayload {
|
|
4
|
+
encoded?: string;
|
|
5
|
+
buffer?: Float32Array;
|
|
6
|
+
fileUri: string;
|
|
7
|
+
lastEmittedSize: number;
|
|
8
|
+
position: number;
|
|
9
|
+
deltaSize: number;
|
|
10
|
+
totalSize: number;
|
|
11
|
+
mimeType: string;
|
|
12
|
+
streamUuid: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function addAudioEventListener(listener: (event: AudioEventPayload) => Promise<void>): EventSubscription;
|
|
15
|
+
export interface AudioAnalysisEvent extends AudioAnalysis {
|
|
16
|
+
}
|
|
17
|
+
export declare function addAudioAnalysisListener(listener: (event: AudioAnalysisEvent) => Promise<void>): EventSubscription;
|
|
18
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +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"}
|
package/build/events.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// packages/expo-audio-stream/src/events.ts
|
|
2
|
+
import { LegacyEventEmitter } from 'expo-modules-core';
|
|
3
|
+
import ExpoAudioStreamModule from './ExpoAudioStreamModule';
|
|
4
|
+
const emitter = new LegacyEventEmitter(ExpoAudioStreamModule);
|
|
5
|
+
export function addAudioEventListener(listener) {
|
|
6
|
+
return emitter.addListener('AudioData', listener);
|
|
7
|
+
}
|
|
8
|
+
export function addAudioAnalysisListener(listener) {
|
|
9
|
+
return emitter.addListener('AudioAnalysis', listener);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +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"]}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { extractAudioAnalysis } from './AudioAnalysis/extractAudioAnalysis';
|
|
2
|
+
import { AudioRecorderProvider, useSharedAudioRecorder } from './AudioRecorder.provider';
|
|
3
|
+
import ExpoAudioStreamModule from './ExpoAudioStreamModule';
|
|
4
|
+
import { useAudioRecorder } from './useAudioRecorder';
|
|
5
|
+
export * from './utils/convertPCMToFloat32';
|
|
6
|
+
export * from './utils/getWavFileInfo';
|
|
7
|
+
export * from './utils/writeWavHeader';
|
|
8
|
+
export { AudioRecorderProvider, ExpoAudioStreamModule, extractAudioAnalysis, useAudioRecorder, useSharedAudioRecorder, };
|
|
9
|
+
export type * from './AudioAnalysis/AudioAnalysis.types';
|
|
10
|
+
export type * from './ExpoAudioStream.types';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAA;AAC3E,OAAO,EACH,qBAAqB,EACrB,sBAAsB,EACzB,MAAM,0BAA0B,CAAA;AACjC,OAAO,qBAAqB,MAAM,yBAAyB,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAErD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAEtC,OAAO,EACH,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,GACzB,CAAA;AAED,mBAAmB,qCAAqC,CAAA;AACxD,mBAAmB,yBAAyB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAe;AAEf,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAA;AAC3E,OAAO,EACH,qBAAqB,EACrB,sBAAsB,GACzB,MAAM,0BAA0B,CAAA;AACjC,OAAO,qBAAqB,MAAM,yBAAyB,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAErD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAEtC,OAAO,EACH,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,GACzB,CAAA","sourcesContent":["// src/index.ts\n\nimport { extractAudioAnalysis } from './AudioAnalysis/extractAudioAnalysis'\nimport {\n AudioRecorderProvider,\n useSharedAudioRecorder,\n} from './AudioRecorder.provider'\nimport ExpoAudioStreamModule from './ExpoAudioStreamModule'\nimport { useAudioRecorder } from './useAudioRecorder'\n\nexport * from './utils/convertPCMToFloat32'\nexport * from './utils/getWavFileInfo'\nexport * from './utils/writeWavHeader'\n\nexport {\n AudioRecorderProvider,\n ExpoAudioStreamModule,\n extractAudioAnalysis,\n useAudioRecorder,\n useSharedAudioRecorder,\n}\n\nexport type * from './AudioAnalysis/AudioAnalysis.types'\nexport type * from './ExpoAudioStream.types'\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types';
|
|
2
|
+
import { AudioRecording, ConsoleLike, RecordingConfig, StartRecordingResult } from './ExpoAudioStream.types';
|
|
3
|
+
export interface UseAudioRecorderProps {
|
|
4
|
+
logger?: ConsoleLike;
|
|
5
|
+
audioWorkletUrl?: string;
|
|
6
|
+
featuresExtratorUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface UseAudioRecorderState {
|
|
9
|
+
startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>;
|
|
10
|
+
stopRecording: () => Promise<AudioRecording>;
|
|
11
|
+
pauseRecording: () => Promise<void>;
|
|
12
|
+
resumeRecording: () => Promise<void>;
|
|
13
|
+
isRecording: boolean;
|
|
14
|
+
isPaused: boolean;
|
|
15
|
+
durationMs: number;
|
|
16
|
+
size: number;
|
|
17
|
+
analysisData?: AudioAnalysis;
|
|
18
|
+
}
|
|
19
|
+
export declare function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl, }?: UseAudioRecorderProps): UseAudioRecorderState;
|
|
20
|
+
//# sourceMappingURL=useAudioRecorder.d.ts.map
|
|
@@ -0,0 +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"}
|