From bd0228bc71b5636f88bdf24fd5c28d7d775bc395 Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Fri, 6 Nov 2015 20:46:11 +0000 Subject: [PATCH 01/23] Started basic work on voice --- lib/Client/InternalClient.js | 1 + lib/Voice/VoiceConnection.js | 9 +++++++++ src/Client/InternalClient.js | 1 + src/Voice/VoiceConnection.js | 9 +++++++++ 4 files changed, 20 insertions(+) create mode 100644 lib/Voice/VoiceConnection.js create mode 100644 src/Voice/VoiceConnection.js diff --git a/lib/Client/InternalClient.js b/lib/Client/InternalClient.js index e1ab0193a..19a46bd0b 100644 --- a/lib/Client/InternalClient.js +++ b/lib/Client/InternalClient.js @@ -43,6 +43,7 @@ var InternalClient = (function () { this.channels = new Cache(); this.servers = new Cache(); this.private_channels = new Cache(); + this.voiceConnections = new Cache(); this.resolver = new Resolver(this); } diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js new file mode 100644 index 000000000..19ac044dd --- /dev/null +++ b/lib/Voice/VoiceConnection.js @@ -0,0 +1,9 @@ +"use strict"; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var VoiceConnection = function VoiceConnection() { + _classCallCheck(this, VoiceConnection); +}; + +module.exports = VoiceConnection; \ No newline at end of file diff --git a/src/Client/InternalClient.js b/src/Client/InternalClient.js index 13bc9ae38..32407bd00 100644 --- a/src/Client/InternalClient.js +++ b/src/Client/InternalClient.js @@ -39,6 +39,7 @@ class InternalClient { this.channels = new Cache(); this.servers = new Cache(); this.private_channels = new Cache(); + this.voiceConnections = new Cache(); this.resolver = new Resolver(this); } // def createServer diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js new file mode 100644 index 000000000..bb2450d53 --- /dev/null +++ b/src/Voice/VoiceConnection.js @@ -0,0 +1,9 @@ +"use strict"; + +class VoiceConnection{ + constructor(){ + + } +} + +module.exports = VoiceConnection; \ No newline at end of file From cb5452f072d33c8de0bb2c7648056b467243d9a5 Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Fri, 6 Nov 2015 21:26:36 +0000 Subject: [PATCH 02/23] Added rudimentary voice joining --- lib/Client/InternalClient.js | 59 ++++++++++++++++++++++++++++++++- lib/Client/Resolver/Resolver.js | 8 +++++ src/Client/InternalClient.js | 58 +++++++++++++++++++++++++++++++- src/Client/Resolver/Resolver.js | 8 +++++ 4 files changed, 131 insertions(+), 2 deletions(-) diff --git a/lib/Client/InternalClient.js b/lib/Client/InternalClient.js index 19a46bd0b..5178c1bef 100644 --- a/lib/Client/InternalClient.js +++ b/lib/Client/InternalClient.js @@ -22,7 +22,8 @@ var User = require("../Structures/User.js"), Server = require("../Structures/Server.js"), Message = require("../Structures/Message.js"), Role = require("../Structures/Role.js"), - Invite = require("../Structures/Invite.js"); + Invite = require("../Structures/Invite.js"), + VoiceConnection = require("../Voice/VoiceConnection.js"); var zlib; @@ -47,6 +48,62 @@ var InternalClient = (function () { this.resolver = new Resolver(this); } + //def joinVoiceChannel() + + InternalClient.prototype.joinVoiceChannel = function joinVoiceChannel(chann) { + var self = this; + return new Promise(function (resolve, reject) { + + var channel = self.resolver.resolveVoiceChannel(chann); + + if (channel) { + if (self.voiceConnections[channel]) { + + self.voiceConnections[channel] = {}; + + var session, + token, + serverID, + endpoint, + fired = 0; + + var check = function check(m) { + var data = JSON.parse(m); + + if (data.t === "VOICE_STATE_UPDATE") { + session = data.d.session_id; + fired++; + } else if (data.t === "VOICE_SERVER_UPDATE") { + token = data.d.token; + serverID = data.d.guild_id; + endpoint = data.d.endpoint; + fired++; + } + + if (fired >= 2) { + self.websocket.removeListener('message', check); + } + }; + + self.websocket.on("message", check); + self.sendWS({ + op: 4, + d: { + "guild_id": serverID, + "channel_id": channel.id, + "self_mute": false, + "self_deaf": false + } + }); + } else { + reject(new Error("voice channel connection exists")); + } + } else { + reject(new Error("voice channel does not exist")); + } + }); + }; + // def createServer InternalClient.prototype.createServer = function createServer(name) { diff --git a/lib/Client/Resolver/Resolver.js b/lib/Client/Resolver/Resolver.js index ceec4167f..a0a6e55ca 100644 --- a/lib/Client/Resolver/Resolver.js +++ b/lib/Client/Resolver/Resolver.js @@ -116,6 +116,14 @@ var Resolver = (function () { return found; }; + Resolver.prototype.resolveVoiceChannel = function resolveVoiceChannel(resource) { + // resolveChannel will also work but this is more apt + if (resource instanceof VoiceChannel) { + return resource; + } + return null; + }; + Resolver.prototype.resolveChannel = function resolveChannel(resource) { /* accepts a Message, Channel, Server, String ID, User diff --git a/src/Client/InternalClient.js b/src/Client/InternalClient.js index 32407bd00..6d262aa31 100644 --- a/src/Client/InternalClient.js +++ b/src/Client/InternalClient.js @@ -20,7 +20,8 @@ var User = require("../Structures/User.js"), Server = require("../Structures/Server.js"), Message = require("../Structures/Message.js"), Role = require("../Structures/Role.js"), - Invite = require("../Structures/Invite.js"); + Invite = require("../Structures/Invite.js"), + VoiceConnection = require("../Voice/VoiceConnection.js"); var zlib; @@ -42,6 +43,61 @@ class InternalClient { this.voiceConnections = new Cache(); this.resolver = new Resolver(this); } + + //def joinVoiceChannel() + joinVoiceChannel(chann){ + var self = this; + return new Promise((resolve, reject) => { + + var channel = self.resolver.resolveVoiceChannel(chann); + + if(channel){ + if(self.voiceConnections[channel]){ + + self.voiceConnections[channel] = {}; + + var session, token, serverID, endpoint, fired = 0; + + var check = (m) => { + var data = JSON.parse(m); + + if(data.t === "VOICE_STATE_UPDATE"){ + session = data.d.session_id; + fired++; + }else if(data.t === "VOICE_SERVER_UPDATE"){ + token = data.d.token; + serverID = data.d.guild_id; + endpoint = data.d.endpoint; + fired++; + } + + if(fired >= 2){ + self.websocket.removeListener('message', check); + } + + }; + + self.websocket.on("message", check); + self.sendWS({ + op : 4, + d : { + "guild_id" : serverID, + "channel_id" : channel.id, + "self_mute" : false, + "self_deaf" : false + } + }); + + }else{ + reject(new Error("voice channel connection exists")); + } + }else{ + reject(new Error("voice channel does not exist")); + } + + }); + } + // def createServer createServer(name, region = "london") { var self = this; diff --git a/src/Client/Resolver/Resolver.js b/src/Client/Resolver/Resolver.js index 1d7252715..6dc88e457 100644 --- a/src/Client/Resolver/Resolver.js +++ b/src/Client/Resolver/Resolver.js @@ -98,6 +98,14 @@ class Resolver { return found; } + + resolveVoiceChannel(resource) { + // resolveChannel will also work but this is more apt + if(resource instanceof VoiceChannel){ + return resource; + } + return null; + } resolveChannel(resource) { /* From 4118252ace543d4babadb99b8ee251efa816d848 Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Fri, 6 Nov 2015 21:36:58 +0000 Subject: [PATCH 03/23] Fixed smallish bugs --- lib/Client/InternalClient.js | 12 +++++------ lib/Voice/VoiceConnection.js | 13 ++++++++++- lib/index.js | 42 ++++++++++++++++-------------------- src/Client/InternalClient.js | 13 ++++++----- src/Voice/VoiceConnection.js | 13 +++++++++-- src/index.js | 22 +++++++------------ 6 files changed, 60 insertions(+), 55 deletions(-) diff --git a/lib/Client/InternalClient.js b/lib/Client/InternalClient.js index 5178c1bef..556fa336e 100644 --- a/lib/Client/InternalClient.js +++ b/lib/Client/InternalClient.js @@ -57,13 +57,13 @@ var InternalClient = (function () { var channel = self.resolver.resolveVoiceChannel(chann); if (channel) { - if (self.voiceConnections[channel]) { + if (!self.voiceConnections[channel]) { self.voiceConnections[channel] = {}; var session, token, - serverID, + server = channel.server, endpoint, fired = 0; @@ -72,14 +72,12 @@ var InternalClient = (function () { if (data.t === "VOICE_STATE_UPDATE") { session = data.d.session_id; - fired++; } else if (data.t === "VOICE_SERVER_UPDATE") { token = data.d.token; - serverID = data.d.guild_id; endpoint = data.d.endpoint; - fired++; - } + self.voiceConnections[channel] = new VoiceConnection(channel, self.client, session, token, server, endpoint); + } if (fired >= 2) { self.websocket.removeListener('message', check); } @@ -89,7 +87,7 @@ var InternalClient = (function () { self.sendWS({ op: 4, d: { - "guild_id": serverID, + "guild_id": server.id, "channel_id": channel.id, "self_mute": false, "self_deaf": false diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index 19ac044dd..2437d9551 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -2,8 +2,19 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } -var VoiceConnection = function VoiceConnection() { +var WebSocket = require("ws"); +var dgram = require("dgram"); + +var VoiceConnection = function VoiceConnection(channel, client, session, token, server, endpoint) { _classCallCheck(this, VoiceConnection); + + this.voiceChannel = channel; + this.client = client; + this.session = session; + this.token = token; + this.server = server; + this.endpoint = endpoint; + console.log("I was instantiated!"); }; module.exports = VoiceConnection; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index 2c2c1a317..745969e80 100644 --- a/lib/index.js +++ b/lib/index.js @@ -4,6 +4,8 @@ module.exports = { Client: require("./Client/Client.js") }; +var VoiceChannel = require("./Structures/VoiceChannel.js"); + var a = new module.exports.Client(); a.on("debug", function (m) { return console.log("[debug]", m); @@ -14,38 +16,30 @@ a.on("warn", function (m) { var start = Date.now(); a.on("message", function (m) { if (m.content === "$$$") { - a.internal.setTopic(m.channel, "a channel topic!"); - } -}); -a.on("userTypingStart", function (user, chan) { - console.log(user.username + " typing"); -}); -a.on("userTypingStop", function (user, chan) { - console.log(user.username + " stopped typing"); -}); -a.on("ready", function () { - for (var _iterator = a.internal.servers, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { - var _ref; + for (var _iterator = m.channel.server.channels, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { + var _ref; - if (_isArray) { - if (_i >= _iterator.length) break; - _ref = _iterator[_i++]; - } else { - _i = _iterator.next(); - if (_i.done) break; - _ref = _i.value; - } + if (_isArray) { + if (_i >= _iterator.length) break; + _ref = _iterator[_i++]; + } else { + _i = _iterator.next(); + if (_i.done) break; + _ref = _i.value; + } - var server = _ref; + var channel = _ref; - if (server.name === "craptown") { - a.leaveServer(server); + if (channel instanceof VoiceChannel) { + a.internal.joinVoiceChannel(channel)["catch"](error); + break; + } } } }); function error(e) { - throw e; + console.log(e); process.exit(0); } diff --git a/src/Client/InternalClient.js b/src/Client/InternalClient.js index 6d262aa31..15b69bd49 100644 --- a/src/Client/InternalClient.js +++ b/src/Client/InternalClient.js @@ -52,25 +52,24 @@ class InternalClient { var channel = self.resolver.resolveVoiceChannel(chann); if(channel){ - if(self.voiceConnections[channel]){ + if(!self.voiceConnections[channel]){ self.voiceConnections[channel] = {}; - var session, token, serverID, endpoint, fired = 0; + var session, token, server = channel.server, endpoint, fired = 0; var check = (m) => { var data = JSON.parse(m); if(data.t === "VOICE_STATE_UPDATE"){ session = data.d.session_id; - fired++; }else if(data.t === "VOICE_SERVER_UPDATE"){ token = data.d.token; - serverID = data.d.guild_id; endpoint = data.d.endpoint; - fired++; + + self.voiceConnections[channel] = new VoiceConnection(channel, self.client, session, token, server, endpoint); + } - if(fired >= 2){ self.websocket.removeListener('message', check); } @@ -81,7 +80,7 @@ class InternalClient { self.sendWS({ op : 4, d : { - "guild_id" : serverID, + "guild_id" : server.id, "channel_id" : channel.id, "self_mute" : false, "self_deaf" : false diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index bb2450d53..8c9a46323 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -1,8 +1,17 @@ "use strict"; +var WebSocket = require("ws"); +var dgram = require("dgram"); + class VoiceConnection{ - constructor(){ - + constructor(channel, client, session, token, server, endpoint){ + this.voiceChannel = channel; + this.client = client; + this.session = session; + this.token = token; + this.server = server; + this.endpoint = endpoint; + console.log("I was instantiated!"); } } diff --git a/src/index.js b/src/index.js index 446644412..87e136ea5 100644 --- a/src/index.js +++ b/src/index.js @@ -2,31 +2,25 @@ module.exports = { Client : require("./Client/Client.js") } +var VoiceChannel = require("./Structures/VoiceChannel.js"); + var a = new module.exports.Client(); a.on("debug", (m) => console.log("[debug]",m)); a.on("warn", (m) => console.log("[warn]", m)); var start = Date.now(); a.on("message", m => { if(m.content === "$$$"){ - a.internal.setTopic(m.channel, "a channel topic!"); - } -}); -a.on("userTypingStart", (user, chan) => { - console.log(user.username + " typing"); -}); -a.on("userTypingStop", (user, chan) => { - console.log(user.username + " stopped typing"); -}); -a.on("ready", () => { - for(var server of a.internal.servers){ - if(server.name === "craptown"){ - a.leaveServer(server); + for(var channel of m.channel.server.channels){ + if(channel instanceof VoiceChannel){ + a.internal.joinVoiceChannel(channel).catch(error); + break; + } } } }); function error(e){ - throw e; + console.log(e); process.exit(0); } From ab3cdf8a39233fd5a3c4fe2861da722f61441ba7 Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Fri, 6 Nov 2015 21:53:45 +0000 Subject: [PATCH 04/23] Voice now properly joins --- lib/Voice/VoiceConnection.js | 115 +++++++++++++++++++++++++++++++---- src/Voice/VoiceConnection.js | 97 +++++++++++++++++++++++++++-- 2 files changed, 196 insertions(+), 16 deletions(-) diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index 2437d9551..8883372d0 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -3,18 +3,111 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var WebSocket = require("ws"); -var dgram = require("dgram"); +var dns = require("dns"); +var udp = require("dgram"); -var VoiceConnection = function VoiceConnection(channel, client, session, token, server, endpoint) { - _classCallCheck(this, VoiceConnection); +var VoiceConnection = (function () { + function VoiceConnection(channel, client, session, token, server, endpoint) { + _classCallCheck(this, VoiceConnection); - this.voiceChannel = channel; - this.client = client; - this.session = session; - this.token = token; - this.server = server; - this.endpoint = endpoint; - console.log("I was instantiated!"); -}; + this.voiceChannel = channel; + this.client = client; + this.session = session; + this.token = token; + this.server = server; + this.endpoint = endpoint.replace(":80", ""); + this.vWS = null; // vWS means voice websocket + this.ready = false; + this.vWSData = {}; + this.init(); + } + + VoiceConnection.prototype.init = function init() { + var _this = this; + + var self = this; + dns.lookup(this.endpoint, function (err, address, family) { + self.endpoint = address; + var vWS = self.vWS = new WebSocket("wss://" + _this.endpoint, null, { rejectUnauthorized: false }); + var udpClient = self.udp = udp.createSocket("udp4"); + + var firstPacket = true; + + var discordIP = "", + discordPort = ""; + + udpClient.bind({ exclusive: true }); + udpClient.on('message', function (msg, rinfo) { + var buffArr = JSON.parse(JSON.stringify(msg)).data; + if (firstPacket === true) { + for (var i = 4; i < buffArr.indexOf(0, i); i++) { + discordIP += String.fromCharCode(buffArr[i]); + } + discordPort = msg.readUIntLE(msg.length - 2, 2).toString(10); + + var wsDiscPayload = { + "op": 1, + "d": { + "protocol": "udp", + "data": { + "address": discordIP, + "port": Number(discordPort), + "mode": self.vWSData.modes[0] //Plain + } + } + }; + console.log("success!!!"); + vWS.send(JSON.stringify(wsDiscPayload)); + firstPacket = false; + } + }); + + vWS.on("open", function () { + vWS.send(JSON.stringify({ + op: 0, + d: { + server_id: self.server.id, + user_id: self.client.internal.user.id, + session_id: self.session, + token: self.token + } + })); + }); + + var KAI; + + vWS.on("message", function (msg) { + var data = JSON.parse(msg); + switch (data.op) { + case 2: + self.vWSData = data.d; + + KAI = setInterval(function () { + vWS.send(JSON.stringify({ + op: 3, + d: null + })); + }, data.d.heartbeat_interval); + + var udpPacket = new Buffer(70); + udpPacket.writeUIntBE(data.d.ssrc, 0, 4); + udpClient.send(udpPacket, 0, udpPacket.length, data.d.port, self.endpoint, function (err) { + console.log("err", err); + }); + break; + case 4: + + self.ready = true; + self.mode = data.d.mode; + console.log("ready!!!"); + + break; + } + }); + }); + }; + + return VoiceConnection; +})(); module.exports = VoiceConnection; \ No newline at end of file diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index 8c9a46323..2ea72757b 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -1,17 +1,104 @@ "use strict"; var WebSocket = require("ws"); -var dgram = require("dgram"); +var dns = require("dns"); +var udp = require("dgram"); -class VoiceConnection{ - constructor(channel, client, session, token, server, endpoint){ +class VoiceConnection { + constructor(channel, client, session, token, server, endpoint) { this.voiceChannel = channel; this.client = client; this.session = session; this.token = token; this.server = server; - this.endpoint = endpoint; - console.log("I was instantiated!"); + this.endpoint = endpoint.replace(":80", ""); + this.vWS = null; // vWS means voice websocket + this.ready = false; + this.vWSData = {}; + this.init(); + } + + init() { + var self = this; + dns.lookup(this.endpoint, (err, address, family) => { + self.endpoint = address; + var vWS = self.vWS = new WebSocket("wss://" + this.endpoint, null, { rejectUnauthorized: false }); + var udpClient = self.udp = udp.createSocket("udp4"); + + var firstPacket = true; + + var discordIP = "", discordPort = ""; + + udpClient.bind({ exclusive: true }); + udpClient.on('message', function (msg, rinfo) { + var buffArr = JSON.parse(JSON.stringify(msg)).data; + if (firstPacket === true) { + for (var i = 4; i < buffArr.indexOf(0, i); i++) { + discordIP += String.fromCharCode(buffArr[i]); + } + discordPort = msg.readUIntLE(msg.length - 2, 2).toString(10); + + var wsDiscPayload = { + "op": 1, + "d": { + "protocol": "udp", + "data": { + "address": discordIP, + "port": Number(discordPort), + "mode": self.vWSData.modes[0] //Plain + } + } + } + console.log("success!!!"); + vWS.send(JSON.stringify(wsDiscPayload)); + firstPacket = false; + } + }); + + vWS.on("open", () => { + vWS.send(JSON.stringify({ + op: 0, + d: { + server_id: self.server.id, + user_id: self.client.internal.user.id, + session_id: self.session, + token: self.token + } + })); + }); + + var KAI; + + vWS.on("message", (msg) => { + var data = JSON.parse(msg); + switch (data.op) { + case 2: + self.vWSData = data.d; + + KAI = setInterval(() => { + vWS.send(JSON.stringify({ + op : 3, + d : null + })); + }, data.d.heartbeat_interval); + + var udpPacket = new Buffer(70); + udpPacket.writeUIntBE(data.d.ssrc, 0, 4); + udpClient.send(udpPacket, 0, udpPacket.length, data.d.port, self.endpoint, err => { + console.log("err", err); + }); + break; + case 4: + + self.ready = true; + self.mode = data.d.mode; + console.log("ready!!!"); + + break; + } + }); + + }); } } From 4bab0a6bbc581374a0404bd05590806e7a66b7a1 Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sat, 7 Nov 2015 14:45:45 +0000 Subject: [PATCH 05/23] Bad wav support --- lib/Voice/VoiceConnection.js | 85 +++++++++++++++++++++++++++++ src/Voice/VoiceConnection.js | 101 ++++++++++++++++++++++++++++++++--- 2 files changed, 179 insertions(+), 7 deletions(-) diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index 8883372d0..ca811dcb4 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -5,6 +5,27 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons var WebSocket = require("ws"); var dns = require("dns"); var udp = require("dgram"); +var Lame = require("lame"); +var Opus = require('node-opus'); +var Wav = require('wav'); +var fs = require("fs"); + +function VoicePacket(packet, sequence, timestamp, ssrc) { + var audioBuffer = packet; + var retBuff = new Buffer(audioBuffer.length + 12); + retBuff.fill(0); + retBuff[0] = 0x80; + retBuff[1] = 0x78; + retBuff.writeUIntBE(sequence, 2, 2); + retBuff.writeUIntBE(timestamp, 4, 4); + retBuff.writeUIntBE(ssrc, 8, 4); + + for (var i = 0; i < audioBuffer.length; i++) { + retBuff[i + 12] = audioBuffer[i]; + } + + return retBuff; +} var VoiceConnection = (function () { function VoiceConnection(channel, client, session, token, server, endpoint) { @@ -22,6 +43,68 @@ var VoiceConnection = (function () { this.init(); } + VoiceConnection.prototype.test = function test() { + var self = this; + var startTime = Date.now(); + var cnt = 0; + function sendAudio(sequence, timestamp, opusEncoder, wavOutput, udpClient, vWS) { + cnt++; + var buff = wavOutput.read(1920); + if (buff && buff.length === 1920) { + sequence + 20 < 65535 ? sequence += 1 : sequence = 0; + timestamp + 9600 < 4294967295 ? timestamp += 960 : timestamp = 0; + + var encoded = opusEncoder.encode(buff, 1920); + var audioPacket = VoicePacket(encoded, sequence, timestamp, self.vWSData.ssrc); + + var nextTime = startTime + cnt * 20; + + udpClient.send(audioPacket, 0, audioPacket.length, self.vWSData.port, self.endpoint, function (err) {}); + setTimeout(function () { + sendAudio(sequence, timestamp, opusEncoder, wavOutput, udpClient, vWS); + }, 20 + (nextTime - new Date().getTime())); + } else { + var speaking = { + "op": 5, + "d": { + "speaking": false, + "delay": 0 + } + }; + vWS.send(JSON.stringify(speaking)); + } + } + + var speaking = { + "op": 5, + "d": { + "speaking": true, + "delay": 0 + } + }; + + var vWS = self.vWS; + var stream = fs.createReadStream("c:/users/amish/desktop/audio.wav"); + + vWS.send(JSON.stringify(speaking)); + + var rate = 48000; + var opusEncoder = new Opus.OpusEncoder(48000, 1); + var wavReader = new Wav.Reader(); + wavReader.on('format', function (format) { + console.log(format); + }); + var wavOutput = stream.pipe(wavReader); + + var sequence = 0; + var timestamp = 0; + + wavOutput.on('readable', function () { + console.log("readable!"); + sendAudio(sequence, timestamp, opusEncoder, wavOutput, self.udp, vWS); + }); + }; + VoiceConnection.prototype.init = function init() { var _this = this; @@ -101,6 +184,8 @@ var VoiceConnection = (function () { self.mode = data.d.mode; console.log("ready!!!"); + self.test(); + break; } }); diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index 2ea72757b..799e7b99e 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -3,6 +3,27 @@ var WebSocket = require("ws"); var dns = require("dns"); var udp = require("dgram"); +var Lame = require("lame"); +var Opus = require('node-opus'); +var Wav = require('wav'); +var fs = require("fs"); + +function VoicePacket(packet, sequence, timestamp, ssrc) { + var audioBuffer = packet; + var retBuff = new Buffer(audioBuffer.length + 12); + retBuff.fill(0); + retBuff[0] = 0x80; + retBuff[1] = 0x78; + retBuff.writeUIntBE(sequence, 2, 2); + retBuff.writeUIntBE(timestamp, 4, 4); + retBuff.writeUIntBE(ssrc, 8, 4); + + for (var i = 0; i < audioBuffer.length; i++) { + retBuff[i + 12] = audioBuffer[i]; + } + + return retBuff; +} class VoiceConnection { constructor(channel, client, session, token, server, endpoint) { @@ -18,6 +39,70 @@ class VoiceConnection { this.init(); } + test() { + var self = this; + var startTime = Date.now(); + var cnt = 0; + function sendAudio(sequence, timestamp, opusEncoder, wavOutput, udpClient, vWS) { + cnt++; + var buff = wavOutput.read(1920); + if (buff && buff.length === 1920) { + sequence + 20 < 65535 ? sequence += 1 : sequence = 0; + timestamp + 9600 < 4294967295 ? timestamp += 960 : timestamp = 0; + + var encoded = opusEncoder.encode(buff, 1920); + var audioPacket = VoicePacket(encoded, sequence, timestamp, self.vWSData.ssrc); + + var nextTime = startTime + cnt * 20; + + udpClient.send(audioPacket, 0, audioPacket.length, self.vWSData.port, self.endpoint, function (err) { }); + setTimeout(function () { + sendAudio(sequence, timestamp, opusEncoder, wavOutput, udpClient, vWS); + }, 20 + (nextTime - new Date().getTime())); + } else { + var speaking = { + "op": 5, + "d": { + "speaking": false, + "delay": 0 + } + } + vWS.send(JSON.stringify(speaking)); + } + } + + var speaking = { + "op": 5, + "d": { + "speaking": true, + "delay": 0 + } + } + + var vWS = self.vWS; + var stream = fs.createReadStream("c:/users/amish/desktop/audio.wav"); + + vWS.send(JSON.stringify(speaking)); + + var rate = 48000; + var opusEncoder = new Opus.OpusEncoder(48000, 1); + var wavReader = new Wav.Reader(); +wavReader.on('format', function (format) { +console.log(format); +}); + var wavOutput = stream.pipe(wavReader); + + var sequence = 0; + var timestamp = 0; + + wavOutput.on('readable', function () { + console.log("readable!"); + sendAudio(sequence, timestamp, opusEncoder, wavOutput, self.udp, vWS); + }); + + + } + init() { var self = this; dns.lookup(this.endpoint, (err, address, family) => { @@ -74,26 +159,28 @@ class VoiceConnection { switch (data.op) { case 2: self.vWSData = data.d; - + KAI = setInterval(() => { vWS.send(JSON.stringify({ - op : 3, - d : null + op: 3, + d: null })); }, data.d.heartbeat_interval); - + var udpPacket = new Buffer(70); udpPacket.writeUIntBE(data.d.ssrc, 0, 4); udpClient.send(udpPacket, 0, udpPacket.length, data.d.port, self.endpoint, err => { - console.log("err", err); + console.log("err", err); }); break; case 4: - + self.ready = true; self.mode = data.d.mode; console.log("ready!!!"); - + + self.test(); + break; } }); From be1f5064c24e68967eadbfcc65af0ee1ef4e994c Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sat, 7 Nov 2015 17:21:05 +0000 Subject: [PATCH 06/23] AWESOME MAGIC --- lib/Voice/AudioEncoder.js | 48 ++++++++++ lib/Voice/VoiceConnection.js | 136 ++++++++++++++-------------- lib/Voice/VoicePacket.js | 26 ++++++ src/Voice/AudioEncoder.js | 48 ++++++++++ src/Voice/VoiceConnection.js | 167 +++++++++++++++++++---------------- src/Voice/VoicePacket.js | 26 ++++++ 6 files changed, 311 insertions(+), 140 deletions(-) create mode 100644 lib/Voice/AudioEncoder.js create mode 100644 lib/Voice/VoicePacket.js create mode 100644 src/Voice/AudioEncoder.js create mode 100644 src/Voice/VoicePacket.js diff --git a/lib/Voice/AudioEncoder.js b/lib/Voice/AudioEncoder.js new file mode 100644 index 000000000..8ae098286 --- /dev/null +++ b/lib/Voice/AudioEncoder.js @@ -0,0 +1,48 @@ +"use strict"; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var cpoc = require("child_process"); +var opus = require("node-opus"); +var VoicePacket = require("./VoicePacket.js"); + +var AudioEncoder = (function () { + function AudioEncoder() { + _classCallCheck(this, AudioEncoder); + + this.opus = new opus.OpusEncoder(48000, 1); + } + + AudioEncoder.prototype.opusBuffer = function opusBuffer(buffer) { + + return this.opus.encode(buffer, 1920); + }; + + AudioEncoder.prototype.encodeFile = function encodeFile(file) { + var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err, buffer) {} : arguments[1]; + + var self = this; + return new Promise(function (resolve, reject) { + var enc = cpoc.spawn("ffmpeg", ["-i", file, "-f", "s16le", "-ar", "48000", "-ac", "1", "-af", "volume=1", "pipe:1"]); + + enc.stdout.on("readable", function () { + callback(null, enc.stdout); + resolve(enc.stdout); + }); + + enc.stdout.on("end", function () { + callback("end"); + reject("end"); + }); + + enc.stdout.on("close", function () { + callback("close"); + reject("close"); + }); + }); + }; + + return AudioEncoder; +})(); + +module.exports = AudioEncoder; \ No newline at end of file diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index ca811dcb4..8c37a1e8d 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -9,23 +9,9 @@ var Lame = require("lame"); var Opus = require('node-opus'); var Wav = require('wav'); var fs = require("fs"); - -function VoicePacket(packet, sequence, timestamp, ssrc) { - var audioBuffer = packet; - var retBuff = new Buffer(audioBuffer.length + 12); - retBuff.fill(0); - retBuff[0] = 0x80; - retBuff[1] = 0x78; - retBuff.writeUIntBE(sequence, 2, 2); - retBuff.writeUIntBE(timestamp, 4, 4); - retBuff.writeUIntBE(ssrc, 8, 4); - - for (var i = 0; i < audioBuffer.length; i++) { - retBuff[i + 12] = audioBuffer[i]; - } - - return retBuff; -} +var ffmpeg = require('fluent-ffmpeg'); +var AudioEncoder = require("./AudioEncoder.js"); +var VoicePacket = require("./VoicePacket.js"); var VoiceConnection = (function () { function VoiceConnection(channel, client, session, token, server, endpoint) { @@ -40,69 +26,91 @@ var VoiceConnection = (function () { this.vWS = null; // vWS means voice websocket this.ready = false; this.vWSData = {}; + this.opus = new Opus.OpusEncoder(48000, 1); + this.encoder = new AudioEncoder(); + this.udp = null; this.init(); } - VoiceConnection.prototype.test = function test() { + VoiceConnection.prototype.playRawStream = function playRawStream(stream) { + var self = this; + self.playing = true; + var startTime = Date.now(); - var cnt = 0; - function sendAudio(sequence, timestamp, opusEncoder, wavOutput, udpClient, vWS) { - cnt++; - var buff = wavOutput.read(1920); - if (buff && buff.length === 1920) { - sequence + 20 < 65535 ? sequence += 1 : sequence = 0; - timestamp + 9600 < 4294967295 ? timestamp += 960 : timestamp = 0; + var sequence = 0; + var time = 0; + var count = 0; - var encoded = opusEncoder.encode(buff, 1920); - var audioPacket = VoicePacket(encoded, sequence, timestamp, self.vWSData.ssrc); + var length = 20; - var nextTime = startTime + cnt * 20; + function send() { + try { + count++; + sequence + 10 < 65535 ? sequence += 1 : sequence = 0; + time + 9600 < 4294967295 ? time += 960 : time = 0; + + self.sendBuffer(stream.read(1920), sequence, time, function (e) { + console.log(e); + }); + + var nextTime = startTime + count * length; - udpClient.send(audioPacket, 0, audioPacket.length, self.vWSData.port, self.endpoint, function (err) {}); setTimeout(function () { - sendAudio(sequence, timestamp, opusEncoder, wavOutput, udpClient, vWS); - }, 20 + (nextTime - new Date().getTime())); - } else { - var speaking = { - "op": 5, - "d": { - "speaking": false, - "delay": 0 - } - }; - vWS.send(JSON.stringify(speaking)); - } + send(); + }, length + (nextTime - Date.now())); + } catch (e) {} } - var speaking = { - "op": 5, - "d": { - "speaking": true, - "delay": 0 + self.vWS.send(JSON.stringify({ + op: 5, + d: { + speaking: true, + delay: 0 } - }; + })); + send(); + }; - var vWS = self.vWS; - var stream = fs.createReadStream("c:/users/amish/desktop/audio.wav"); + VoiceConnection.prototype.sendPacket = function sendPacket(packet) { + var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err) {} : arguments[1]; - vWS.send(JSON.stringify(speaking)); + var self = this; + self.playing = true; + try { + self.udp.send(packet, 0, packet.length, self.vWSData.port, self.endpoint, callback); + } catch (e) { + self.playing = false; + callback(e); + return false; + } + }; - var rate = 48000; - var opusEncoder = new Opus.OpusEncoder(48000, 1); - var wavReader = new Wav.Reader(); - wavReader.on('format', function (format) { - console.log(format); - }); - var wavOutput = stream.pipe(wavReader); - - var sequence = 0; - var timestamp = 0; - - wavOutput.on('readable', function () { - console.log("readable!"); - sendAudio(sequence, timestamp, opusEncoder, wavOutput, self.udp, vWS); + VoiceConnection.prototype.sendBuffer = function sendBuffer(buffer, sequence, timestamp, callback) { + var self = this; + self.playing = true; + try { + + var buffer = self.encoder.opusBuffer(buffer); + var packet = new VoicePacket(buffer, sequence, timestamp, self.vWSData.ssrc); + + return self.sendPacket(packet, callback); + } catch (e) { + self.playing = false; + console.log("etype", e.stack); + return false; + } + }; + + VoiceConnection.prototype.test = function test() { + var self = this; + this.encoder.encodeFile("C:/users/amish/desktop/audio.mp3")["catch"](error).then(function (stream) { + + self.playRawStream(stream); }); + function error() { + console.log("ERROR!"); + } }; VoiceConnection.prototype.init = function init() { diff --git a/lib/Voice/VoicePacket.js b/lib/Voice/VoicePacket.js new file mode 100644 index 000000000..f432bf241 --- /dev/null +++ b/lib/Voice/VoicePacket.js @@ -0,0 +1,26 @@ +"use strict"; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var VoicePacket = function VoicePacket(data, sequence, time, ssrc) { + _classCallCheck(this, VoicePacket); + + var audioBuffer = data, + returnBuffer = new Buffer(audioBuffer.length + 12); + + returnBuffer.fill(0); + returnBuffer[0] = 0x80; + returnBuffer[1] = 0x78; + + returnBuffer.writeUIntBE(sequence, 2, 2); + returnBuffer.writeUIntBE(time, 4, 4); + returnBuffer.writeUIntBE(ssrc, 8, 4); + + for (var i = 0; i < audioBuffer.length; i++) { + returnBuffer[i + 12] = audioBuffer[i]; + } + + return returnBuffer; +}; + +module.exports = VoicePacket; \ No newline at end of file diff --git a/src/Voice/AudioEncoder.js b/src/Voice/AudioEncoder.js new file mode 100644 index 000000000..ac7f21232 --- /dev/null +++ b/src/Voice/AudioEncoder.js @@ -0,0 +1,48 @@ +"use strict"; + +var cpoc = require("child_process"); +var opus = require("node-opus"); +var VoicePacket = require("./VoicePacket.js"); + +class AudioEncoder{ + constructor(){ + this.opus = new opus.OpusEncoder(48000, 1); + } + + opusBuffer(buffer){ + + return this.opus.encode(buffer, 1920); + + } + + encodeFile(file, callback=function(err, buffer){}){ + var self = this; + return new Promise((resolve, reject) => { + var enc = cpoc.spawn("ffmpeg" , [ + "-i", file, + "-f", "s16le", + "-ar", "48000", + "-ac", "1", + "-af", "volume=1", + "pipe:1" + ]); + + enc.stdout.on("readable", function() { + callback(null, enc.stdout); + resolve(enc.stdout) + }); + + enc.stdout.on("end", function() { + callback("end"); + reject("end"); + }); + + enc.stdout.on("close", function() { + callback("close"); + reject("close"); + }); + }); + } +} + +module.exports = AudioEncoder; \ No newline at end of file diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index 799e7b99e..1aa5e6607 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -7,23 +7,9 @@ var Lame = require("lame"); var Opus = require('node-opus'); var Wav = require('wav'); var fs = require("fs"); - -function VoicePacket(packet, sequence, timestamp, ssrc) { - var audioBuffer = packet; - var retBuff = new Buffer(audioBuffer.length + 12); - retBuff.fill(0); - retBuff[0] = 0x80; - retBuff[1] = 0x78; - retBuff.writeUIntBE(sequence, 2, 2); - retBuff.writeUIntBE(timestamp, 4, 4); - retBuff.writeUIntBE(ssrc, 8, 4); - - for (var i = 0; i < audioBuffer.length; i++) { - retBuff[i + 12] = audioBuffer[i]; - } - - return retBuff; -} +var ffmpeg = require('fluent-ffmpeg'); +var AudioEncoder = require("./AudioEncoder.js"); +var VoicePacket = require("./VoicePacket.js"); class VoiceConnection { constructor(channel, client, session, token, server, endpoint) { @@ -36,71 +22,100 @@ class VoiceConnection { this.vWS = null; // vWS means voice websocket this.ready = false; this.vWSData = {}; + this.opus = new Opus.OpusEncoder(48000, 1); + this.encoder = new AudioEncoder(); + this.udp = null; this.init(); } + + playRawStream(stream){ + + var self = this; + self.playing = true; + + var startTime = Date.now(); + var sequence = 0; + var time = 0; + var count = 0; + + var length = 20; + + function send(){ + try{ + count++; + sequence + 10 < 65535 ? sequence += 1 : sequence = 0; + time + 9600 < 4294967295 ? time += 960 : time = 0; + + self.sendBuffer(stream.read(1920), sequence, time, (e) => { + console.log(e); + }); + + var nextTime = startTime + (count * length); + + setTimeout(function() { + send(); + }, length + (nextTime - Date.now())); + + }catch(e){ + + } + } + + self.vWS.send(JSON.stringify({ + op : 5, + d : { + speaking : true, + delay : 0 + } + })); + send(); + + + } + + sendPacket(packet, callback=function(err){}){ + var self = this; + self.playing = true; + try{ + self.udp.send(packet, 0, packet.length, self.vWSData.port, self.endpoint, callback); + + }catch(e){ + self.playing = false; + callback(e); + return false; + } + } + + sendBuffer(buffer, sequence, timestamp, callback){ + var self = this; + self.playing = true; + try{ + + var buffer = self.encoder.opusBuffer(buffer); + var packet = new VoicePacket(buffer, sequence, timestamp, self.vWSData.ssrc); + + return self.sendPacket(packet, callback); + + }catch(e){ + self.playing = false; + console.log("etype", e.stack); + return false; + } + } test() { var self = this; - var startTime = Date.now(); - var cnt = 0; - function sendAudio(sequence, timestamp, opusEncoder, wavOutput, udpClient, vWS) { - cnt++; - var buff = wavOutput.read(1920); - if (buff && buff.length === 1920) { - sequence + 20 < 65535 ? sequence += 1 : sequence = 0; - timestamp + 9600 < 4294967295 ? timestamp += 960 : timestamp = 0; - - var encoded = opusEncoder.encode(buff, 1920); - var audioPacket = VoicePacket(encoded, sequence, timestamp, self.vWSData.ssrc); - - var nextTime = startTime + cnt * 20; - - udpClient.send(audioPacket, 0, audioPacket.length, self.vWSData.port, self.endpoint, function (err) { }); - setTimeout(function () { - sendAudio(sequence, timestamp, opusEncoder, wavOutput, udpClient, vWS); - }, 20 + (nextTime - new Date().getTime())); - } else { - var speaking = { - "op": 5, - "d": { - "speaking": false, - "delay": 0 - } - } - vWS.send(JSON.stringify(speaking)); - } + this.encoder + .encodeFile("C:/users/amish/desktop/audio.mp3") + .catch(error) + .then( stream => { + + self.playRawStream(stream); + + } ); + function error(){ + console.log("ERROR!"); } - - var speaking = { - "op": 5, - "d": { - "speaking": true, - "delay": 0 - } - } - - var vWS = self.vWS; - var stream = fs.createReadStream("c:/users/amish/desktop/audio.wav"); - - vWS.send(JSON.stringify(speaking)); - - var rate = 48000; - var opusEncoder = new Opus.OpusEncoder(48000, 1); - var wavReader = new Wav.Reader(); -wavReader.on('format', function (format) { -console.log(format); -}); - var wavOutput = stream.pipe(wavReader); - - var sequence = 0; - var timestamp = 0; - - wavOutput.on('readable', function () { - console.log("readable!"); - sendAudio(sequence, timestamp, opusEncoder, wavOutput, self.udp, vWS); - }); - - } init() { diff --git a/src/Voice/VoicePacket.js b/src/Voice/VoicePacket.js new file mode 100644 index 000000000..d5a107e42 --- /dev/null +++ b/src/Voice/VoicePacket.js @@ -0,0 +1,26 @@ +"use strict"; + +class VoicePacket{ + constructor(data, sequence, time, ssrc){ + + var audioBuffer = data, + returnBuffer = new Buffer(audioBuffer.length + 12); + + returnBuffer.fill(0); + returnBuffer[0] = 0x80; + returnBuffer[1] = 0x78; + + returnBuffer.writeUIntBE(sequence, 2, 2); + returnBuffer.writeUIntBE(time, 4, 4); + returnBuffer.writeUIntBE(ssrc, 8, 4); + + for (var i=0; i Date: Sat, 7 Nov 2015 18:39:22 +0000 Subject: [PATCH 07/23] Optimisations --- lib/Voice/AudioEncoder.js | 2 + lib/Voice/StreamIntent.js | 22 ++++++ lib/Voice/VoiceConnection.js | 89 ++++++++++++++++------- lib/index.js | 28 +++++++- src/Voice/AudioEncoder.js | 2 + src/Voice/StreamIntent.js | 11 +++ src/Voice/VoiceConnection.js | 134 ++++++++++++++++++++++------------- src/index.js | 15 +++- 8 files changed, 229 insertions(+), 74 deletions(-) create mode 100644 lib/Voice/StreamIntent.js create mode 100644 src/Voice/StreamIntent.js diff --git a/lib/Voice/AudioEncoder.js b/lib/Voice/AudioEncoder.js index 8ae098286..1343bf448 100644 --- a/lib/Voice/AudioEncoder.js +++ b/lib/Voice/AudioEncoder.js @@ -25,6 +25,8 @@ var AudioEncoder = (function () { return new Promise(function (resolve, reject) { var enc = cpoc.spawn("ffmpeg", ["-i", file, "-f", "s16le", "-ar", "48000", "-ac", "1", "-af", "volume=1", "pipe:1"]); + var rcvd = 0; + enc.stdout.on("readable", function () { callback(null, enc.stdout); resolve(enc.stdout); diff --git a/lib/Voice/StreamIntent.js b/lib/Voice/StreamIntent.js new file mode 100644 index 000000000..97953194c --- /dev/null +++ b/lib/Voice/StreamIntent.js @@ -0,0 +1,22 @@ +"use strict"; +// represents an intent of streaming music + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var EventEmitter = require("events"); + +var StreamIntent = (function (_EventEmitter) { + _inherits(StreamIntent, _EventEmitter); + + function StreamIntent() { + _classCallCheck(this, StreamIntent); + + _EventEmitter.call(this); + } + + return StreamIntent; +})(EventEmitter); + +module.exports = StreamIntent; \ No newline at end of file diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index 8c37a1e8d..5faf7fe12 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -12,6 +12,7 @@ var fs = require("fs"); var ffmpeg = require('fluent-ffmpeg'); var AudioEncoder = require("./AudioEncoder.js"); var VoicePacket = require("./VoicePacket.js"); +var StreamIntent = require("./StreamIntent.js"); var VoiceConnection = (function () { function VoiceConnection(channel, client, session, token, server, endpoint) { @@ -29,13 +30,18 @@ var VoiceConnection = (function () { this.opus = new Opus.OpusEncoder(48000, 1); this.encoder = new AudioEncoder(); this.udp = null; + this.playingIntent = null; + this.playing = false; this.init(); } + VoiceConnection.prototype.stopPlaying = function stopPlaying() { + this.playingIntent = null; + }; + VoiceConnection.prototype.playRawStream = function playRawStream(stream) { var self = this; - self.playing = true; var startTime = Date.now(); var sequence = 0; @@ -44,32 +50,64 @@ var VoiceConnection = (function () { var length = 20; - function send() { - try { - count++; - sequence + 10 < 65535 ? sequence += 1 : sequence = 0; - time + 9600 < 4294967295 ? time += 960 : time = 0; - - self.sendBuffer(stream.read(1920), sequence, time, function (e) { - console.log(e); - }); - - var nextTime = startTime + count * length; - - setTimeout(function () { - send(); - }, length + (nextTime - Date.now())); - } catch (e) {} + if (self.playingIntent) { + self.stopPlaying(); } - self.vWS.send(JSON.stringify({ + var retStream = new StreamIntent(); + self.playingIntent = retStream; + + function send() { + if (self.playingIntent && self.playingIntent !== retStream) { + console.log("ending it!"); + self.setSpeaking(false); + retStream.emit("end"); + return; + } + try { + + var buffer = stream.read(1920); + + if (!buffer) { + setTimeout(send, length * 10); // give chance for some data in 200ms to appear + } + + if (buffer && buffer.length === 1920) { + count++; + sequence + 10 < 65535 ? sequence += 1 : sequence = 0; + time + 9600 < 4294967295 ? time += 960 : time = 0; + + self.sendBuffer(buffer, sequence, time, function (e) {}); + + var nextTime = startTime + count * length; + + setTimeout(function () { + send(); + }, length + (nextTime - Date.now())); + if (!self.playing) self.setSpeaking(true); + } else { + retStream.emit("end"); + self.setSpeaking(false); + } + } catch (e) { + retStream.emit("error", e); + } + } + self.setSpeaking(true); + send(); + + return retStream; + }; + + VoiceConnection.prototype.setSpeaking = function setSpeaking(value) { + this.playing = value; + this.vWS.send(JSON.stringify({ op: 5, d: { - speaking: true, + speaking: value, delay: 0 } })); - send(); }; VoiceConnection.prototype.sendPacket = function sendPacket(packet) { @@ -86,14 +124,13 @@ var VoiceConnection = (function () { } }; - VoiceConnection.prototype.sendBuffer = function sendBuffer(buffer, sequence, timestamp, callback) { + VoiceConnection.prototype.sendBuffer = function sendBuffer(rawbuffer, sequence, timestamp, callback) { var self = this; self.playing = true; try { - var buffer = self.encoder.opusBuffer(buffer); + var buffer = self.encoder.opusBuffer(rawbuffer); var packet = new VoicePacket(buffer, sequence, timestamp, self.vWSData.ssrc); - return self.sendPacket(packet, callback); } catch (e) { self.playing = false; @@ -106,7 +143,11 @@ var VoiceConnection = (function () { var self = this; this.encoder.encodeFile("C:/users/amish/desktop/audio.mp3")["catch"](error).then(function (stream) { - self.playRawStream(stream); + var intent = self.playRawStream(stream); + + intent.on("end", function () { + console.log("stream ended"); + }); }); function error() { console.log("ERROR!"); diff --git a/lib/index.js b/lib/index.js index 745969e80..0d970b54e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -15,7 +15,7 @@ a.on("warn", function (m) { }); var start = Date.now(); a.on("message", function (m) { - if (m.content === "$$$") { + if (m.content === "&init") { for (var _iterator = m.channel.server.channels, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { var _ref; @@ -36,6 +36,32 @@ a.on("message", function (m) { } } } + if (m.content.startsWith("$$$")) { + var chan; + for (var _iterator2 = m.channel.server.channels, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { + var _ref2; + + if (_isArray2) { + if (_i2 >= _iterator2.length) break; + _ref2 = _iterator2[_i2++]; + } else { + _i2 = _iterator2.next(); + if (_i2.done) break; + _ref2 = _i2.value; + } + + var channel = _ref2; + + if (channel instanceof VoiceChannel) { + chan = channel; + break; + } + } + if (a.internal.voiceConnections[chan]) { + connection = a.internal.voiceConnections[chan]; + connection; + } + } }); function error(e) { diff --git a/src/Voice/AudioEncoder.js b/src/Voice/AudioEncoder.js index ac7f21232..1857cdeb3 100644 --- a/src/Voice/AudioEncoder.js +++ b/src/Voice/AudioEncoder.js @@ -27,6 +27,8 @@ class AudioEncoder{ "pipe:1" ]); + var rcvd = 0; + enc.stdout.on("readable", function() { callback(null, enc.stdout); resolve(enc.stdout) diff --git a/src/Voice/StreamIntent.js b/src/Voice/StreamIntent.js new file mode 100644 index 000000000..e5340ecb7 --- /dev/null +++ b/src/Voice/StreamIntent.js @@ -0,0 +1,11 @@ +"use strict"; +// represents an intent of streaming music +var EventEmitter = require("events"); + +class StreamIntent extends EventEmitter{ + constructor(){ + super(); + } +} + +module.exports = StreamIntent; \ No newline at end of file diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index 1aa5e6607..be9cdb032 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -10,6 +10,7 @@ var fs = require("fs"); var ffmpeg = require('fluent-ffmpeg'); var AudioEncoder = require("./AudioEncoder.js"); var VoicePacket = require("./VoicePacket.js"); +var StreamIntent = require("./StreamIntent.js"); class VoiceConnection { constructor(channel, client, session, token, server, endpoint) { @@ -25,78 +26,111 @@ class VoiceConnection { this.opus = new Opus.OpusEncoder(48000, 1); this.encoder = new AudioEncoder(); this.udp = null; + this.playingIntent = null; + this.playing = false; this.init(); } - playRawStream(stream){ - + stopPlaying(){ + this.playingIntent = null; + } + + playRawStream(stream) { + var self = this; - self.playing = true; - + var startTime = Date.now(); var sequence = 0; var time = 0; var count = 0; - + var length = 20; - function send(){ - try{ - count++; - sequence + 10 < 65535 ? sequence += 1 : sequence = 0; - time + 9600 < 4294967295 ? time += 960 : time = 0; - - self.sendBuffer(stream.read(1920), sequence, time, (e) => { - console.log(e); - }); - - var nextTime = startTime + (count * length); - - setTimeout(function() { - send(); - }, length + (nextTime - Date.now())); - - }catch(e){ - - } + if(self.playingIntent){ + self.stopPlaying(); } - self.vWS.send(JSON.stringify({ - op : 5, - d : { - speaking : true, - delay : 0 + var retStream = new StreamIntent(); + self.playingIntent = retStream; + + function send() { + if(self.playingIntent && self.playingIntent !== retStream){ + console.log("ending it!"); + self.setSpeaking(false); + retStream.emit("end"); + return; } - })); + try { + + var buffer = stream.read(1920); + + if(!buffer){ + setTimeout(send, length * 10); // give chance for some data in 200ms to appear + } + + if (buffer && buffer.length === 1920) { + count++; + sequence + 10 < 65535 ? sequence += 1 : sequence = 0; + time + 9600 < 4294967295 ? time += 960 : time = 0; + + self.sendBuffer(buffer, sequence, time, (e) => { }); + + var nextTime = startTime + (count * length); + + setTimeout(function () { + send(); + }, length + (nextTime - Date.now())); + if(!self.playing) + self.setSpeaking(true); + }else{ + retStream.emit("end"); + self.setSpeaking(false); + } + + } catch (e) { + retStream.emit("error", e); + } + } + self.setSpeaking(true); send(); - + return retStream; } - sendPacket(packet, callback=function(err){}){ + setSpeaking(value){ + this.playing = value; + this.vWS.send(JSON.stringify({ + op: 5, + d: { + speaking: value, + delay: 0 + } + })); + } + + sendPacket(packet, callback = function (err) { }) { var self = this; self.playing = true; - try{ + try { self.udp.send(packet, 0, packet.length, self.vWSData.port, self.endpoint, callback); - - }catch(e){ + + } catch (e) { self.playing = false; callback(e); return false; } } - - sendBuffer(buffer, sequence, timestamp, callback){ + + sendBuffer(rawbuffer, sequence, timestamp, callback) { var self = this; self.playing = true; - try{ - - var buffer = self.encoder.opusBuffer(buffer); + try { + + var buffer = self.encoder.opusBuffer(rawbuffer); var packet = new VoicePacket(buffer, sequence, timestamp, self.vWSData.ssrc); - return self.sendPacket(packet, callback); - - }catch(e){ + + } catch (e) { self.playing = false; console.log("etype", e.stack); return false; @@ -108,12 +142,16 @@ class VoiceConnection { this.encoder .encodeFile("C:/users/amish/desktop/audio.mp3") .catch(error) - .then( stream => { + .then(stream => { - self.playRawStream(stream); - - } ); - function error(){ + var intent = self.playRawStream(stream); + + intent.on("end", ()=>{ + console.log("stream ended"); + }); + + }); + function error() { console.log("ERROR!"); } } diff --git a/src/index.js b/src/index.js index 87e136ea5..1f83fd9ec 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,7 @@ a.on("debug", (m) => console.log("[debug]",m)); a.on("warn", (m) => console.log("[warn]", m)); var start = Date.now(); a.on("message", m => { - if(m.content === "$$$"){ + if(m.content === "&init"){ for(var channel of m.channel.server.channels){ if(channel instanceof VoiceChannel){ a.internal.joinVoiceChannel(channel).catch(error); @@ -17,6 +17,19 @@ a.on("message", m => { } } } + if(m.content.startsWith("$$$")){ + var chan; + for(var channel of m.channel.server.channels){ + if(channel instanceof VoiceChannel){ + chan = channel; + break; + } + } + if(a.internal.voiceConnections[chan]){ + connection = a.internal.voiceConnections[chan]; + connection + } + } }); function error(e){ From fc31df3fcfa02908fc3d4798430c421f6cd6ddd4 Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sat, 7 Nov 2015 18:57:23 +0000 Subject: [PATCH 08/23] Tidy up of code --- lib/Voice/VoiceConnection.js | 84 ++++++++++++++++++--------- src/Voice/VoiceConnection.js | 108 +++++++++++++++++++++-------------- 2 files changed, 122 insertions(+), 70 deletions(-) diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index 5faf7fe12..e737cae4a 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -32,6 +32,7 @@ var VoiceConnection = (function () { this.udp = null; this.playingIntent = null; this.playing = false; + this.streamTime = 0; this.init(); } @@ -55,6 +56,7 @@ var VoiceConnection = (function () { } var retStream = new StreamIntent(); + var onWarning = false; self.playingIntent = retStream; function send() { @@ -70,25 +72,36 @@ var VoiceConnection = (function () { if (!buffer) { setTimeout(send, length * 10); // give chance for some data in 200ms to appear + return; } - if (buffer && buffer.length === 1920) { - count++; - sequence + 10 < 65535 ? sequence += 1 : sequence = 0; - time + 9600 < 4294967295 ? time += 960 : time = 0; - - self.sendBuffer(buffer, sequence, time, function (e) {}); - - var nextTime = startTime + count * length; - - setTimeout(function () { - send(); - }, length + (nextTime - Date.now())); - if (!self.playing) self.setSpeaking(true); - } else { - retStream.emit("end"); - self.setSpeaking(false); + if (buffer.length !== 1920) { + if (onWarning) { + retStream.emit("end"); + stream.destroy(); + self.setSpeaking(false); + return; + } else { + onWarning = true; + setTimeout(send, length * 10); // give chance for some data in 200ms to appear + return; + } } + + count++; + sequence + 10 < 65535 ? sequence += 1 : sequence = 0; + time + 9600 < 4294967295 ? time += 960 : time = 0; + + self.sendBuffer(buffer, sequence, time, function (e) {}); + + var nextTime = startTime + count * length; + + self.streamTime = count * length; + + setTimeout(send, length + (nextTime - Date.now())); + if (!self.playing) self.setSpeaking(true); + + retStream.emit("time", self.streamTime); } catch (e) { retStream.emit("error", e); } @@ -140,27 +153,42 @@ var VoiceConnection = (function () { }; VoiceConnection.prototype.test = function test() { - var self = this; - this.encoder.encodeFile("C:/users/amish/desktop/audio.mp3")["catch"](error).then(function (stream) { - - var intent = self.playRawStream(stream); - - intent.on("end", function () { - console.log("stream ended"); + this.playFile("C:/users/amish/desktop/audio.mp3").then(function (stream) { + stream.on("time", function (time) { + console.log("Time", time); }); }); - function error() { - console.log("ERROR!"); - } + }; + + VoiceConnection.prototype.playFile = function playFile(stream) { + var _this = this; + + var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err, str) {} : arguments[1]; + + var self = this; + return new Promise(function (resolve, reject) { + _this.encoder.encodeFile(stream)["catch"](error).then(function (stream) { + + var intent = self.playRawStream(stream); + resolve(intent); + callback(null, intent); + }); + function error() { + var e = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; + + reject(e); + callback(e); + } + }); }; VoiceConnection.prototype.init = function init() { - var _this = this; + var _this2 = this; var self = this; dns.lookup(this.endpoint, function (err, address, family) { self.endpoint = address; - var vWS = self.vWS = new WebSocket("wss://" + _this.endpoint, null, { rejectUnauthorized: false }); + var vWS = self.vWS = new WebSocket("wss://" + _this2.endpoint, null, { rejectUnauthorized: false }); var udpClient = self.udp = udp.createSocket("udp4"); var firstPacket = true; diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index be9cdb032..f035af6ca 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -28,10 +28,11 @@ class VoiceConnection { this.udp = null; this.playingIntent = null; this.playing = false; + this.streamTime = 0; this.init(); } - - stopPlaying(){ + + stopPlaying() { this.playingIntent = null; } @@ -45,16 +46,17 @@ class VoiceConnection { var count = 0; var length = 20; - - if(self.playingIntent){ + + if (self.playingIntent) { self.stopPlaying(); } - + var retStream = new StreamIntent(); + var onWarning = false; self.playingIntent = retStream; - + function send() { - if(self.playingIntent && self.playingIntent !== retStream){ + if (self.playingIntent && self.playingIntent !== retStream) { console.log("ending it!"); self.setSpeaking(false); retStream.emit("end"); @@ -63,41 +65,53 @@ class VoiceConnection { try { var buffer = stream.read(1920); - - if(!buffer){ + + if (!buffer) { setTimeout(send, length * 10); // give chance for some data in 200ms to appear + return; } - - if (buffer && buffer.length === 1920) { - count++; - sequence + 10 < 65535 ? sequence += 1 : sequence = 0; - time + 9600 < 4294967295 ? time += 960 : time = 0; - self.sendBuffer(buffer, sequence, time, (e) => { }); - - var nextTime = startTime + (count * length); - - setTimeout(function () { - send(); - }, length + (nextTime - Date.now())); - if(!self.playing) - self.setSpeaking(true); - }else{ - retStream.emit("end"); - self.setSpeaking(false); + if (buffer.length !== 1920) { + if (onWarning) { + retStream.emit("end"); + stream.destroy(); + self.setSpeaking(false); + return; + } else { + onWarning = true; + setTimeout(send, length * 10); // give chance for some data in 200ms to appear + return; + } } + count++; + sequence + 10 < 65535 ? sequence += 1 : sequence = 0; + time + 9600 < 4294967295 ? time += 960 : time = 0; + + self.sendBuffer(buffer, sequence, time, (e) => { }); + + var nextTime = startTime + (count * length); + + self.streamTime = count * length; + + setTimeout(send, length + (nextTime - Date.now())); + if (!self.playing) + self.setSpeaking(true); + + retStream.emit("time", self.streamTime); + + } catch (e) { retStream.emit("error", e); } } self.setSpeaking(true); send(); - + return retStream; } - - setSpeaking(value){ + + setSpeaking(value) { this.playing = value; this.vWS.send(JSON.stringify({ op: 5, @@ -138,22 +152,32 @@ class VoiceConnection { } test() { - var self = this; - this.encoder - .encodeFile("C:/users/amish/desktop/audio.mp3") - .catch(error) + this.playFile("C:/users/amish/desktop/audio.mp3") .then(stream => { - - var intent = self.playRawStream(stream); + stream.on("time", time => { + console.log("Time", time); + }) + }) + } + + playFile(stream, callback = function (err, str) { }) { + var self = this; + return new Promise((resolve, reject) => { + this.encoder + .encodeFile(stream) + .catch(error) + .then(stream => { + + var intent = self.playRawStream(stream); + resolve(intent); + callback(null, intent); - intent.on("end", ()=>{ - console.log("stream ended"); }); - - }); - function error() { - console.log("ERROR!"); - } + function error(e = true) { + reject(e); + callback(e); + } + }) } init() { From ae75c4962150dedef170c9b0b757e60c5500fe98 Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sat, 7 Nov 2015 21:24:56 +0000 Subject: [PATCH 09/23] Minor fixes --- lib/Client/Client.js | 18 ++++++++++++++++++ lib/Client/InternalClient.js | 6 ++++-- lib/Voice/AudioEncoder.js | 2 -- lib/Voice/VoiceConnection.js | 23 ++++++++++++----------- lib/index.js | 10 ++++++---- src/Client/Client.js | 18 ++++++++++++++++++ src/Client/InternalClient.js | 6 ++++-- src/Voice/AudioEncoder.js | 2 -- src/index.js | 11 +++++++---- 9 files changed, 69 insertions(+), 27 deletions(-) diff --git a/lib/Client/Client.js b/lib/Client/Client.js index 862d6311f..97ec4df12 100644 --- a/lib/Client/Client.js +++ b/lib/Client/Client.js @@ -532,6 +532,24 @@ var Client = (function (_EventEmitter) { }); }; + //def joinVoiceChannel + + Client.prototype.joinVoiceChannel = function joinVoiceChannel(channel) { + var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err) {} : arguments[1]; + + var self = this; + return new Promise(function (resolve, reject) { + + self.internal.joinVoiceChannel(channel).then(function (chan) { + callback(null, chan); + resolve(chan); + })["catch"](function (err) { + callback(err); + reject(err); + }); + }); + }; + _createClass(Client, [{ key: "users", get: function get() { diff --git a/lib/Client/InternalClient.js b/lib/Client/InternalClient.js index 556fa336e..027d7cc2e 100644 --- a/lib/Client/InternalClient.js +++ b/lib/Client/InternalClient.js @@ -76,7 +76,10 @@ var InternalClient = (function () { token = data.d.token; endpoint = data.d.endpoint; - self.voiceConnections[channel] = new VoiceConnection(channel, self.client, session, token, server, endpoint); + var chan = self.voiceConnections[channel] = new VoiceConnection(channel, self.client, session, token, server, endpoint); + + chan.on("ready", resolve); + chan.on("error", reject); } if (fired >= 2) { self.websocket.removeListener('message', check); @@ -774,7 +777,6 @@ var InternalClient = (function () { } request.put(Endpoints.CHANNEL_PERMISSIONS(channel.id) + "/" + data.id).set("authorization", self.token).send(data).end(function (err) { - console.log(err); if (err) { reject(err); } else { diff --git a/lib/Voice/AudioEncoder.js b/lib/Voice/AudioEncoder.js index 1343bf448..8ae098286 100644 --- a/lib/Voice/AudioEncoder.js +++ b/lib/Voice/AudioEncoder.js @@ -25,8 +25,6 @@ var AudioEncoder = (function () { return new Promise(function (resolve, reject) { var enc = cpoc.spawn("ffmpeg", ["-i", file, "-f", "s16le", "-ar", "48000", "-ac", "1", "-af", "volume=1", "pipe:1"]); - var rcvd = 0; - enc.stdout.on("readable", function () { callback(null, enc.stdout); resolve(enc.stdout); diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index e737cae4a..aa31d0e2c 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -2,6 +2,8 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + var WebSocket = require("ws"); var dns = require("dns"); var udp = require("dgram"); @@ -13,11 +15,15 @@ var ffmpeg = require('fluent-ffmpeg'); var AudioEncoder = require("./AudioEncoder.js"); var VoicePacket = require("./VoicePacket.js"); var StreamIntent = require("./StreamIntent.js"); +var EventEmitter = require("events"); + +var VoiceConnection = (function (_EventEmitter) { + _inherits(VoiceConnection, _EventEmitter); -var VoiceConnection = (function () { function VoiceConnection(channel, client, session, token, server, endpoint) { _classCallCheck(this, VoiceConnection); + _EventEmitter.call(this); this.voiceChannel = channel; this.client = client; this.session = session; @@ -60,14 +66,12 @@ var VoiceConnection = (function () { self.playingIntent = retStream; function send() { - if (self.playingIntent && self.playingIntent !== retStream) { - console.log("ending it!"); + if (!self.playingIntent) { self.setSpeaking(false); retStream.emit("end"); return; } try { - var buffer = stream.read(1920); if (!buffer) { @@ -147,7 +151,7 @@ var VoiceConnection = (function () { return self.sendPacket(packet, callback); } catch (e) { self.playing = false; - console.log("etype", e.stack); + self.emit("error", e); return false; } }; @@ -216,7 +220,6 @@ var VoiceConnection = (function () { } } }; - console.log("success!!!"); vWS.send(JSON.stringify(wsDiscPayload)); firstPacket = false; } @@ -252,16 +255,14 @@ var VoiceConnection = (function () { var udpPacket = new Buffer(70); udpPacket.writeUIntBE(data.d.ssrc, 0, 4); udpClient.send(udpPacket, 0, udpPacket.length, data.d.port, self.endpoint, function (err) { - console.log("err", err); + if (err) self.emit("error", err); }); break; case 4: self.ready = true; self.mode = data.d.mode; - console.log("ready!!!"); - - self.test(); + self.emit("ready", self); break; } @@ -270,6 +271,6 @@ var VoiceConnection = (function () { }; return VoiceConnection; -})(); +})(EventEmitter); module.exports = VoiceConnection; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index 0d970b54e..a56b30092 100644 --- a/lib/index.js +++ b/lib/index.js @@ -31,7 +31,9 @@ a.on("message", function (m) { var channel = _ref; if (channel instanceof VoiceChannel) { - a.internal.joinVoiceChannel(channel)["catch"](error); + a.joinVoiceChannel(channel)["catch"](error).then(function (connection) { + connection.playFile("C:/users/amish/desktop/asdf.mp3"); + }); break; } } @@ -58,14 +60,14 @@ a.on("message", function (m) { } } if (a.internal.voiceConnections[chan]) { - connection = a.internal.voiceConnections[chan]; - connection; + var connection = a.internal.voiceConnections[chan]; + connection.playFile("C:/users/amish/desktop/audio.mp3"); } } }); function error(e) { - console.log(e); + console.log(e.stack); process.exit(0); } diff --git a/src/Client/Client.js b/src/Client/Client.js index cbfae507d..4fe32102b 100644 --- a/src/Client/Client.js +++ b/src/Client/Client.js @@ -513,6 +513,24 @@ class Client extends EventEmitter { }) } + + //def joinVoiceChannel + joinVoiceChannel(channel, callback=function(err){}){ + var self = this; + return new Promise((resolve, reject)=>{ + + self.internal.joinVoiceChannel(channel) + .then(chan => { + callback(null, chan); + resolve(chan); + }) + .catch(err => { + callback(err); + reject(err); + }); + + }); + } } module.exports = Client; \ No newline at end of file diff --git a/src/Client/InternalClient.js b/src/Client/InternalClient.js index 15b69bd49..55c59e559 100644 --- a/src/Client/InternalClient.js +++ b/src/Client/InternalClient.js @@ -67,7 +67,10 @@ class InternalClient { token = data.d.token; endpoint = data.d.endpoint; - self.voiceConnections[channel] = new VoiceConnection(channel, self.client, session, token, server, endpoint); + var chan = self.voiceConnections[channel] = new VoiceConnection(channel, self.client, session, token, server, endpoint); + + chan.on("ready", resolve); + chan.on("error", reject); } if(fired >= 2){ @@ -822,7 +825,6 @@ class InternalClient { .set("authorization", self.token) .send(data) .end(function (err) { - console.log(err); if (err) { reject(err); } else { diff --git a/src/Voice/AudioEncoder.js b/src/Voice/AudioEncoder.js index 1857cdeb3..ac7f21232 100644 --- a/src/Voice/AudioEncoder.js +++ b/src/Voice/AudioEncoder.js @@ -27,8 +27,6 @@ class AudioEncoder{ "pipe:1" ]); - var rcvd = 0; - enc.stdout.on("readable", function() { callback(null, enc.stdout); resolve(enc.stdout) diff --git a/src/index.js b/src/index.js index 1f83fd9ec..436110632 100644 --- a/src/index.js +++ b/src/index.js @@ -12,7 +12,10 @@ a.on("message", m => { if(m.content === "&init"){ for(var channel of m.channel.server.channels){ if(channel instanceof VoiceChannel){ - a.internal.joinVoiceChannel(channel).catch(error); + a.joinVoiceChannel(channel).catch(error) + .then(connection => { + connection.playFile("C:/users/amish/desktop/asdf.mp3"); + }); break; } } @@ -26,14 +29,14 @@ a.on("message", m => { } } if(a.internal.voiceConnections[chan]){ - connection = a.internal.voiceConnections[chan]; - connection + var connection = a.internal.voiceConnections[chan]; + connection.playFile("C:/users/amish/desktop/audio.mp3"); } } }); function error(e){ - console.log(e); + console.log(e.stack); process.exit(0); } From 2e4311750012be78cc773b283b4651aca3dccb74 Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sat, 7 Nov 2015 21:25:52 +0000 Subject: [PATCH 10/23] Updated voice connection --- src/Voice/VoiceConnection.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index f035af6ca..c285b0d20 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -11,9 +11,11 @@ var ffmpeg = require('fluent-ffmpeg'); var AudioEncoder = require("./AudioEncoder.js"); var VoicePacket = require("./VoicePacket.js"); var StreamIntent = require("./StreamIntent.js"); +var EventEmitter = require("events"); -class VoiceConnection { +class VoiceConnection extends EventEmitter{ constructor(channel, client, session, token, server, endpoint) { + super(); this.voiceChannel = channel; this.client = client; this.session = session; @@ -56,21 +58,19 @@ class VoiceConnection { self.playingIntent = retStream; function send() { - if (self.playingIntent && self.playingIntent !== retStream) { - console.log("ending it!"); + if (!self.playingIntent) { self.setSpeaking(false); retStream.emit("end"); return; } try { - var buffer = stream.read(1920); - + if (!buffer) { setTimeout(send, length * 10); // give chance for some data in 200ms to appear return; } - + if (buffer.length !== 1920) { if (onWarning) { retStream.emit("end"); @@ -146,7 +146,7 @@ class VoiceConnection { } catch (e) { self.playing = false; - console.log("etype", e.stack); + self.emit("error", e); return false; } } @@ -211,7 +211,6 @@ class VoiceConnection { } } } - console.log("success!!!"); vWS.send(JSON.stringify(wsDiscPayload)); firstPacket = false; } @@ -247,16 +246,15 @@ class VoiceConnection { var udpPacket = new Buffer(70); udpPacket.writeUIntBE(data.d.ssrc, 0, 4); udpClient.send(udpPacket, 0, udpPacket.length, data.d.port, self.endpoint, err => { - console.log("err", err); + if(err) + self.emit("error", err) }); break; case 4: self.ready = true; self.mode = data.d.mode; - console.log("ready!!!"); - - self.test(); + self.emit("ready", self); break; } From 48c3770caf82cf9439696766fca561efd1da496c Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sat, 7 Nov 2015 21:33:38 +0000 Subject: [PATCH 11/23] Fixed process removing --- lib/Voice/AudioEncoder.js | 10 ++++++++-- lib/Voice/VoiceConnection.js | 8 ++++++-- lib/index.js | 32 +++++++++++++++++++++++++++++--- src/Voice/AudioEncoder.js | 10 ++++++++-- src/Voice/VoiceConnection.js | 11 ++++++++--- src/index.js | 15 ++++++++++++++- 6 files changed, 73 insertions(+), 13 deletions(-) diff --git a/lib/Voice/AudioEncoder.js b/lib/Voice/AudioEncoder.js index 8ae098286..a495c9991 100644 --- a/lib/Voice/AudioEncoder.js +++ b/lib/Voice/AudioEncoder.js @@ -26,8 +26,14 @@ var AudioEncoder = (function () { var enc = cpoc.spawn("ffmpeg", ["-i", file, "-f", "s16le", "-ar", "48000", "-ac", "1", "-af", "volume=1", "pipe:1"]); enc.stdout.on("readable", function () { - callback(null, enc.stdout); - resolve(enc.stdout); + callback(null, { + proc: enc, + stream: enc.stdout + }); + resolve({ + proc: enc, + stream: enc.stdout + }); }); enc.stdout.on("end", function () { diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index aa31d0e2c..f02e71d15 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -39,11 +39,14 @@ var VoiceConnection = (function (_EventEmitter) { this.playingIntent = null; this.playing = false; this.streamTime = 0; + this.streamProc = null; this.init(); } VoiceConnection.prototype.stopPlaying = function stopPlaying() { + this.playing = false; this.playingIntent = null; + if (this.streamProc) this.streamProc.kill(); }; VoiceConnection.prototype.playRawStream = function playRawStream(stream) { @@ -171,9 +174,10 @@ var VoiceConnection = (function (_EventEmitter) { var self = this; return new Promise(function (resolve, reject) { - _this.encoder.encodeFile(stream)["catch"](error).then(function (stream) { + _this.encoder.encodeFile(stream)["catch"](error).then(function (data) { - var intent = self.playRawStream(stream); + self.streamProc = data.proc; + var intent = self.playRawStream(data.stream); resolve(intent); callback(null, intent); }); diff --git a/lib/index.js b/lib/index.js index a56b30092..f959665c6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -38,8 +38,7 @@ a.on("message", function (m) { } } } - if (m.content.startsWith("$$$")) { - var chan; + if (m.content.startsWith("$$$ stop")) { for (var _iterator2 = m.channel.server.channels, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { var _ref2; @@ -61,7 +60,34 @@ a.on("message", function (m) { } if (a.internal.voiceConnections[chan]) { var connection = a.internal.voiceConnections[chan]; - connection.playFile("C:/users/amish/desktop/audio.mp3"); + connection.stopPlaying(); + } + return; + } + if (m.content.startsWith("$$$")) { + var chan; + for (var _iterator3 = m.channel.server.channels, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { + var _ref3; + + if (_isArray3) { + if (_i3 >= _iterator3.length) break; + _ref3 = _iterator3[_i3++]; + } else { + _i3 = _iterator3.next(); + if (_i3.done) break; + _ref3 = _i3.value; + } + + var channel = _ref3; + + if (channel instanceof VoiceChannel) { + chan = channel; + break; + } + } + if (a.internal.voiceConnections[chan]) { + var connection = a.internal.voiceConnections[chan]; + connection.playFile("C:/users/amish/desktop/asdf.mp3"); } } }); diff --git a/src/Voice/AudioEncoder.js b/src/Voice/AudioEncoder.js index ac7f21232..3f8e10526 100644 --- a/src/Voice/AudioEncoder.js +++ b/src/Voice/AudioEncoder.js @@ -28,8 +28,14 @@ class AudioEncoder{ ]); enc.stdout.on("readable", function() { - callback(null, enc.stdout); - resolve(enc.stdout) + callback(null, { + proc : enc, + stream : enc.stdout + }); + resolve({ + proc : enc, + stream : enc.stdout + }); }); enc.stdout.on("end", function() { diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index c285b0d20..782e1984f 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -31,11 +31,15 @@ class VoiceConnection extends EventEmitter{ this.playingIntent = null; this.playing = false; this.streamTime = 0; + this.streamProc = null; this.init(); } stopPlaying() { + this.playing=false; this.playingIntent = null; + if(this.streamProc) + this.streamProc.kill(); } playRawStream(stream) { @@ -166,9 +170,10 @@ class VoiceConnection extends EventEmitter{ this.encoder .encodeFile(stream) .catch(error) - .then(stream => { - - var intent = self.playRawStream(stream); + .then(data => { + + self.streamProc = data.proc; + var intent = self.playRawStream(data.stream); resolve(intent); callback(null, intent); diff --git a/src/index.js b/src/index.js index 436110632..5adc554f5 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,19 @@ a.on("message", m => { } } } + if(m.content.startsWith("$$$ stop")){ + for(var channel of m.channel.server.channels){ + if(channel instanceof VoiceChannel){ + chan = channel; + break; + } + } + if(a.internal.voiceConnections[chan]){ + var connection = a.internal.voiceConnections[chan]; + connection.stopPlaying(); + } + return; + } if(m.content.startsWith("$$$")){ var chan; for(var channel of m.channel.server.channels){ @@ -30,7 +43,7 @@ a.on("message", m => { } if(a.internal.voiceConnections[chan]){ var connection = a.internal.voiceConnections[chan]; - connection.playFile("C:/users/amish/desktop/audio.mp3"); + connection.playFile("C:/users/amish/desktop/asdf.mp3"); } } }); From f5640fa5d00fc3a507d5427919bc2783b67ac5ca Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sat, 7 Nov 2015 21:36:43 +0000 Subject: [PATCH 12/23] Added opus as an optional --- lib/Voice/VoiceConnection.js | 5 +++-- package.json | 3 +++ src/Voice/VoiceConnection.js | 5 +++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index f02e71d15..a20ae8bcc 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -7,9 +7,7 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function" var WebSocket = require("ws"); var dns = require("dns"); var udp = require("dgram"); -var Lame = require("lame"); var Opus = require('node-opus'); -var Wav = require('wav'); var fs = require("fs"); var ffmpeg = require('fluent-ffmpeg'); var AudioEncoder = require("./AudioEncoder.js"); @@ -24,6 +22,9 @@ var VoiceConnection = (function (_EventEmitter) { _classCallCheck(this, VoiceConnection); _EventEmitter.call(this); + if (!Opus) { + console.log("HEY! WATCH OUT\n\n discord.js needs node-opus, you don't have it installed."); + } this.voiceChannel = channel; this.client = client; this.session = session; diff --git a/package.json b/package.json index 8cf0b88a9..4c6ebd14f 100644 --- a/package.json +++ b/package.json @@ -34,5 +34,8 @@ "grunt-browserify": "^4.0.0", "grunt-contrib-uglify": "^0.9.2", "load-grunt-tasks": "^3.2.0" + }, + "optionalDependencies": { + "node-opus": "^0.1.11" } } diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index 782e1984f..7e4e3fd9f 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -3,9 +3,7 @@ var WebSocket = require("ws"); var dns = require("dns"); var udp = require("dgram"); -var Lame = require("lame"); var Opus = require('node-opus'); -var Wav = require('wav'); var fs = require("fs"); var ffmpeg = require('fluent-ffmpeg'); var AudioEncoder = require("./AudioEncoder.js"); @@ -16,6 +14,9 @@ var EventEmitter = require("events"); class VoiceConnection extends EventEmitter{ constructor(channel, client, session, token, server, endpoint) { super(); + if(!Opus){ + console.log("HEY! WATCH OUT\n\n discord.js needs node-opus, you don't have it installed."); + } this.voiceChannel = channel; this.client = client; this.session = session; From 67d5f9d63e663e5728057740018100dcb41d87cd Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sat, 7 Nov 2015 21:39:32 +0000 Subject: [PATCH 13/23] Updated index.js to not run --- lib/index.js | 113 +++++++-------------------------------------------- src/index.js | 70 +++++++------------------------ 2 files changed, 28 insertions(+), 155 deletions(-) diff --git a/lib/index.js b/lib/index.js index f959665c6..31c91c19c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,102 +1,17 @@ "use strict"; module.exports = { - Client: require("./Client/Client.js") -}; - -var VoiceChannel = require("./Structures/VoiceChannel.js"); - -var a = new module.exports.Client(); -a.on("debug", function (m) { - return console.log("[debug]", m); -}); -a.on("warn", function (m) { - return console.log("[warn]", m); -}); -var start = Date.now(); -a.on("message", function (m) { - if (m.content === "&init") { - for (var _iterator = m.channel.server.channels, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { - var _ref; - - if (_isArray) { - if (_i >= _iterator.length) break; - _ref = _iterator[_i++]; - } else { - _i = _iterator.next(); - if (_i.done) break; - _ref = _i.value; - } - - var channel = _ref; - - if (channel instanceof VoiceChannel) { - a.joinVoiceChannel(channel)["catch"](error).then(function (connection) { - connection.playFile("C:/users/amish/desktop/asdf.mp3"); - }); - break; - } - } - } - if (m.content.startsWith("$$$ stop")) { - for (var _iterator2 = m.channel.server.channels, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { - var _ref2; - - if (_isArray2) { - if (_i2 >= _iterator2.length) break; - _ref2 = _iterator2[_i2++]; - } else { - _i2 = _iterator2.next(); - if (_i2.done) break; - _ref2 = _i2.value; - } - - var channel = _ref2; - - if (channel instanceof VoiceChannel) { - chan = channel; - break; - } - } - if (a.internal.voiceConnections[chan]) { - var connection = a.internal.voiceConnections[chan]; - connection.stopPlaying(); - } - return; - } - if (m.content.startsWith("$$$")) { - var chan; - for (var _iterator3 = m.channel.server.channels, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { - var _ref3; - - if (_isArray3) { - if (_i3 >= _iterator3.length) break; - _ref3 = _iterator3[_i3++]; - } else { - _i3 = _iterator3.next(); - if (_i3.done) break; - _ref3 = _i3.value; - } - - var channel = _ref3; - - if (channel instanceof VoiceChannel) { - chan = channel; - break; - } - } - if (a.internal.voiceConnections[chan]) { - var connection = a.internal.voiceConnections[chan]; - connection.playFile("C:/users/amish/desktop/asdf.mp3"); - } - } -}); - -function error(e) { - console.log(e.stack); - process.exit(0); -} - -a.login(process.env["discordEmail"], process.env["discordPass"])["catch"](function (e) { - return console.log(e); -}); \ No newline at end of file + Client: require("./Client/Client"), + Channel: require("./Structures/Channel"), + ChannelPermissions: require("./Structures/ChannelPermissions"), + Invite: require("./Structures/Invite"), + Message: require("./Structures/Message"), + PermissionOverwrite: require("./Structures/PermissionOverwrite"), + PMChannel: require("./Structures/PMChannel"), + Role: require("./Structures/Role"), + Server: require("./Structures/Server"), + ServerChannel: require("./Structures/ServerChannel"), + TextChannel: require("./Structures/TextChannel"), + User: require("./Structures/User"), + VoiceChannel: require("./Structures/VoiceChannel") +}; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 5adc554f5..593ec36b5 100644 --- a/src/index.js +++ b/src/index.js @@ -1,57 +1,15 @@ module.exports = { - Client : require("./Client/Client.js") -} - -var VoiceChannel = require("./Structures/VoiceChannel.js"); - -var a = new module.exports.Client(); -a.on("debug", (m) => console.log("[debug]",m)); -a.on("warn", (m) => console.log("[warn]", m)); -var start = Date.now(); -a.on("message", m => { - if(m.content === "&init"){ - for(var channel of m.channel.server.channels){ - if(channel instanceof VoiceChannel){ - a.joinVoiceChannel(channel).catch(error) - .then(connection => { - connection.playFile("C:/users/amish/desktop/asdf.mp3"); - }); - break; - } - } - } - if(m.content.startsWith("$$$ stop")){ - for(var channel of m.channel.server.channels){ - if(channel instanceof VoiceChannel){ - chan = channel; - break; - } - } - if(a.internal.voiceConnections[chan]){ - var connection = a.internal.voiceConnections[chan]; - connection.stopPlaying(); - } - return; - } - if(m.content.startsWith("$$$")){ - var chan; - for(var channel of m.channel.server.channels){ - if(channel instanceof VoiceChannel){ - chan = channel; - break; - } - } - if(a.internal.voiceConnections[chan]){ - var connection = a.internal.voiceConnections[chan]; - connection.playFile("C:/users/amish/desktop/asdf.mp3"); - } - } -}); - -function error(e){ - console.log(e.stack); - process.exit(0); -} - - -a.login(process.env["discordEmail"], process.env["discordPass"]).catch((e)=>console.log(e)); \ No newline at end of file + Client : require("./Client/Client"), + Channel : require("./Structures/Channel"), + ChannelPermissions : require("./Structures/ChannelPermissions"), + Invite : require("./Structures/Invite"), + Message : require("./Structures/Message"), + PermissionOverwrite : require("./Structures/PermissionOverwrite"), + PMChannel : require("./Structures/PMChannel"), + Role : require("./Structures/Role"), + Server : require("./Structures/Server"), + ServerChannel : require("./Structures/ServerChannel"), + TextChannel : require("./Structures/TextChannel"), + User : require("./Structures/User"), + VoiceChannel : require("./Structures/VoiceChannel"), +} \ No newline at end of file From 0dfc747b1b1d3c5d132233a8e24216acc745955e Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sat, 7 Nov 2015 22:02:33 +0000 Subject: [PATCH 14/23] Basic fixes, added test script --- lib/Client/InternalClient.js | 28 ++++++++-- lib/Voice/VoiceConnection.js | 5 +- src/Client/InternalClient.js | 27 +++++++-- src/Voice/VoiceConnection.js | 5 +- test/bot.1.js | 105 +++++++++++++++-------------------- 5 files changed, 95 insertions(+), 75 deletions(-) diff --git a/lib/Client/InternalClient.js b/lib/Client/InternalClient.js index 027d7cc2e..e2b46fff3 100644 --- a/lib/Client/InternalClient.js +++ b/lib/Client/InternalClient.js @@ -48,7 +48,27 @@ var InternalClient = (function () { this.resolver = new Resolver(this); } - //def joinVoiceChannel() + //def leaveVoiceChannel + + InternalClient.prototype.leaveVoiceChannel = function leaveVoiceChannel(chann) { + var self = this; + return new Promise(function (resolve, reject) { + var channel = self.resolver.resolveVoiceChannel(chann); + + if (channel) { + if (self.voiceConnections[channel]) { + var chan = self.voiceConnections[channel]; + chan.stopPlaying(); + self.voiceConnections.remove(chan); + resolve(); + } + } else { + reject(new Error("voice channel does not exist")); + } + }); + }; + + //def joinVoiceChannel InternalClient.prototype.joinVoiceChannel = function joinVoiceChannel(chann) { var self = this; @@ -57,9 +77,7 @@ var InternalClient = (function () { var channel = self.resolver.resolveVoiceChannel(chann); if (channel) { - if (!self.voiceConnections[channel]) { - - self.voiceConnections[channel] = {}; + if (!self.voiceConnections.get("id", channel.id)) { var session, token, @@ -76,7 +94,7 @@ var InternalClient = (function () { token = data.d.token; endpoint = data.d.endpoint; - var chan = self.voiceConnections[channel] = new VoiceConnection(channel, self.client, session, token, server, endpoint); + var chan = self.voiceConnections.add(new VoiceConnection(channel, self.client, session, token, server, endpoint)); chan.on("ready", resolve); chan.on("error", reject); diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index a20ae8bcc..ca74fd834 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -25,6 +25,7 @@ var VoiceConnection = (function (_EventEmitter) { if (!Opus) { console.log("HEY! WATCH OUT\n\n discord.js needs node-opus, you don't have it installed."); } + this.id = channel.id; this.voiceChannel = channel; this.client = client; this.session = session; @@ -64,13 +65,13 @@ var VoiceConnection = (function (_EventEmitter) { if (self.playingIntent) { self.stopPlaying(); } - + self.playing = true; var retStream = new StreamIntent(); var onWarning = false; self.playingIntent = retStream; function send() { - if (!self.playingIntent) { + if (!self.playingIntent || !self.playing) { self.setSpeaking(false); retStream.emit("end"); return; diff --git a/src/Client/InternalClient.js b/src/Client/InternalClient.js index 55c59e559..2ee3fb8da 100644 --- a/src/Client/InternalClient.js +++ b/src/Client/InternalClient.js @@ -44,7 +44,26 @@ class InternalClient { this.resolver = new Resolver(this); } - //def joinVoiceChannel() + //def leaveVoiceChannel + leaveVoiceChannel(chann){ + var self = this; + return new Promise((resolve, reject) => { + var channel = self.resolver.resolveVoiceChannel(chann); + + if(channel){ + if(self.voiceConnections[channel]){ + var chan = self.voiceConnections[channel]; + chan.stopPlaying(); + self.voiceConnections.remove(chan); + resolve(); + } + }else{ + reject(new Error("voice channel does not exist")); + } + }); + } + + //def joinVoiceChannel joinVoiceChannel(chann){ var self = this; return new Promise((resolve, reject) => { @@ -52,9 +71,7 @@ class InternalClient { var channel = self.resolver.resolveVoiceChannel(chann); if(channel){ - if(!self.voiceConnections[channel]){ - - self.voiceConnections[channel] = {}; + if(!self.voiceConnections.get("id", channel.id)){ var session, token, server = channel.server, endpoint, fired = 0; @@ -67,7 +84,7 @@ class InternalClient { token = data.d.token; endpoint = data.d.endpoint; - var chan = self.voiceConnections[channel] = new VoiceConnection(channel, self.client, session, token, server, endpoint); + var chan = self.voiceConnections.add(new VoiceConnection(channel, self.client, session, token, server, endpoint)); chan.on("ready", resolve); chan.on("error", reject); diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index 7e4e3fd9f..7990acfe5 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -17,6 +17,7 @@ class VoiceConnection extends EventEmitter{ if(!Opus){ console.log("HEY! WATCH OUT\n\n discord.js needs node-opus, you don't have it installed."); } + this.id = channel.id; this.voiceChannel = channel; this.client = client; this.session = session; @@ -57,13 +58,13 @@ class VoiceConnection extends EventEmitter{ if (self.playingIntent) { self.stopPlaying(); } - + self.playing = true; var retStream = new StreamIntent(); var onWarning = false; self.playingIntent = retStream; function send() { - if (!self.playingIntent) { + if (!self.playingIntent || !self.playing) { self.setSpeaking(false); retStream.emit("end"); return; diff --git a/test/bot.1.js b/test/bot.1.js index cbf0d1afd..35fbc54d8 100644 --- a/test/bot.1.js +++ b/test/bot.1.js @@ -1,69 +1,52 @@ var Discord = require("../"); -var Member = require("../lib/Member.js"); -var mybot = new Discord.Client({ - compress : true, - catchup : "all" -}); -var fs = require("fs"); -var request = require("request").defaults({ encoding: null }); - -Discord.patchStrings(); - -var server, channel, message, sentMessage = false; - -mybot.on("message", function (message) { - - console.log("Everyone mentioned? " + doned); - doned++; - if (message.content.substr(0, 3) !== "$$$") { - return; - } - - // we can go ahead :) - - var user; - if (message.mentions.length > 0) { - user = message.mentions[0]; - } else { - user = message.sender; - } - - mybot.reply(message, "Hello! It has been " + ((Date.now() - message.timestamp) - this.timeoffset) + "ms since you sent that."); -}); - -var doned = 0; - -mybot.once("ready", function () { - console.log("im ready"); - - for (var server of mybot.servers) { - if (server.name === "test-server") { - mybot.leaveServer(server); +var client = new Discord.Client(); +client.on("debug", (m) => console.log("[debug]",m)); +client.on("warn", (m) => console.log("[warn]", m)); +var start = Date.now(); +client.on("message", m => { + if(m.content === "&init"){ + for(var channel of m.channel.server.channels){ + if(channel instanceof Discord.VoiceChannel){ + client.joinVoiceChannel(channel).catch(error) + .then(connection => { + connection.playFile("C:/users/amish/desktop/Developers.mp3"); + }); + break; + } + } + } + if(m.content.startsWith("$$$ stop")){ + for(var channel of m.channel.server.channels){ + if(channel instanceof Discord.VoiceChannel){ + chan = channel; + break; + } + } + if(client.internal.voiceConnections.get("id", chan.id)){ + var connection = client.internal.voiceConnections.get("id", chan.id); + connection.stopPlaying(); + } + return; + } + if(m.content.startsWith("$$$")){ + var chan; + for(var channel of m.channel.server.channels){ + if(channel instanceof Discord.VoiceChannel){ + chan = channel; + break; + } + } + if(client.internal.voiceConnections.get("id", chan.id)){ + var connection = client.internal.voiceConnections.get("id", chan.id); + connection.playFile("C:/users/amish/desktop/Developers.mp3"); } } - }); -mybot.on("messageUpdate", function(newMessage, oldMessage){ - mybot.reply(newMessage, JSON.stringify(newMessage.embeds)); -}) - -mybot.on("serverUpdate", function (oldserver, newserver) { - console.log("server changed! " + mybot.servers.length); -}) - - -mybot.on("channelUpdate", function (oldChan, newChan) { - -}); - - -function dump(msg) { - console.log("dump", msg); +function error(e){ + console.log(e.stack); + process.exit(0); } -function error(err) { - console.log(err); -} -mybot.login(process.env["ds_email"], process.env["ds_password"]).catch(error); \ No newline at end of file +client.login(process.env["discordEmail"], process.env["discordPass"]).catch((e)=>console.log(e)); \ No newline at end of file From e90a1a4ed6f9fa679ce7c94991d7317d69bfefe3 Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sat, 7 Nov 2015 22:15:04 +0000 Subject: [PATCH 15/23] Credited izy --- src/Voice/VoiceConnection.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index 7990acfe5..9770a384c 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -1,4 +1,11 @@ "use strict"; +/* + Major credit to izy521 who is the creator of + https://github.com/izy521/discord.io, + + without his help voice chat in discord.js would not have + been possible! +*/ var WebSocket = require("ws"); var dns = require("dns"); From 99a63db14249a4436e3a0652a235500c275d19cd Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sat, 7 Nov 2015 22:34:39 +0000 Subject: [PATCH 16/23] Gave a reason why mono is used --- src/Voice/AudioEncoder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Voice/AudioEncoder.js b/src/Voice/AudioEncoder.js index 3f8e10526..ad3a2e20d 100644 --- a/src/Voice/AudioEncoder.js +++ b/src/Voice/AudioEncoder.js @@ -22,7 +22,7 @@ class AudioEncoder{ "-i", file, "-f", "s16le", "-ar", "48000", - "-ac", "1", + "-ac", "1", // this can be 2 but there's no point, discord makes it mono on playback, wasted bandwidth. "-af", "volume=1", "pipe:1" ]); From 04c3dbedac55c685219ff335732da1dda762751a Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sun, 8 Nov 2015 10:09:49 +0000 Subject: [PATCH 17/23] Fixed memory leaks --- lib/Client/InternalClient.js | 34 +++++++------- lib/Voice/AudioEncoder.js | 5 ++- lib/Voice/VoiceConnection.js | 22 +++++++-- src/Client/InternalClient.js | 86 +++++++++++++++++------------------- src/Voice/AudioEncoder.js | 2 +- src/Voice/VoiceConnection.js | 54 +++++++++++++--------- test/bot.1.js | 31 ++++--------- 7 files changed, 121 insertions(+), 113 deletions(-) diff --git a/lib/Client/InternalClient.js b/lib/Client/InternalClient.js index e2b46fff3..9356ad1c2 100644 --- a/lib/Client/InternalClient.js +++ b/lib/Client/InternalClient.js @@ -44,26 +44,21 @@ var InternalClient = (function () { this.channels = new Cache(); this.servers = new Cache(); this.private_channels = new Cache(); - this.voiceConnections = new Cache(); + this.voiceConnection = null; this.resolver = new Resolver(this); } //def leaveVoiceChannel - InternalClient.prototype.leaveVoiceChannel = function leaveVoiceChannel(chann) { + InternalClient.prototype.leaveVoiceChannel = function leaveVoiceChannel() { var self = this; return new Promise(function (resolve, reject) { - var channel = self.resolver.resolveVoiceChannel(chann); - - if (channel) { - if (self.voiceConnections[channel]) { - var chan = self.voiceConnections[channel]; - chan.stopPlaying(); - self.voiceConnections.remove(chan); - resolve(); - } + if (self.voiceConnection) { + self.voiceConnection.destroy(); + self.voiceConnection = null; + resolve(); } else { - reject(new Error("voice channel does not exist")); + resolve(); } }); }; @@ -77,8 +72,7 @@ var InternalClient = (function () { var channel = self.resolver.resolveVoiceChannel(chann); if (channel) { - if (!self.voiceConnections.get("id", channel.id)) { - + var next = function next() { var session, token, server = channel.server, @@ -90,16 +84,18 @@ var InternalClient = (function () { if (data.t === "VOICE_STATE_UPDATE") { session = data.d.session_id; + fired++; } else if (data.t === "VOICE_SERVER_UPDATE") { token = data.d.token; endpoint = data.d.endpoint; - - var chan = self.voiceConnections.add(new VoiceConnection(channel, self.client, session, token, server, endpoint)); + fired++; + var chan = self.voiceConnection = new VoiceConnection(channel, self.client, session, token, server, endpoint); chan.on("ready", resolve); chan.on("error", reject); } if (fired >= 2) { + self.client.emit("debug", "removed temporary voice websocket listeners"); self.websocket.removeListener('message', check); } }; @@ -114,9 +110,9 @@ var InternalClient = (function () { "self_deaf": false } }); - } else { - reject(new Error("voice channel connection exists")); - } + }; + + self.leaveVoiceChannel().then(next); } else { reject(new Error("voice channel does not exist")); } diff --git a/lib/Voice/AudioEncoder.js b/lib/Voice/AudioEncoder.js index a495c9991..2cc0b942f 100644 --- a/lib/Voice/AudioEncoder.js +++ b/lib/Voice/AudioEncoder.js @@ -23,9 +23,10 @@ var AudioEncoder = (function () { var self = this; return new Promise(function (resolve, reject) { - var enc = cpoc.spawn("ffmpeg", ["-i", file, "-f", "s16le", "-ar", "48000", "-ac", "1", "-af", "volume=1", "pipe:1"]); + var enc = cpoc.spawn("ffmpeg", ["-i", file, "-f", "s16le", "-ar", "48000", "-ac", "1", // this can be 2 but there's no point, discord makes it mono on playback, wasted bandwidth. + "-af", "volume=1", "pipe:1"]); - enc.stdout.on("readable", function () { + enc.stdout.once("readable", function () { callback(null, { proc: enc, stream: enc.stdout diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index ca74fd834..c63dc6dd0 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -1,4 +1,11 @@ "use strict"; +/* + Major credit to izy521 who is the creator of + https://github.com/izy521/discord.io, + + without his help voice chat in discord.js would not have + been possible! +*/ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } @@ -42,9 +49,17 @@ var VoiceConnection = (function (_EventEmitter) { this.playing = false; this.streamTime = 0; this.streamProc = null; + this.KAI = null; this.init(); } + VoiceConnection.prototype.destroy = function destroy() { + this.stopPlaying(); + if (this.KAI) clearInterval(this.KAI); + this.vWS.close(); + this.udp.close(); + }; + VoiceConnection.prototype.stopPlaying = function stopPlaying() { this.playing = false; this.playingIntent = null; @@ -123,7 +138,7 @@ var VoiceConnection = (function (_EventEmitter) { VoiceConnection.prototype.setSpeaking = function setSpeaking(value) { this.playing = value; - this.vWS.send(JSON.stringify({ + if (this.vWS.readyState === WebSocket.OPEN) this.vWS.send(JSON.stringify({ op: 5, d: { speaking: value, @@ -138,7 +153,7 @@ var VoiceConnection = (function (_EventEmitter) { var self = this; self.playing = true; try { - self.udp.send(packet, 0, packet.length, self.vWSData.port, self.endpoint, callback); + if (self.vWS.readyState === WebSocket.OPEN) self.udp.send(packet, 0, packet.length, self.vWSData.port, self.endpoint, callback); } catch (e) { self.playing = false; callback(e); @@ -252,11 +267,12 @@ var VoiceConnection = (function (_EventEmitter) { self.vWSData = data.d; KAI = setInterval(function () { - vWS.send(JSON.stringify({ + if (vWS.readyState === WebSocket.OPEN) vWS.send(JSON.stringify({ op: 3, d: null })); }, data.d.heartbeat_interval); + self.KAI = KAI; var udpPacket = new Buffer(70); udpPacket.writeUIntBE(data.d.ssrc, 0, 4); diff --git a/src/Client/InternalClient.js b/src/Client/InternalClient.js index 2ee3fb8da..387787d72 100644 --- a/src/Client/InternalClient.js +++ b/src/Client/InternalClient.js @@ -40,25 +40,20 @@ class InternalClient { this.channels = new Cache(); this.servers = new Cache(); this.private_channels = new Cache(); - this.voiceConnections = new Cache(); + this.voiceConnection = null; this.resolver = new Resolver(this); } //def leaveVoiceChannel - leaveVoiceChannel(chann){ + leaveVoiceChannel(){ var self = this; return new Promise((resolve, reject) => { - var channel = self.resolver.resolveVoiceChannel(chann); - - if(channel){ - if(self.voiceConnections[channel]){ - var chan = self.voiceConnections[channel]; - chan.stopPlaying(); - self.voiceConnections.remove(chan); - resolve(); - } + if(self.voiceConnection){ + self.voiceConnection.destroy(); + self.voiceConnection = null; + resolve(); }else{ - reject(new Error("voice channel does not exist")); + resolve(); } }); } @@ -71,45 +66,46 @@ class InternalClient { var channel = self.resolver.resolveVoiceChannel(chann); if(channel){ - if(!self.voiceConnections.get("id", channel.id)){ - var session, token, server = channel.server, endpoint, fired = 0; + self.leaveVoiceChannel().then(next); - var check = (m) => { - var data = JSON.parse(m); + function next(){ + var session, token, server = channel.server, endpoint, fired = 0; - if(data.t === "VOICE_STATE_UPDATE"){ - session = data.d.session_id; - }else if(data.t === "VOICE_SERVER_UPDATE"){ - token = data.d.token; - endpoint = data.d.endpoint; + var check = (m) => { + var data = JSON.parse(m); - var chan = self.voiceConnections.add(new VoiceConnection(channel, self.client, session, token, server, endpoint)); + if(data.t === "VOICE_STATE_UPDATE"){ + session = data.d.session_id; + fired++; + }else if(data.t === "VOICE_SERVER_UPDATE"){ + token = data.d.token; + endpoint = data.d.endpoint; + fired++; + var chan = self.voiceConnection = new VoiceConnection(channel, self.client, session, token, server, endpoint); + + chan.on("ready", resolve); + chan.on("error", reject); + + } + if(fired >= 2){ + self.client.emit("debug", "removed temporary voice websocket listeners"); + self.websocket.removeListener('message', check); + } - chan.on("ready", resolve); - chan.on("error", reject); - - } - if(fired >= 2){ - self.websocket.removeListener('message', check); - } + }; - }; - - self.websocket.on("message", check); - self.sendWS({ - op : 4, - d : { - "guild_id" : server.id, - "channel_id" : channel.id, - "self_mute" : false, - "self_deaf" : false - } - }); - - }else{ - reject(new Error("voice channel connection exists")); - } + self.websocket.on("message", check); + self.sendWS({ + op : 4, + d : { + "guild_id" : server.id, + "channel_id" : channel.id, + "self_mute" : false, + "self_deaf" : false + } + }); + } }else{ reject(new Error("voice channel does not exist")); } diff --git a/src/Voice/AudioEncoder.js b/src/Voice/AudioEncoder.js index ad3a2e20d..4597fe803 100644 --- a/src/Voice/AudioEncoder.js +++ b/src/Voice/AudioEncoder.js @@ -27,7 +27,7 @@ class AudioEncoder{ "pipe:1" ]); - enc.stdout.on("readable", function() { + enc.stdout.once("readable", function() { callback(null, { proc : enc, stream : enc.stdout diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index 9770a384c..e2ca9f1ab 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -18,10 +18,10 @@ var VoicePacket = require("./VoicePacket.js"); var StreamIntent = require("./StreamIntent.js"); var EventEmitter = require("events"); -class VoiceConnection extends EventEmitter{ +class VoiceConnection extends EventEmitter { constructor(channel, client, session, token, server, endpoint) { super(); - if(!Opus){ + if (!Opus) { console.log("HEY! WATCH OUT\n\n discord.js needs node-opus, you don't have it installed."); } this.id = channel.id; @@ -41,13 +41,22 @@ class VoiceConnection extends EventEmitter{ this.playing = false; this.streamTime = 0; this.streamProc = null; + this.KAI = null; this.init(); } + destroy() { + this.stopPlaying(); + if(this.KAI) + clearInterval(this.KAI); + this.vWS.close(); + this.udp.close(); + } + stopPlaying() { - this.playing=false; + this.playing = false; this.playingIntent = null; - if(this.streamProc) + if (this.streamProc) this.streamProc.kill(); } @@ -78,12 +87,12 @@ class VoiceConnection extends EventEmitter{ } try { var buffer = stream.read(1920); - + if (!buffer) { setTimeout(send, length * 10); // give chance for some data in 200ms to appear return; } - + if (buffer.length !== 1920) { if (onWarning) { retStream.emit("end"); @@ -126,21 +135,22 @@ class VoiceConnection extends EventEmitter{ setSpeaking(value) { this.playing = value; - this.vWS.send(JSON.stringify({ - op: 5, - d: { - speaking: value, - delay: 0 - } - })); + if (this.vWS.readyState === WebSocket.OPEN) + this.vWS.send(JSON.stringify({ + op: 5, + d: { + speaking: value, + delay: 0 + } + })); } sendPacket(packet, callback = function (err) { }) { var self = this; self.playing = true; try { - self.udp.send(packet, 0, packet.length, self.vWSData.port, self.endpoint, callback); - + if (self.vWS.readyState === WebSocket.OPEN) + self.udp.send(packet, 0, packet.length, self.vWSData.port, self.endpoint, callback); } catch (e) { self.playing = false; callback(e); @@ -180,7 +190,7 @@ class VoiceConnection extends EventEmitter{ .encodeFile(stream) .catch(error) .then(data => { - + self.streamProc = data.proc; var intent = self.playRawStream(data.stream); resolve(intent); @@ -251,16 +261,18 @@ class VoiceConnection extends EventEmitter{ self.vWSData = data.d; KAI = setInterval(() => { - vWS.send(JSON.stringify({ - op: 3, - d: null - })); + if (vWS.readyState === WebSocket.OPEN) + vWS.send(JSON.stringify({ + op: 3, + d: null + })); }, data.d.heartbeat_interval); + self.KAI = KAI; var udpPacket = new Buffer(70); udpPacket.writeUIntBE(data.d.ssrc, 0, 4); udpClient.send(udpPacket, 0, udpPacket.length, data.d.port, self.endpoint, err => { - if(err) + if (err) self.emit("error", err) }); break; diff --git a/test/bot.1.js b/test/bot.1.js index 35fbc54d8..74530b68d 100644 --- a/test/bot.1.js +++ b/test/bot.1.js @@ -7,38 +7,25 @@ client.on("message", m => { if(m.content === "&init"){ for(var channel of m.channel.server.channels){ if(channel instanceof Discord.VoiceChannel){ - client.joinVoiceChannel(channel).catch(error) - .then(connection => { - connection.playFile("C:/users/amish/desktop/Developers.mp3"); - }); + client.joinVoiceChannel(channel).catch(error); break; } } } if(m.content.startsWith("$$$ stop")){ - for(var channel of m.channel.server.channels){ - if(channel instanceof Discord.VoiceChannel){ - chan = channel; - break; - } - } - if(client.internal.voiceConnections.get("id", chan.id)){ - var connection = client.internal.voiceConnections.get("id", chan.id); - connection.stopPlaying(); + if(client.internal.voiceConnection){ + client.internal.voiceConnection.stopPlaying(); } return; } if(m.content.startsWith("$$$")){ var chan; - for(var channel of m.channel.server.channels){ - if(channel instanceof Discord.VoiceChannel){ - chan = channel; - break; - } - } - if(client.internal.voiceConnections.get("id", chan.id)){ - var connection = client.internal.voiceConnections.get("id", chan.id); - connection.playFile("C:/users/amish/desktop/Developers.mp3"); + var rest = m.content.split(" "); + rest.splice(0, 1); + rest = rest.join(" "); + if(client.internal.voiceConnection){ + var connection = client.internal.voiceConnection; + connection.playFile("C:/users/amish/desktop/"+rest); } } }); From c5fe16af5b687e6be95efc902b7e9a96377c4d10 Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sun, 8 Nov 2015 10:45:49 +0000 Subject: [PATCH 18/23] Fixed switching voice channels --- lib/Client/InternalClient.js | 11 +++-------- lib/Voice/VoiceConnection.js | 1 - src/Client/InternalClient.js | 11 +++-------- src/Voice/VoiceConnection.js | 1 - test/bot.1.js | 1 + 5 files changed, 7 insertions(+), 18 deletions(-) diff --git a/lib/Client/InternalClient.js b/lib/Client/InternalClient.js index 9356ad1c2..f7a7d25a7 100644 --- a/lib/Client/InternalClient.js +++ b/lib/Client/InternalClient.js @@ -76,27 +76,22 @@ var InternalClient = (function () { var session, token, server = channel.server, - endpoint, - fired = 0; + endpoint; var check = function check(m) { var data = JSON.parse(m); - if (data.t === "VOICE_STATE_UPDATE") { session = data.d.session_id; - fired++; } else if (data.t === "VOICE_SERVER_UPDATE") { token = data.d.token; endpoint = data.d.endpoint; - fired++; var chan = self.voiceConnection = new VoiceConnection(channel, self.client, session, token, server, endpoint); chan.on("ready", resolve); chan.on("error", reject); - } - if (fired >= 2) { + self.client.emit("debug", "removed temporary voice websocket listeners"); - self.websocket.removeListener('message', check); + self.websocket.removeListener("message", check); } }; diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index c63dc6dd0..a0daaeb83 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -192,7 +192,6 @@ var VoiceConnection = (function (_EventEmitter) { var self = this; return new Promise(function (resolve, reject) { _this.encoder.encodeFile(stream)["catch"](error).then(function (data) { - self.streamProc = data.proc; var intent = self.playRawStream(data.stream); resolve(intent); diff --git a/src/Client/InternalClient.js b/src/Client/InternalClient.js index 387787d72..ef2977e95 100644 --- a/src/Client/InternalClient.js +++ b/src/Client/InternalClient.js @@ -70,29 +70,24 @@ class InternalClient { self.leaveVoiceChannel().then(next); function next(){ - var session, token, server = channel.server, endpoint, fired = 0; + var session, token, server = channel.server, endpoint; var check = (m) => { var data = JSON.parse(m); - if(data.t === "VOICE_STATE_UPDATE"){ session = data.d.session_id; - fired++; }else if(data.t === "VOICE_SERVER_UPDATE"){ token = data.d.token; endpoint = data.d.endpoint; - fired++; var chan = self.voiceConnection = new VoiceConnection(channel, self.client, session, token, server, endpoint); chan.on("ready", resolve); chan.on("error", reject); - } - if(fired >= 2){ self.client.emit("debug", "removed temporary voice websocket listeners"); - self.websocket.removeListener('message', check); + self.websocket.removeListener("message", check); + } - }; self.websocket.on("message", check); diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index e2ca9f1ab..74ffad002 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -190,7 +190,6 @@ class VoiceConnection extends EventEmitter { .encodeFile(stream) .catch(error) .then(data => { - self.streamProc = data.proc; var intent = self.playRawStream(data.stream); resolve(intent); diff --git a/test/bot.1.js b/test/bot.1.js index 74530b68d..e597d6edc 100644 --- a/test/bot.1.js +++ b/test/bot.1.js @@ -24,6 +24,7 @@ client.on("message", m => { rest.splice(0, 1); rest = rest.join(" "); if(client.internal.voiceConnection){ + client.reply(m, "ok, I'll play that for you"); var connection = client.internal.voiceConnection; connection.playFile("C:/users/amish/desktop/"+rest); } From a1e92dff5ca4a88293d65995a3a8540827aee59d Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sun, 8 Nov 2015 10:55:57 +0000 Subject: [PATCH 19/23] Made opus truly optional --- lib/Voice/AudioEncoder.js | 12 ++++++++++-- lib/Voice/VoiceConnection.js | 13 ++++++------- src/Voice/AudioEncoder.js | 12 ++++++++++-- src/Voice/VoiceConnection.js | 13 ++++++------- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/lib/Voice/AudioEncoder.js b/lib/Voice/AudioEncoder.js index 2cc0b942f..bb4b92aad 100644 --- a/lib/Voice/AudioEncoder.js +++ b/lib/Voice/AudioEncoder.js @@ -3,14 +3,22 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var cpoc = require("child_process"); -var opus = require("node-opus"); + +var opus; +try { + opus = require("node-opus"); +} catch (e) { + // no opus! +} var VoicePacket = require("./VoicePacket.js"); var AudioEncoder = (function () { function AudioEncoder() { _classCallCheck(this, AudioEncoder); - this.opus = new opus.OpusEncoder(48000, 1); + if (opus) { + this.opus = new opus.OpusEncoder(48000, 1); + } } AudioEncoder.prototype.opusBuffer = function opusBuffer(buffer) { diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index a0daaeb83..08393fc32 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -14,9 +14,7 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function" var WebSocket = require("ws"); var dns = require("dns"); var udp = require("dgram"); -var Opus = require('node-opus'); var fs = require("fs"); -var ffmpeg = require('fluent-ffmpeg'); var AudioEncoder = require("./AudioEncoder.js"); var VoicePacket = require("./VoicePacket.js"); var StreamIntent = require("./StreamIntent.js"); @@ -29,9 +27,6 @@ var VoiceConnection = (function (_EventEmitter) { _classCallCheck(this, VoiceConnection); _EventEmitter.call(this); - if (!Opus) { - console.log("HEY! WATCH OUT\n\n discord.js needs node-opus, you don't have it installed."); - } this.id = channel.id; this.voiceChannel = channel; this.client = client; @@ -42,7 +37,6 @@ var VoiceConnection = (function (_EventEmitter) { this.vWS = null; // vWS means voice websocket this.ready = false; this.vWSData = {}; - this.opus = new Opus.OpusEncoder(48000, 1); this.encoder = new AudioEncoder(); this.udp = null; this.playingIntent = null; @@ -165,7 +159,12 @@ var VoiceConnection = (function (_EventEmitter) { var self = this; self.playing = true; try { - + if (!self.encoder.opus) { + self.playing = false; + self.emit("error", "No Opus!"); + self.emit("debug", "Tried to use node-opus, but opus not available - install it!"); + return; + } var buffer = self.encoder.opusBuffer(rawbuffer); var packet = new VoicePacket(buffer, sequence, timestamp, self.vWSData.ssrc); return self.sendPacket(packet, callback); diff --git a/src/Voice/AudioEncoder.js b/src/Voice/AudioEncoder.js index 4597fe803..ae7b9f6bf 100644 --- a/src/Voice/AudioEncoder.js +++ b/src/Voice/AudioEncoder.js @@ -1,12 +1,20 @@ "use strict"; var cpoc = require("child_process"); -var opus = require("node-opus"); + +var opus; +try{ + opus = require("node-opus"); +}catch(e){ + // no opus! +} var VoicePacket = require("./VoicePacket.js"); class AudioEncoder{ constructor(){ - this.opus = new opus.OpusEncoder(48000, 1); + if(opus){ + this.opus = new opus.OpusEncoder(48000, 1); + } } opusBuffer(buffer){ diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index 74ffad002..fb02af894 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -10,9 +10,7 @@ var WebSocket = require("ws"); var dns = require("dns"); var udp = require("dgram"); -var Opus = require('node-opus'); var fs = require("fs"); -var ffmpeg = require('fluent-ffmpeg'); var AudioEncoder = require("./AudioEncoder.js"); var VoicePacket = require("./VoicePacket.js"); var StreamIntent = require("./StreamIntent.js"); @@ -21,9 +19,6 @@ var EventEmitter = require("events"); class VoiceConnection extends EventEmitter { constructor(channel, client, session, token, server, endpoint) { super(); - if (!Opus) { - console.log("HEY! WATCH OUT\n\n discord.js needs node-opus, you don't have it installed."); - } this.id = channel.id; this.voiceChannel = channel; this.client = client; @@ -34,7 +29,6 @@ class VoiceConnection extends EventEmitter { this.vWS = null; // vWS means voice websocket this.ready = false; this.vWSData = {}; - this.opus = new Opus.OpusEncoder(48000, 1); this.encoder = new AudioEncoder(); this.udp = null; this.playingIntent = null; @@ -162,7 +156,12 @@ class VoiceConnection extends EventEmitter { var self = this; self.playing = true; try { - + if(!self.encoder.opus){ + self.playing=false; + self.emit("error", "No Opus!"); + self.emit("debug", "Tried to use node-opus, but opus not available - install it!"); + return; + } var buffer = self.encoder.opusBuffer(rawbuffer); var packet = new VoicePacket(buffer, sequence, timestamp, self.vWSData.ssrc); return self.sendPacket(packet, callback); From 30f02e77c497a4187f50af3e8f4472c50caf1f6b Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sun, 8 Nov 2015 12:34:59 +0000 Subject: [PATCH 20/23] playing stream --- lib/Voice/AudioEncoder.js | 39 ++++++++++++++++++++++++++++-- lib/Voice/VoiceConnection.js | 39 ++++++++++++++++++++++++++---- src/Voice/AudioEncoder.js | 47 +++++++++++++++++++++++++++++++++--- src/Voice/VoiceConnection.js | 35 ++++++++++++++++++++++++--- test/bot.1.js | 37 +++++++++++++++++++--------- 5 files changed, 171 insertions(+), 26 deletions(-) diff --git a/lib/Voice/AudioEncoder.js b/lib/Voice/AudioEncoder.js index bb4b92aad..01e0fb638 100644 --- a/lib/Voice/AudioEncoder.js +++ b/lib/Voice/AudioEncoder.js @@ -26,13 +26,48 @@ var AudioEncoder = (function () { return this.opus.encode(buffer, 1920); }; + AudioEncoder.prototype.encodeStream = function encodeStream(stream) { + var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err, buffer) {} : arguments[1]; + + var self = this; + return new Promise(function (resolve, reject) { + var enc = cpoc.spawn("ffmpeg", ["-f", "s16le", "-ar", "48000", "-ac", "1", // this can be 2 but there's no point, discord makes it mono on playback, wasted bandwidth. + "-af", "volume=1", "pipe:1", "-i", "-"]); + + stream.pipe(enc.stdin); + + enc.stdout.once("readable", function () { + callback(null, { + proc: enc, + stream: enc.stdout, + instream: stream + }); + resolve({ + proc: enc, + stream: enc.stdout, + instream: stream + }); + }); + + enc.stdout.on("end", function () { + callback("end"); + reject("end"); + }); + + enc.stdout.on("close", function () { + callback("close"); + reject("close"); + }); + }); + }; + AudioEncoder.prototype.encodeFile = function encodeFile(file) { var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err, buffer) {} : arguments[1]; var self = this; return new Promise(function (resolve, reject) { - var enc = cpoc.spawn("ffmpeg", ["-i", file, "-f", "s16le", "-ar", "48000", "-ac", "1", // this can be 2 but there's no point, discord makes it mono on playback, wasted bandwidth. - "-af", "volume=1", "pipe:1"]); + var enc = cpoc.spawn("ffmpeg", ["-f", "s16le", "-ar", "48000", "-ac", "1", // this can be 2 but there's no point, discord makes it mono on playback, wasted bandwidth. + "-af", "volume=1", "pipe:1", "-i", file]); enc.stdout.once("readable", function () { callback(null, { diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index 08393fc32..226527f33 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -57,7 +57,10 @@ var VoiceConnection = (function (_EventEmitter) { VoiceConnection.prototype.stopPlaying = function stopPlaying() { this.playing = false; this.playingIntent = null; - if (this.streamProc) this.streamProc.kill(); + if (this.instream) { + console.log(this.instream); + this.instream.unpipe(this.streamProc); + }if (this.streamProc) this.streamProc.destroy(); }; VoiceConnection.prototype.playRawStream = function playRawStream(stream) { @@ -80,14 +83,15 @@ var VoiceConnection = (function (_EventEmitter) { self.playingIntent = retStream; function send() { + if (!self.playingIntent || !self.playing) { self.setSpeaking(false); retStream.emit("end"); + self; return; } try { var buffer = stream.read(1920); - if (!buffer) { setTimeout(send, length * 10); // give chance for some data in 200ms to appear return; @@ -162,7 +166,7 @@ var VoiceConnection = (function (_EventEmitter) { if (!self.encoder.opus) { self.playing = false; self.emit("error", "No Opus!"); - self.emit("debug", "Tried to use node-opus, but opus not available - install it!"); + self.client.emit("debug", "Tried to use node-opus, but opus not available - install it!"); return; } var buffer = self.encoder.opusBuffer(rawbuffer); @@ -199,6 +203,31 @@ var VoiceConnection = (function (_EventEmitter) { function error() { var e = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; + console.log(e); + reject(e); + callback(e); + } + }); + }; + + VoiceConnection.prototype.playStream = function playStream(stream) { + var _this2 = this; + + var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err, str) {} : arguments[1]; + + var self = this; + return new Promise(function (resolve, reject) { + _this2.encoder.encodeStream(stream)["catch"](error).then(function (data) { + self.streamProc = data.proc; + self.instream = data.instream; + var intent = self.playRawStream(data.stream); + resolve(intent); + callback(null, intent); + }); + function error() { + var e = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; + + console.log(e); reject(e); callback(e); } @@ -206,12 +235,12 @@ var VoiceConnection = (function (_EventEmitter) { }; VoiceConnection.prototype.init = function init() { - var _this2 = this; + var _this3 = this; var self = this; dns.lookup(this.endpoint, function (err, address, family) { self.endpoint = address; - var vWS = self.vWS = new WebSocket("wss://" + _this2.endpoint, null, { rejectUnauthorized: false }); + var vWS = self.vWS = new WebSocket("wss://" + _this3.endpoint, null, { rejectUnauthorized: false }); var udpClient = self.udp = udp.createSocket("udp4"); var firstPacket = true; diff --git a/src/Voice/AudioEncoder.js b/src/Voice/AudioEncoder.js index ae7b9f6bf..7293d5ef1 100644 --- a/src/Voice/AudioEncoder.js +++ b/src/Voice/AudioEncoder.js @@ -23,16 +23,55 @@ class AudioEncoder{ } - encodeFile(file, callback=function(err, buffer){}){ + encodeStream(stream, callback=function(err, buffer){}){ var self = this; return new Promise((resolve, reject) => { var enc = cpoc.spawn("ffmpeg" , [ - "-i", file, "-f", "s16le", "-ar", "48000", "-ac", "1", // this can be 2 but there's no point, discord makes it mono on playback, wasted bandwidth. "-af", "volume=1", - "pipe:1" + "pipe:1", + "-i", "-" + ]); + + stream.pipe(enc.stdin); + + enc.stdout.once("readable", function() { + callback(null, { + proc : enc, + stream : enc.stdout, + instream : stream + }); + resolve({ + proc : enc, + stream : enc.stdout, + instream : stream + }); + }); + + enc.stdout.on("end", function() { + callback("end"); + reject("end"); + }); + + enc.stdout.on("close", function() { + callback("close"); + reject("close"); + }); + }); + } + + encodeFile(file, callback=function(err, buffer){}){ + var self = this; + return new Promise((resolve, reject) => { + var enc = cpoc.spawn("ffmpeg" , [ + "-f", "s16le", + "-ar", "48000", + "-ac", "1", // this can be 2 but there's no point, discord makes it mono on playback, wasted bandwidth. + "-af", "volume=1", + "pipe:1", + "-i", file ]); enc.stdout.once("readable", function() { @@ -45,7 +84,7 @@ class AudioEncoder{ stream : enc.stdout }); }); - + enc.stdout.on("end", function() { callback("end"); reject("end"); diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index fb02af894..a4b829bca 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -50,8 +50,11 @@ class VoiceConnection extends EventEmitter { stopPlaying() { this.playing = false; this.playingIntent = null; - if (this.streamProc) - this.streamProc.kill(); + if(this.instream){ + console.log(this.instream); + this.instream.unpipe(this.streamProc); + }if (this.streamProc) + this.streamProc.destroy(); } playRawStream(stream) { @@ -74,14 +77,15 @@ class VoiceConnection extends EventEmitter { self.playingIntent = retStream; function send() { + if (!self.playingIntent || !self.playing) { self.setSpeaking(false); retStream.emit("end"); + self return; } try { var buffer = stream.read(1920); - if (!buffer) { setTimeout(send, length * 10); // give chance for some data in 200ms to appear return; @@ -159,7 +163,7 @@ class VoiceConnection extends EventEmitter { if(!self.encoder.opus){ self.playing=false; self.emit("error", "No Opus!"); - self.emit("debug", "Tried to use node-opus, but opus not available - install it!"); + self.client.emit("debug", "Tried to use node-opus, but opus not available - install it!"); return; } var buffer = self.encoder.opusBuffer(rawbuffer); @@ -196,6 +200,29 @@ class VoiceConnection extends EventEmitter { }); function error(e = true) { + console.log(e); + reject(e); + callback(e); + } + }) + } + + playStream(stream, callback = function (err, str) { }) { + var self = this; + return new Promise((resolve, reject) => { + this.encoder + .encodeStream(stream) + .catch(error) + .then(data => { + self.streamProc = data.proc; + self.instream = data.instream; + var intent = self.playRawStream(data.stream); + resolve(intent); + callback(null, intent); + + }); + function error(e = true) { + console.log(e); reject(e); callback(e); } diff --git a/test/bot.1.js b/test/bot.1.js index e597d6edc..c15b05e5d 100644 --- a/test/bot.1.js +++ b/test/bot.1.js @@ -1,40 +1,55 @@ var Discord = require("../"); var client = new Discord.Client(); -client.on("debug", (m) => console.log("[debug]",m)); +var request = require("superagent"); +client.on("debug", (m) => console.log("[debug]", m)); client.on("warn", (m) => console.log("[warn]", m)); var start = Date.now(); client.on("message", m => { - if(m.content === "&init"){ - for(var channel of m.channel.server.channels){ - if(channel instanceof Discord.VoiceChannel){ + if (m.content === "&init") { + for (var channel of m.channel.server.channels) { + if (channel instanceof Discord.VoiceChannel) { client.joinVoiceChannel(channel).catch(error); break; } } } - if(m.content.startsWith("$$$ stop")){ - if(client.internal.voiceConnection){ + if (m.content.startsWith("$$$ stop")) { + if (client.internal.voiceConnection) { client.internal.voiceConnection.stopPlaying(); } return; } - if(m.content.startsWith("$$$")){ + if (m.content.startsWith("$$$")) { var chan; var rest = m.content.split(" "); rest.splice(0, 1); rest = rest.join(" "); - if(client.internal.voiceConnection){ + if (client.internal.voiceConnection) { client.reply(m, "ok, I'll play that for you"); var connection = client.internal.voiceConnection; - connection.playFile("C:/users/amish/desktop/"+rest); + connection.playFile("C:/users/amish/desktop/" + rest); + } + } if (m.content.startsWith("$pipebitch")) { + var chan; + var rest = m.content.split(" "); + rest.splice(0, 1); + rest = rest.join(" "); + + if (client.internal.voiceConnection) { + client.reply(m, "ok, I'll play that for you " + rest); + var connection = client.internal.voiceConnection; + + var request = require("request"); + + connection.playStream(request(rest)); } } }); -function error(e){ +function error(e) { console.log(e.stack); process.exit(0); } -client.login(process.env["discordEmail"], process.env["discordPass"]).catch((e)=>console.log(e)); \ No newline at end of file +client.login(process.env["discordEmail"], process.env["discordPass"]).catch((e) => console.log(e)); \ No newline at end of file From 639fc99b861e0ed6d7e07f24b126a71217ac661e Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sun, 8 Nov 2015 12:41:39 +0000 Subject: [PATCH 21/23] Temporary fix --- lib/Voice/VoiceConnection.js | 6 +++--- src/Voice/VoiceConnection.js | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index 226527f33..ebe6604fb 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -58,9 +58,9 @@ var VoiceConnection = (function (_EventEmitter) { this.playing = false; this.playingIntent = null; if (this.instream) { - console.log(this.instream); - this.instream.unpipe(this.streamProc); - }if (this.streamProc) this.streamProc.destroy(); + this.instream.end(); + this.instream.destroy(); + } }; VoiceConnection.prototype.playRawStream = function playRawStream(stream) { diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index a4b829bca..9e053ad50 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -51,10 +51,9 @@ class VoiceConnection extends EventEmitter { this.playing = false; this.playingIntent = null; if(this.instream){ - console.log(this.instream); - this.instream.unpipe(this.streamProc); - }if (this.streamProc) - this.streamProc.destroy(); + this.instream.end(); + this.instream.destroy(); + } } playRawStream(stream) { From 71786148bcc2b1ac8dc8593910accbe2f91c870a Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sun, 8 Nov 2015 12:55:10 +0000 Subject: [PATCH 22/23] Added playStream and renamed some functions --- lib/Voice/VoiceConnection.js | 10 ++++------ src/Voice/VoiceConnection.js | 10 ++++------ test/bot.1.js | 5 ++--- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index ebe6604fb..ea2f4812b 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -63,7 +63,7 @@ var VoiceConnection = (function (_EventEmitter) { } }; - VoiceConnection.prototype.playRawStream = function playRawStream(stream) { + VoiceConnection.prototype.playStream = function playStream(stream) { var self = this; @@ -196,21 +196,20 @@ var VoiceConnection = (function (_EventEmitter) { return new Promise(function (resolve, reject) { _this.encoder.encodeFile(stream)["catch"](error).then(function (data) { self.streamProc = data.proc; - var intent = self.playRawStream(data.stream); + var intent = self.playStream(data.stream); resolve(intent); callback(null, intent); }); function error() { var e = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; - console.log(e); reject(e); callback(e); } }); }; - VoiceConnection.prototype.playStream = function playStream(stream) { + VoiceConnection.prototype.playRawStream = function playRawStream(stream) { var _this2 = this; var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err, str) {} : arguments[1]; @@ -220,14 +219,13 @@ var VoiceConnection = (function (_EventEmitter) { _this2.encoder.encodeStream(stream)["catch"](error).then(function (data) { self.streamProc = data.proc; self.instream = data.instream; - var intent = self.playRawStream(data.stream); + var intent = self.playStream(data.stream); resolve(intent); callback(null, intent); }); function error() { var e = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; - console.log(e); reject(e); callback(e); } diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index 9e053ad50..040d00ea0 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -56,7 +56,7 @@ class VoiceConnection extends EventEmitter { } } - playRawStream(stream) { + playStream(stream) { var self = this; @@ -193,20 +193,19 @@ class VoiceConnection extends EventEmitter { .catch(error) .then(data => { self.streamProc = data.proc; - var intent = self.playRawStream(data.stream); + var intent = self.playStream(data.stream); resolve(intent); callback(null, intent); }); function error(e = true) { - console.log(e); reject(e); callback(e); } }) } - playStream(stream, callback = function (err, str) { }) { + playRawStream(stream, callback = function (err, str) { }) { var self = this; return new Promise((resolve, reject) => { this.encoder @@ -215,13 +214,12 @@ class VoiceConnection extends EventEmitter { .then(data => { self.streamProc = data.proc; self.instream = data.instream; - var intent = self.playRawStream(data.stream); + var intent = self.playStream(data.stream); resolve(intent); callback(null, intent); }); function error(e = true) { - console.log(e); reject(e); callback(e); } diff --git a/test/bot.1.js b/test/bot.1.js index c15b05e5d..cc2262f19 100644 --- a/test/bot.1.js +++ b/test/bot.1.js @@ -4,6 +4,7 @@ var request = require("superagent"); client.on("debug", (m) => console.log("[debug]", m)); client.on("warn", (m) => console.log("[warn]", m)); var start = Date.now(); + client.on("message", m => { if (m.content === "&init") { for (var channel of m.channel.server.channels) { @@ -38,10 +39,8 @@ client.on("message", m => { if (client.internal.voiceConnection) { client.reply(m, "ok, I'll play that for you " + rest); var connection = client.internal.voiceConnection; - - var request = require("request"); - connection.playStream(request(rest)); + connection.playFile(rest); } } }); From 2c548dd90321c9bd242e6aac5a50fb3fd05b544e Mon Sep 17 00:00:00 2001 From: hydrabolt Date: Sun, 8 Nov 2015 15:56:49 +0000 Subject: [PATCH 23/23] Updated bod --- test/bot.1.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/bot.1.js b/test/bot.1.js index cc2262f19..afcd544ff 100644 --- a/test/bot.1.js +++ b/test/bot.1.js @@ -9,6 +9,7 @@ client.on("message", m => { if (m.content === "&init") { for (var channel of m.channel.server.channels) { if (channel instanceof Discord.VoiceChannel) { + client.reply(m, channel.name + " - " + channel.id); client.joinVoiceChannel(channel).catch(error); break; } @@ -30,17 +31,25 @@ client.on("message", m => { var connection = client.internal.voiceConnection; connection.playFile("C:/users/amish/desktop/" + rest); } - } if (m.content.startsWith("$pipebitch")) { + } if (m.content.startsWith("pipeit")) { var chan; var rest = m.content.split(" "); rest.splice(0, 1); rest = rest.join(" "); if (client.internal.voiceConnection) { - client.reply(m, "ok, I'll play that for you " + rest); var connection = client.internal.voiceConnection; - connection.playFile(rest); + connection.playFile(rest).then(intent => { + client.reply(m, "playing!").then((msg) => { + + intent.on("end", () => { + client.updateMessage(msg, "that song has finished now."); + }); + + }); + + }); } } });