@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.
- package/README.md +7 -18
- package/android/build.gradle +5 -0
- package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +120 -0
- package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +34 -4
- package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +635 -0
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +194 -79
- package/android/src/main/java/net/siteed/audiostream/Constants.kt +1 -0
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +48 -2
- package/android/src/main/java/net/siteed/audiostream/FFT.kt +44 -0
- package/android/src/main/java/net/siteed/audiostream/Features.kt +56 -0
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +12 -0
- package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +56 -0
- package/app.plugin.js +1 -1
- package/build/AudioRecorder.provider.js +1 -1
- package/build/AudioRecorder.provider.js.map +1 -1
- 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 +79 -6
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStream.web.d.ts +41 -0
- package/build/ExpoAudioStream.web.d.ts.map +1 -0
- package/build/ExpoAudioStream.web.js +184 -0
- package/build/ExpoAudioStream.web.js.map +1 -0
- package/build/ExpoAudioStreamModule.d.ts +2 -2
- package/build/ExpoAudioStreamModule.d.ts.map +1 -1
- package/build/ExpoAudioStreamModule.js +12 -3
- package/build/ExpoAudioStreamModule.js.map +1 -1
- package/build/WebRecorder.d.ts +47 -0
- package/build/WebRecorder.d.ts.map +1 -0
- package/build/WebRecorder.js +243 -0
- package/build/WebRecorder.js.map +1 -0
- package/build/index.d.ts +14 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +106 -7
- package/build/index.js.map +1 -1
- package/build/inlineAudioWebWorker.d.ts +3 -0
- package/build/inlineAudioWebWorker.d.ts.map +1 -0
- package/build/inlineAudioWebWorker.js +340 -0
- package/build/inlineAudioWebWorker.js.map +1 -0
- package/build/useAudioRecording.d.ts +24 -9
- package/build/useAudioRecording.d.ts.map +1 -1
- package/build/useAudioRecording.js +107 -29
- package/build/useAudioRecording.js.map +1 -1
- package/build/utils.d.ts +31 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +143 -0
- package/build/utils.js.map +1 -0
- package/expo-module.config.json +13 -4
- package/ios/AudioAnalysisData.swift +39 -0
- package/ios/AudioProcessingHelpers.swift +59 -0
- package/ios/AudioProcessor.swift +317 -0
- package/ios/AudioStreamError.swift +7 -0
- package/ios/AudioStreamManager.swift +204 -52
- package/ios/AudioStreamManagerDelegate.swift +4 -0
- package/ios/DataPoint.swift +41 -0
- package/ios/ExpoAudioStreamModule.swift +188 -6
- package/ios/Features.swift +44 -0
- package/ios/RecordingResult.swift +19 -0
- package/ios/RecordingSettings.swift +13 -0
- package/ios/WaveformExtractor.swift +105 -0
- package/package.json +9 -9
- package/plugin/tsconfig.json +13 -8
- package/publish.sh +8 -0
- package/src/AudioRecorder.provider.tsx +1 -1
- package/src/ExpoAudioStream.native.ts +6 -0
- package/src/ExpoAudioStream.types.ts +97 -11
- package/src/ExpoAudioStream.web.ts +228 -0
- package/src/ExpoAudioStreamModule.ts +17 -3
- package/src/WebRecorder.ts +364 -0
- package/src/index.ts +166 -20
- package/src/inlineAudioWebWorker.tsx +340 -0
- package/src/useAudioRecording.tsx +410 -0
- package/src/utils.ts +189 -0
- package/build/ExpoAudioStreamModule.web.d.ts +0 -37
- package/build/ExpoAudioStreamModule.web.d.ts.map +0 -1
- package/build/ExpoAudioStreamModule.web.js +0 -156
- package/build/ExpoAudioStreamModule.web.js.map +0 -1
- package/docs/demo.gif +0 -0
- package/release-it.js +0 -18
- package/src/ExpoAudioStreamModule.web.ts +0 -181
- package/src/useAudioRecording.ts +0 -268
- 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
|
+
|
|
161
|
+
// Because we use expo and needs to include the worker script in the shared library, better inline it in the module.
|
|
162
|
+
export const InlineProcessorScrippt = `
|
|
163
|
+
class RecorderProcessor extends AudioWorkletProcessor {
|
|
164
|
+
constructor() {
|
|
165
|
+
super();
|
|
166
|
+
this.recLength = 0;
|
|
167
|
+
this.recBuffer = [];
|
|
168
|
+
this.headerSent = false;
|
|
169
|
+
this.newRecBuffer = [];
|
|
170
|
+
this.exportIntervalSamples = 0;
|
|
171
|
+
this.samplesSinceLastExport = 0;
|
|
172
|
+
this.recordSampleRate = 44100; // To be overwrited
|
|
173
|
+
this.exportSampleRate = 44100; // To be overwrited
|
|
174
|
+
this.isRecording = true;
|
|
175
|
+
|
|
176
|
+
this.port.onmessage = this.handleMessage.bind(this);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
handleMessage(event) {
|
|
180
|
+
switch (event.data.command) {
|
|
181
|
+
case 'init':
|
|
182
|
+
this.recordSampleRate = event.data.recordSampleRate;
|
|
183
|
+
this.exportSampleRate = event.data.exportSampleRate || event.data.recordSampleRate;
|
|
184
|
+
this.exportIntervalSamples = this.recordSampleRate * (event.data.interval / 1000);
|
|
185
|
+
break;
|
|
186
|
+
case 'stop':
|
|
187
|
+
this.isRecording = false;
|
|
188
|
+
break;
|
|
189
|
+
case 'getRecordedData':
|
|
190
|
+
const recordedData = this.getRecordedData();
|
|
191
|
+
this.port.postMessage({ command: 'recordedData', recordedData });
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
process(inputs) {
|
|
197
|
+
if (!this.isRecording) {
|
|
198
|
+
return true; // Exit early if not recording
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (inputs.length === 0 || inputs[0].length === 0) {
|
|
202
|
+
console.warn('RecorderProcessor -- No input received.');
|
|
203
|
+
return true; // Exit early if no input
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const buffer = inputs[0];
|
|
207
|
+
if (!buffer) {
|
|
208
|
+
console.error('Input buffer is null.');
|
|
209
|
+
return true; // Exit early if buffer is null
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
this.record(buffer);
|
|
214
|
+
|
|
215
|
+
// this.samplesSinceLastExport += buffer.length;
|
|
216
|
+
// if (this.samplesSinceLastExport >= this.exportIntervalSamples) {
|
|
217
|
+
// this.exportBuffer();
|
|
218
|
+
// this.samplesSinceLastExport = 0;
|
|
219
|
+
// }
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.error('Error during processing:', error);
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
record(inputBuffer) {
|
|
227
|
+
this.recBuffer.push(inputBuffer);
|
|
228
|
+
this.newRecBuffer.push(inputBuffer);
|
|
229
|
+
this.recLength += inputBuffer.length;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
exportBuffer() {
|
|
233
|
+
const mergedBuffers = this.mergeBuffers(this.newRecBuffer, this.newRecBuffer.reduce((len, buf) => len + buf.length, 0));
|
|
234
|
+
console.log('Merged buffer length:', mergedBuffers.length); // Debug log
|
|
235
|
+
|
|
236
|
+
const downsampledBuffer = this.downsampleBuffer(mergedBuffers, this.exportSampleRate);
|
|
237
|
+
console.log('Downsampled buffer length:', downsampledBuffer.length); // Debug log
|
|
238
|
+
|
|
239
|
+
const encodedWav = downsampledBuffer;
|
|
240
|
+
// const encodedWav = this.encodeWAV(downsampledBuffer);
|
|
241
|
+
// console.log('Encoded WAV length:', encodedWav.byteLength); // Debug log
|
|
242
|
+
|
|
243
|
+
this.port.postMessage({ encodedWav, sampleRate: this.exportSampleRate });
|
|
244
|
+
this.newRecBuffer = []; // Clear the new data buffer after export
|
|
245
|
+
this.headerSent = true; // Indicate that the header has been sent
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
downsampleBuffer(buffer, exportSampleRate) {
|
|
249
|
+
console.log('Original buffer length:', buffer.length); // Debug log
|
|
250
|
+
console.log('Record sample rate:', this.recordSampleRate); // Debug log
|
|
251
|
+
console.log('Export sample rate:', exportSampleRate); // Debug log
|
|
252
|
+
if (exportSampleRate === this.recordSampleRate) {
|
|
253
|
+
return buffer;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const sampleRateRatio = this.recordSampleRate / exportSampleRate;
|
|
257
|
+
const newLength = Math.round(buffer.length / sampleRateRatio);
|
|
258
|
+
console.log('Sample rate ratio:', sampleRateRatio); // Debug log
|
|
259
|
+
console.log('New length after downsampling:', newLength); // Debug log
|
|
260
|
+
|
|
261
|
+
if (newLength <= 0) {
|
|
262
|
+
console.error('New length is zero or negative, returning empty buffer.'); // Debug log
|
|
263
|
+
return new Float32Array(0);
|
|
264
|
+
}
|
|
265
|
+
const result = new Float32Array(newLength);
|
|
266
|
+
let offsetResult = 0;
|
|
267
|
+
let offsetBuffer = 0;
|
|
268
|
+
while (offsetResult < result.length) {
|
|
269
|
+
const nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);
|
|
270
|
+
let accum = 0, count = 0;
|
|
271
|
+
for (let i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) {
|
|
272
|
+
accum += buffer[i];
|
|
273
|
+
count++;
|
|
274
|
+
}
|
|
275
|
+
result[offsetResult] = accum / count;
|
|
276
|
+
offsetResult++;
|
|
277
|
+
offsetBuffer = nextOffsetBuffer;
|
|
278
|
+
}
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
mergeBuffers(bufferArray, recLength) {
|
|
283
|
+
const result = new Float32Array(recLength);
|
|
284
|
+
let offset = 0;
|
|
285
|
+
for (let i = 0; i < bufferArray.length; i++) {
|
|
286
|
+
result.set(bufferArray[i], offset);
|
|
287
|
+
offset += bufferArray[i].length;
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
floatTo16BitPCM(output, offset, input) {
|
|
293
|
+
for (let i = 0; i < input.length; i++, offset += 2) {
|
|
294
|
+
const s = Math.max(-1, Math.min(1, input[i]));
|
|
295
|
+
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
writeString(view, offset, string) {
|
|
300
|
+
for (let i = 0; i < string.length; i++) {
|
|
301
|
+
view.setUint8(offset + i, string.charCodeAt(i));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
getRecordedData() {
|
|
306
|
+
const length = this.recBuffer.reduce((acc, buffer) => acc + buffer.length, 0);
|
|
307
|
+
const result = new Float32Array(length);
|
|
308
|
+
let offset = 0;
|
|
309
|
+
for (const buffer of this.recBuffer) {
|
|
310
|
+
result.set(buffer, offset);
|
|
311
|
+
offset += buffer.length;
|
|
312
|
+
}
|
|
313
|
+
this.recBuffer.length = 0; // Clear the buffers after extraction
|
|
314
|
+
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
encodeWAV(samples) {
|
|
319
|
+
const buffer = new ArrayBuffer(44 + samples.length * 2);
|
|
320
|
+
const view = new DataView(buffer);
|
|
321
|
+
this.writeString(view, 0, 'RIFF');
|
|
322
|
+
view.setUint32(4, 32 + samples.length * 2, true);
|
|
323
|
+
this.writeString(view, 8, 'WAVE');
|
|
324
|
+
this.writeString(view, 12, 'fmt ');
|
|
325
|
+
view.setUint32(16, 16, true);
|
|
326
|
+
view.setUint16(20, 1, true);
|
|
327
|
+
view.setUint16(22, 1, true);
|
|
328
|
+
view.setUint32(24, sampleRate, true);
|
|
329
|
+
view.setUint32(28, sampleRate * 2, true);
|
|
330
|
+
view.setUint16(32, 2, true);
|
|
331
|
+
view.setUint16(34, 16, true);
|
|
332
|
+
this.writeString(view, 36, 'data');
|
|
333
|
+
view.setUint32(40, samples.length * 2, true);
|
|
334
|
+
this.floatTo16BitPCM(view, 44, samples);
|
|
335
|
+
return view;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
registerProcessor('recorder-processor', RecorderProcessor);
|
|
340
|
+
`;
|