Enables ContextIsolation
This commit is contained in:
parent
4bbf5eb5d4
commit
9374832ea4
83 changed files with 1009 additions and 1073 deletions
161
ts/WebAudioRecorder.ts
Normal file
161
ts/WebAudioRecorder.ts
Normal file
|
@ -0,0 +1,161 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
bufferSize: undefined, // buffer size (use browser default)
|
||||
encodeAfterRecord: false,
|
||||
mp3: {
|
||||
mimeType: 'audio/mpeg',
|
||||
bitRate: 160, // (CBR only): bit rate = [64 .. 320]
|
||||
},
|
||||
numChannels: 2, // number of channels
|
||||
progressInterval: 1000, // encoding progress report interval (millisec)
|
||||
timeLimit: 300, // recording time limit (sec)
|
||||
};
|
||||
|
||||
type OptionsType = {
|
||||
bufferSize: number | undefined;
|
||||
numChannels: number;
|
||||
timeLimit?: number;
|
||||
};
|
||||
|
||||
export class WebAudioRecorder {
|
||||
private buffer: Array<Float32Array>;
|
||||
private options: OptionsType;
|
||||
private context: BaseAudioContext;
|
||||
private input: GainNode;
|
||||
private onComplete: (recorder: WebAudioRecorder, blob: Blob) => unknown;
|
||||
private onError: (recorder: WebAudioRecorder, error: string) => unknown;
|
||||
private processor?: ScriptProcessorNode;
|
||||
public worker?: Worker;
|
||||
|
||||
constructor(
|
||||
sourceNode: GainNode,
|
||||
options: Pick<OptionsType, 'timeLimit'>,
|
||||
callbacks: {
|
||||
onComplete: (recorder: WebAudioRecorder, blob: Blob) => unknown;
|
||||
onError: (recorder: WebAudioRecorder, error: string) => unknown;
|
||||
}
|
||||
) {
|
||||
this.options = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...options,
|
||||
};
|
||||
|
||||
this.context = sourceNode.context;
|
||||
this.input = this.context.createGain();
|
||||
sourceNode.connect(this.input);
|
||||
this.buffer = [];
|
||||
this.initWorker();
|
||||
|
||||
this.onComplete = callbacks.onComplete;
|
||||
this.onError = callbacks.onError;
|
||||
}
|
||||
|
||||
isRecording(): boolean {
|
||||
return this.processor != null;
|
||||
}
|
||||
|
||||
startRecording(): void {
|
||||
if (this.isRecording()) {
|
||||
this.error('startRecording: previous recording is running');
|
||||
return;
|
||||
}
|
||||
|
||||
const { buffer, worker } = this;
|
||||
const { bufferSize, numChannels } = this.options;
|
||||
|
||||
if (!worker) {
|
||||
this.error('startRecording: worker not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
this.processor = this.context.createScriptProcessor(
|
||||
bufferSize,
|
||||
numChannels,
|
||||
numChannels
|
||||
);
|
||||
this.input.connect(this.processor);
|
||||
this.processor.connect(this.context.destination);
|
||||
this.processor.onaudioprocess = event => {
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let ch = 0; ch < numChannels; ++ch) {
|
||||
buffer[ch] = event.inputBuffer.getChannelData(ch);
|
||||
}
|
||||
worker.postMessage({ command: 'record', buffer });
|
||||
};
|
||||
worker.postMessage({
|
||||
command: 'start',
|
||||
bufferSize: this.processor.bufferSize,
|
||||
});
|
||||
}
|
||||
|
||||
cancelRecording(): void {
|
||||
if (!this.isRecording()) {
|
||||
this.error('cancelRecording: no recording is running');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.worker || !this.processor) {
|
||||
this.error('startRecording: worker not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
this.input.disconnect();
|
||||
this.processor.disconnect();
|
||||
delete this.processor;
|
||||
this.worker.postMessage({ command: 'cancel' });
|
||||
}
|
||||
|
||||
finishRecording(): void {
|
||||
if (!this.isRecording()) {
|
||||
this.error('finishRecording: no recording is running');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.worker || !this.processor) {
|
||||
this.error('startRecording: worker not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
this.input.disconnect();
|
||||
this.processor.disconnect();
|
||||
delete this.processor;
|
||||
this.worker.postMessage({ command: 'finish' });
|
||||
}
|
||||
|
||||
private initWorker(): void {
|
||||
if (this.worker != null) {
|
||||
this.worker.terminate();
|
||||
}
|
||||
|
||||
this.worker = new Worker('js/WebAudioRecorderMp3.js');
|
||||
this.worker.onmessage = event => {
|
||||
const { data } = event;
|
||||
switch (data.command) {
|
||||
case 'complete':
|
||||
this.onComplete(this, data.blob);
|
||||
break;
|
||||
case 'error':
|
||||
this.error(data.message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
this.worker.postMessage({
|
||||
command: 'init',
|
||||
config: {
|
||||
sampleRate: this.context.sampleRate,
|
||||
numChannels: this.options.numChannels,
|
||||
},
|
||||
options: this.options,
|
||||
});
|
||||
}
|
||||
|
||||
error(message: string): void {
|
||||
this.onError(this, `WebAudioRecorder.js: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
window.WebAudioRecorder = WebAudioRecorder;
|
Loading…
Add table
Add a link
Reference in a new issue