Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 5c685dc

Browse files
committed
Avoid use of deprecated APIs, instead using an AudioWorklet
A bit annoying that it is async, but it'll do.
1 parent e523ce6 commit 5c685dc

File tree

2 files changed

+51
-14
lines changed

2 files changed

+51
-14
lines changed

src/voice/VoiceRecorder.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616

1717
import * as Recorder from 'opus-recorder';
1818
import encoderPath from 'opus-recorder/dist/encoderWorker.min.js';
19+
import mxVoiceWorkletPath from './mxVoiceWorklet';
1920
import {MatrixClient} from "matrix-js-sdk/src/client";
2021
import CallMediaHandler from "../CallMediaHandler";
2122
import {SimpleObservable} from "matrix-widget-api";
@@ -36,7 +37,7 @@ export class VoiceRecorder {
3637
private recorderSource: MediaStreamAudioSourceNode;
3738
private recorderStream: MediaStream;
3839
private recorderFFT: AnalyserNode;
39-
private recorderProcessor: ScriptProcessorNode;
40+
private recorderWorklet: AudioWorkletNode;
4041
private buffer = new Uint8Array(0);
4142
private mxc: string;
4243
private recording = false;
@@ -70,18 +71,20 @@ export class VoiceRecorder {
7071
// it makes the time domain less than helpful.
7172
this.recorderFFT.fftSize = 64;
7273

73-
// We use an audio processor to get accurate timing information.
74-
// The size of the audio buffer largely decides how quickly we push timing/waveform data
75-
// out of this class. Smaller buffers mean we update more frequently as we can't hold as
76-
// many bytes. Larger buffers mean slower updates. For scale, 1024 gives us about 30Hz of
77-
// updates and 2048 gives us about 20Hz. We use 1024 to get as close to perceived realtime
78-
// as possible. Must be a power of 2.
79-
this.recorderProcessor = this.recorderContext.createScriptProcessor(1024, CHANNELS, CHANNELS);
74+
await this.recorderContext.audioWorklet.addModule(mxVoiceWorkletPath);
75+
this.recorderWorklet = new AudioWorkletNode(this.recorderContext, "mx-voice-worklet");
8076

8177
// Connect our inputs and outputs
8278
this.recorderSource.connect(this.recorderFFT);
83-
this.recorderSource.connect(this.recorderProcessor);
84-
this.recorderProcessor.connect(this.recorderContext.destination);
79+
this.recorderSource.connect(this.recorderWorklet);
80+
this.recorderWorklet.connect(this.recorderContext.destination);
81+
82+
// Dev note: we can't use `addEventListener` for some reason. It just doesn't work.
83+
this.recorderWorklet.port.onmessage = (ev) => {
84+
if (ev.data['ev'] === 'proc') {
85+
this.tryUpdateLiveData(ev.data['timeMs']);
86+
}
87+
};
8588

8689
this.recorder = new Recorder({
8790
encoderPath, // magic from webpack
@@ -128,7 +131,7 @@ export class VoiceRecorder {
128131
return this.mxc;
129132
}
130133

131-
private tryUpdateLiveData = (ev: AudioProcessingEvent) => {
134+
private tryUpdateLiveData = (timeMillis: number) => {
132135
if (!this.recording) return;
133136

134137
// The time domain is the input to the FFT, which means we use an array of the same
@@ -150,7 +153,7 @@ export class VoiceRecorder {
150153

151154
this.observable.update({
152155
waveform: translatedData,
153-
timeSeconds: ev.playbackTime,
156+
timeSeconds: timeMillis / 1000,
154157
});
155158
};
156159

@@ -166,7 +169,6 @@ export class VoiceRecorder {
166169
}
167170
this.observable = new SimpleObservable<IRecordingUpdate>();
168171
await this.makeRecorder();
169-
this.recorderProcessor.addEventListener("audioprocess", this.tryUpdateLiveData);
170172
await this.recorder.start();
171173
this.recording = true;
172174
}
@@ -178,6 +180,7 @@ export class VoiceRecorder {
178180

179181
// Disconnect the source early to start shutting down resources
180182
this.recorderSource.disconnect();
183+
this.recorderWorklet.disconnect();
181184
await this.recorder.stop();
182185

183186
// close the context after the recorder so the recorder doesn't try to
@@ -189,7 +192,6 @@ export class VoiceRecorder {
189192

190193
// Finally do our post-processing and clean up
191194
this.recording = false;
192-
this.recorderProcessor.removeEventListener("audioprocess", this.tryUpdateLiveData);
193195
await this.recorder.close();
194196

195197
return this.buffer;

src/voice/mxVoiceWorklet.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright 2021 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
class MxVoiceWorklet extends AudioWorkletProcessor {
18+
constructor() {
19+
super();
20+
21+
this._timeStart = 0;
22+
}
23+
24+
process(inputs, outputs, parameters) {
25+
const now = (new Date()).getTime();
26+
if (this._timeStart === 0) {
27+
this._timeStart = now;
28+
}
29+
30+
this.port.postMessage({ev: 'proc', timeMs: now - this._timeStart});
31+
return true;
32+
}
33+
}
34+
35+
registerProcessor('mx-voice-worklet', MxVoiceWorklet);

0 commit comments

Comments
 (0)