From e0f52162eabc901d994f644ba9dcdcc3bf70776d Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Tue, 14 Aug 2018 12:12:59 +0100 Subject: [PATCH] voice: allow for streaming silence to avoid audio glitches with repeated pausing/resuming (#2354) --- .../voice/dispatcher/StreamDispatcher.js | 31 ++++++++++++++----- src/client/voice/util/Silence.js | 11 +++++++ 2 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 src/client/voice/util/Silence.js diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js index e79531b02..04e32f3a9 100644 --- a/src/client/voice/dispatcher/StreamDispatcher.js +++ b/src/client/voice/dispatcher/StreamDispatcher.js @@ -2,6 +2,7 @@ const VolumeInterface = require('../util/VolumeInterface'); const { Writable } = require('stream'); const secretbox = require('../util/Secretbox'); +const Silence = require('../util/Silence'); const FRAME_LENGTH = 20; const CHANNELS = 2; @@ -41,6 +42,7 @@ class StreamDispatcher extends Writable { this.player = player; this.streamOptions = streamOptions; this.streams = streams; + this.streams.silence = new Silence(); this._nonce = 0; this._nonceBuffer = Buffer.alloc(24); @@ -59,6 +61,7 @@ class StreamDispatcher extends Writable { this.broadcast = this.streams.broadcast; this._pausedTime = 0; + this._silentPausedTime = 0; this.count = 0; this.on('finish', () => { @@ -119,12 +122,17 @@ class StreamDispatcher extends Writable { /** * Pauses playback + * @param {boolean} [silence=false] Whether to play silence while paused to prevent audio glitches */ - pause() { + pause(silence = false) { if (this.paused) return; if (this.streams.opus) this.streams.opus.unpipe(this); - if (this._writeCallback) this._writeCallback(); - this._setSpeaking(false); + if (silence) { + this.streams.silence.pipe(this); + this._silence = true; + } else { + this._setSpeaking(false); + } this.pausedSince = Date.now(); } @@ -138,15 +146,23 @@ class StreamDispatcher extends Writable { * Total time that this dispatcher has been paused * @type {number} */ - get pausedTime() { return this._pausedTime + (this.paused ? Date.now() - this.pausedSince : 0); } + get pausedTime() { + return this._silentPausedTime + this._pausedTime + (this.paused ? Date.now() - this.pausedSince : 0); + } /** * Resumes playback */ resume() { if (!this.pausedSince) return; + this.streams.silence.unpipe(this); if (this.streams.opus) this.streams.opus.pipe(this); - this._pausedTime += Date.now() - this.pausedSince; + if (this._silence) { + this._silentPausedTime += Date.now() - this.pausedSince; + this._silence = false; + } else { + this._pausedTime += Date.now() - this.pausedSince; + } this.pausedSince = null; if (typeof this._writeCallback === 'function') this._writeCallback(); } @@ -207,11 +223,10 @@ class StreamDispatcher extends Writable { this._writeCallback = null; done(); }; - if (this.pausedSince) return; if (!this.streams.broadcast) { - const next = FRAME_LENGTH + (this.count * FRAME_LENGTH) - (Date.now() - this.startTime - this.pausedTime); + const next = FRAME_LENGTH + (this.count * FRAME_LENGTH) - (Date.now() - this.startTime - this._pausedTime); setTimeout(() => { - if (!this.pausedSince && this._writeCallback) this._writeCallback(); + if ((!this.pausedSince || this._silence) && this._writeCallback) this._writeCallback(); }, next); } this._sdata.sequence++; diff --git a/src/client/voice/util/Silence.js b/src/client/voice/util/Silence.js new file mode 100644 index 000000000..b9643da17 --- /dev/null +++ b/src/client/voice/util/Silence.js @@ -0,0 +1,11 @@ +const { Readable } = require('stream'); + +const SILENCE_FRAME = Buffer.from([0xF8, 0xFF, 0xFE]); + +class Silence extends Readable { + _read() { + this.push(SILENCE_FRAME); + } +} + +module.exports = Silence;