Browse Source

Update AudioStream.js

- Verify last bytes of input stream are written to audio output stream before disconnecting audio nodes
- Start MediaRecorder before first AudioData is written to audio output stream
- Include error handling in disconnect() and abortHandler for abort() called immediately following start() for case of close() called on a closed stream
- Check if data set in recorder dataavailable event is defined before calling data.arrayBuffer()
master
guest271314 3 years ago
parent
commit
0a06191497
No account linked to committer's email address
1 changed files with 43 additions and 25 deletions
  1. 43
    25
      chromium_extension/AudioStream.js

+ 43
- 25
chromium_extension/AudioStream.js View File


class AudioStream { class AudioStream {
constructor({ stdin, recorder = false }) { constructor({ stdin, recorder = false }) {
if (!/^espeak-ng/.test(stdin)) { if (!/^espeak-ng/.test(stdin)) {
this.audioReadableSignal = audioReadableSignal; this.audioReadableSignal = audioReadableSignal;
this.audioReadableSignal.onabort = (e) => console.log(e.type); this.audioReadableSignal.onabort = (e) => console.log(e.type);
this.abortHandler = async (e) => { this.abortHandler = async (e) => {
await this.disconnect(true);
try {
await this.disconnect(true);
} catch (err) {
console.warn(err.message);
}
console.log( console.log(
`readOffset:${this.readOffset}, duration:${this.duration}, ac.currentTime:${this.ac.currentTime}`, `readOffset:${this.readOffset}, duration:${this.duration}, ac.currentTime:${this.ac.currentTime}`,
`generator.readyState:${this.generator.readyState}, audioWriter.desiredSize:${this.audioWriter.desiredSize}`, `generator.readyState:${this.generator.readyState}, audioWriter.desiredSize:${this.audioWriter.desiredSize}`,
this.osc.disconnect(); this.osc.disconnect();
this.outputSource.disconnect(); this.outputSource.disconnect();
this.track.stop(); this.track.stop();
await this.audioWriter.close();
await this.audioWriter.closed;
await this.inputReader.cancel();
try {
await this.audioWriter.close();
await this.audioWriter.closed;
await this.inputReader.cancel();
} catch (err) {
throw err;
}
this.generator.stop(); this.generator.stop();
if (this.recorder && this.recorder.state === 'recording') { if (this.recorder && this.recorder.state === 'recording') {
this.recorder.stop(); this.recorder.stop();
this.init = true; this.init = true;
i = 44; i = 44;
} }
for (;
i < value.buffer.byteLength;
i++, this.readOffset++
) {
for (; i < value.buffer.byteLength; i++, this.readOffset++) {
if (channelData.length === this.channelDataLength) { if (channelData.length === this.channelDataLength) {
this.inputController.enqueue(new Uint8Array(channelData));
channelData.length = 0;
this.inputController.enqueue(
new Uint8Array(
channelData.splice(0, this.channelDataLength)
)
);
} }
channelData.push(value[i]); channelData.push(value[i]);
} }
close: async () => { close: async () => {
console.log('Done writing input stream.'); console.log('Done writing input stream.');
if (channelData.length) { if (channelData.length) {
this.inputController.enqueue(channelData);
this.inputController.enqueue(new Uint8Array(channelData));
} }
this.inputController.close(); this.inputController.close();
this.source.postMessage('Done writing input stream.', '*'); this.source.postMessage('Done writing input stream.', '*');
this.audioReadable.pipeTo( this.audioReadable.pipeTo(
new WritableStream({ new WritableStream({
write: async ({ timestamp }) => { write: async ({ timestamp }) => {
if (this.inputController.desiredSize === 0) {
await this.disconnect();
console.log(
`readOffset:${this.readOffset}, duration:${this.duration}, ac.currentTime:${this.ac.currentTime}`,
`generator.readyState:${this.generator.readyState}, audioWriter.desiredSize:${this.audioWriter.desiredSize}`
);
return await Promise.all([
new Promise((resolve) => (this.stream.oninactive = resolve)),
new Promise((resolve) => (this.ac.onstatechange = resolve)),
]);
}
const { value, done } = await this.inputReader.read(); const { value, done } = await this.inputReader.read();
if (done) { if (done) {
console.log({ done }); console.log({ done });
buffer.getChannelData(0).set(floats); buffer.getChannelData(0).set(floats);
this.duration += buffer.duration; this.duration += buffer.duration;
const frame = new AudioData({ const frame = new AudioData({
format:'FLTP',
format: 'FLTP',
sampleRate: 22050, sampleRate: 22050,
numberOfChannels: 1, numberOfChannels: 1,
numberOfFrames: 220, numberOfFrames: 220,
timestamp, timestamp,
data: buffer.getChannelData(0), data: buffer.getChannelData(0),
}); });
await this.audioWriter.write(frame);
if (this.recorder && this.recorder.state === 'inactive') { if (this.recorder && this.recorder.state === 'inactive') {
this.recorder.start(); this.recorder.start();
} }
await this.audioWriter.write(frame);
if (
value.length < this.channelDataLength &&
(await this.inputReader.read()).done
) {
try {
await this.disconnect();
} catch (err) {
console.warn(err.message);
}
console.log(
`readOffset:${this.readOffset}, duration:${this.duration}, ac.currentTime:${this.ac.currentTime}`,
`generator.readyState:${this.generator.readyState}, audioWriter.desiredSize:${this.audioWriter.desiredSize}`
);
return await Promise.all([
new Promise((resolve) => (this.stream.oninactive = resolve)),
new Promise((resolve) => (this.ac.onstatechange = resolve)),
]);
}
}, },
abort(e) { abort(e) {
console.error(e.message); console.error(e.message);
), ),
]); ]);
this.resolve( this.resolve(
this.recorder ? await this.data.arrayBuffer() : 'Done streaming.'
this.recorder
? this.data && (await this.data.arrayBuffer())
: 'Done streaming.'
); );
return this.promise; return this.promise;
} catch (err) { } catch (err) {

Loading…
Cancel
Save