Skip to content

Commit

Permalink
feat(voice): expose methods to directly send packets
Browse files Browse the repository at this point in the history
Low-level methods for sending Opus frames and UDP packets
  • Loading branch information
abalabahaha committed Nov 15, 2021
1 parent 470ca79 commit dcf8ff1
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 40 deletions.
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3624,6 +3624,8 @@ declare namespace Eris {
receive(type: "opus" | "pcm"): VoiceDataStream;
registerReceiveEventHandler(): void;
resume(): void;
sendAudioFrame(frame: Buffer): void;
sendUDPPacket(packet: Buffer): void;
sendWS(op: number, data: Record<string, unknown>): void;
setSpeaking(value: boolean): void;
setVolume(volume: number): void;
Expand Down
2 changes: 1 addition & 1 deletion lib/voice/SharedStream.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ class SharedStream extends EventEmitter {

this.voiceConnections.forEach((connection) => {
if(connection.ready && this.current.buffer) {
connection._sendAudioPacket(this.current.buffer);
connection._sendAudioFrame(this.current.buffer);
}
});
this.current.playTime += this.current.options.frameDuration;
Expand Down
87 changes: 48 additions & 39 deletions lib/voice/VoiceConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ try {
let Sodium = null;
let NaCl = null;

const MAX_FRAME_SIZE = 1276 * 3;
const ENCRYPTION_MODE = "xsalsa20_poly1305";
const MAX_FRAME_SIZE = 1276 * 3;
const SILENCE_FRAME = Buffer.from([0xF8, 0xFF, 0xFE]);

const converterCommand = {
cmd: null,
Expand Down Expand Up @@ -265,7 +266,7 @@ class VoiceConnection extends EventEmitter {
udpMessage.writeUInt16BE(0x1, 0);
udpMessage.writeUInt16BE(70, 2);
udpMessage.writeUInt32BE(this.ssrc, 4);
this._sendPacket(udpMessage);
this.sendUDPPacket(udpMessage);
break;
}
case VoiceOPCodes.SESSION_DESCRIPTION: {
Expand Down Expand Up @@ -448,7 +449,7 @@ class VoiceConnection extends EventEmitter {
if(this.udpSocket) {
// NAT/connection table keep-alive
const udpMessage = Buffer.from([0x80, 0xC8, 0x0, 0x0]);
this._sendPacket(udpMessage);
this.sendUDPPacket(udpMessage);
}
}

Expand Down Expand Up @@ -632,6 +633,37 @@ class VoiceConnection extends EventEmitter {
}
}

/**
* Send a packet containing an Opus audio frame
* @arg {Buffer} frame The Opus audio frame
*/
sendAudioFrame(frame) {
this.timestamp += this.current.options.frameSize;
if(this.timestamp >= 4294967295) {
this.timestamp -= 4294967295;
}

if(++this.sequence >= 65536) {
this.sequence -= 65536;
}

return this._sendAudioFrame(frame);
}

/**
* Send a packet through the connection's UDP socket. The packet is dropped if the socket isn't established
* @arg {Buffer} packet The packet data
*/
sendUDPPacket(packet) {
if(this.udpSocket) {
try {
this.udpSocket.send(packet, 0, packet.length, this.udpPort, this.udpIP);
} catch(e) {
this.emit("error", e);
}
}
}

sendWS(op, data) {
if(this.ws && this.ws.readyState === WebSocket.OPEN) {
data = JSON.stringify({op: op, d: data});
Expand Down Expand Up @@ -677,16 +709,7 @@ class VoiceConnection extends EventEmitter {

if(this.secret) {
for(let i = 0; i < 5; i++) {
this.timestamp += this.frameSize;
if(this.timestamp >= 4294967295) {
this.timestamp -= 4294967295;
}

if(++this.sequence >= 65536) {
this.sequence -= 65536;
}

this._sendAudioPacket(Buffer.from([0xF8, 0xFF, 0xFE]));
this.sendAudioFrame(SILENCE_FRAME);
}
}
this.playing = false;
Expand Down Expand Up @@ -757,15 +780,6 @@ class VoiceConnection extends EventEmitter {
return this.stopPlaying();
}

this.timestamp += this.current.options.frameSize;
if(this.timestamp >= 4294967295) {
this.timestamp -= 4294967295;
}

if(++this.sequence >= 65536) {
this.sequence -= 65536;
}

if((this.current.buffer = this.piper.getDataPacket())) {
if(this.current.startTime === 0) {
this.current.startTime = Date.now();
Expand All @@ -789,43 +803,38 @@ class VoiceConnection extends EventEmitter {
return this.stopPlaying();
}

this._sendAudioPacket(this.current.buffer);
this.sendAudioFrame(this.current.buffer);
this.current.playTime += this.current.options.frameDuration;
this.current.timeout = setTimeout(this._send, this.current.startTime + this.current.pausedTime + this.current.playTime - Date.now());
}

_sendAudioPacket(audio) {
_sendAudioFrame(frame) {
this.sendNonce.writeUInt16BE(this.sequence, 2);
this.sendNonce.writeUInt32BE(this.timestamp, 4);

if(Sodium) {
const MACBYTES = Sodium.crypto_secretbox_MACBYTES;
const length = audio.length + MACBYTES;
const length = frame.length + MACBYTES;
this.sendBuffer.fill(0, 12, 12 + MACBYTES);
audio.copy(this.sendBuffer, 12 + MACBYTES);
frame.copy(this.sendBuffer, 12 + MACBYTES);
Sodium.crypto_secretbox_easy(this.sendBuffer.subarray(12, 12 + length), this.sendBuffer.subarray(12 + MACBYTES, 12 + length), this.sendNonce, this.secret);
this.sendNonce.copy(this.sendBuffer, 0, 0, 12);
return this._sendPacket(this.sendBuffer.subarray(0, 12 + length));
return this.sendUDPPacket(this.sendBuffer.subarray(0, 12 + length));
} else {
const BOXZEROBYTES = NaCl.lowlevel.crypto_secretbox_BOXZEROBYTES;
const ZEROBYTES = NaCl.lowlevel.crypto_secretbox_ZEROBYTES;
const length = audio.length + BOXZEROBYTES;
const length = frame.length + BOXZEROBYTES;
this.sendBuffer.fill(0, BOXZEROBYTES, BOXZEROBYTES + ZEROBYTES);
audio.copy(this.sendBuffer, BOXZEROBYTES + ZEROBYTES);
NaCl.lowlevel.crypto_secretbox(this.sendBuffer, this.sendBuffer.subarray(BOXZEROBYTES), ZEROBYTES + audio.length, this.sendNonce, this.secret);
frame.copy(this.sendBuffer, BOXZEROBYTES + ZEROBYTES);
NaCl.lowlevel.crypto_secretbox(this.sendBuffer, this.sendBuffer.subarray(BOXZEROBYTES), ZEROBYTES + frame.length, this.sendNonce, this.secret);
this.sendNonce.copy(this.sendBuffer, BOXZEROBYTES - 12, 0, 12);
return this._sendPacket(this.sendBuffer.subarray(BOXZEROBYTES - 12, BOXZEROBYTES + length));
return this.sendUDPPacket(this.sendBuffer.subarray(BOXZEROBYTES - 12, BOXZEROBYTES + length));
}
}

_sendPacket(packet) {
if(this.udpSocket) {
try {
this.udpSocket.send(packet, 0, packet.length, this.udpPort, this.udpIP);
} catch(e) {
this.emit("error", e);
}
}
// [DEPRECATED]
_sendAudioPacket(audio) {
return this._sendAudioFrame(audio);
}

[util.inspect.custom]() {
Expand Down

0 comments on commit dcf8ff1

Please sign in to comment.