From d122184b6b685fcd9ba3c0b6f937226adf7c6c77 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Sat, 5 Dec 2015 20:57:08 +0000 Subject: [PATCH 1/2] voice fixes --- .gitignore | 1 - lib/Voice/AudioEncoder.js | 8 ++-- lib/Voice/VoiceConnection.js | 4 +- src/Voice/AudioEncoder.js | 82 ++++++++++++++++++------------------ src/Voice/VoiceConnection.js | 4 +- test/msgbot.js | 38 +++++++++++++++++ 6 files changed, 87 insertions(+), 50 deletions(-) create mode 100644 test/msgbot.js diff --git a/.gitignore b/.gitignore index 36326a28..e48f0097 100644 --- a/.gitignore +++ b/.gitignore @@ -36,5 +36,4 @@ build/Release node_modules test/auth.json examples/auth.json -test/msgbot.js docs/_build \ No newline at end of file diff --git a/lib/Voice/AudioEncoder.js b/lib/Voice/AudioEncoder.js index 4b8489b9..37031d32 100644 --- a/lib/Voice/AudioEncoder.js +++ b/lib/Voice/AudioEncoder.js @@ -67,8 +67,7 @@ var AudioEncoder = (function () { var self = this; return new Promise(function (resolve, reject) { - var enc = _child_process2["default"].spawn(self.getCommand(), ["-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", "-"]); + var enc = _child_process2["default"].spawn(self.getCommand(), ['-i', "-", '-f', 's16le', '-ar', '48000', '-ac', 1, 'pipe:1']); stream.pipe(enc.stdin); @@ -102,8 +101,7 @@ var AudioEncoder = (function () { var self = this; return new Promise(function (resolve, reject) { - var enc = _child_process2["default"].spawn(self.getCommand(), ["-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]); + var enc = _child_process2["default"].spawn(self.getCommand(), ['-i', file, '-f', 's16le', '-ar', '48000', '-ac', 1, 'pipe:1']); enc.stdout.once("readable", function () { callback(null, { @@ -117,11 +115,13 @@ var AudioEncoder = (function () { }); enc.stdout.on("end", function () { + console.log("end"); callback("end"); reject("end"); }); enc.stdout.on("close", function () { + console.log("close"); callback("close"); reject("close"); }); diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index ac43034e..ac4125ee 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -122,11 +122,10 @@ var VoiceConnection = (function (_EventEmitter) { self.playingIntent = retStream; function send() { - if (!self.playingIntent || !self.playing) { self.setSpeaking(false); retStream.emit("end"); - self; + console.log("ending 1"); return; } try { @@ -137,6 +136,7 @@ var VoiceConnection = (function (_EventEmitter) { if (onWarning) { retStream.emit("end"); self.setSpeaking(false); + console.log("ending 2"); return; } else { onWarning = true; diff --git a/src/Voice/AudioEncoder.js b/src/Voice/AudioEncoder.js index 8e1f86b2..df94630a 100644 --- a/src/Voice/AudioEncoder.js +++ b/src/Voice/AudioEncoder.js @@ -3,36 +3,36 @@ import cpoc from "child_process"; var opus; -try{ +try { opus = require("node-opus"); -}catch(e){ +} catch (e) { // no opus! } export default class AudioEncoder { - constructor(){ - if(opus){ + constructor() { + if (opus) { this.opus = new opus.OpusEncoder(48000, 1); } this.choice = false; } - opusBuffer(buffer){ + opusBuffer(buffer) { return this.opus.encode(buffer, 1920); } - getCommand(force){ + getCommand(force) { - if(this.choice && force) + if (this.choice && force) return choice; var choices = ["avconv", "ffmpeg"]; - for(var choice of choices){ + for (var choice of choices) { var p = cpoc.spawnSync(choice); - if(!p.error){ + if (!p.error) { this.choice = choice; return choice; } @@ -41,74 +41,74 @@ export default class AudioEncoder { return "help"; } - encodeStream(stream, callback=function(err, buffer){}){ + encodeStream(stream, callback = function (err, buffer) { }) { var self = this; return new Promise((resolve, reject) => { - var enc = cpoc.spawn(self.getCommand() , [ - "-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", "-" + var enc = cpoc.spawn(self.getCommand(), [ + '-i', "-", + '-f', 's16le', + '-ar', '48000', + '-ac', 1, + 'pipe:1' ]); stream.pipe(enc.stdin); - enc.stdout.once("readable", function() { + enc.stdout.once("readable", function () { callback(null, { - proc : enc, - stream : enc.stdout, - instream : stream + proc: enc, + stream: enc.stdout, + instream: stream }); resolve({ - proc : enc, - stream : enc.stdout, - instream : stream + proc: enc, + stream: enc.stdout, + instream: stream }); }); - enc.stdout.on("end", function() { + enc.stdout.on("end", function () { callback("end"); reject("end"); }); - enc.stdout.on("close", function() { + enc.stdout.on("close", function () { callback("close"); reject("close"); }); }); } - encodeFile(file, callback=function(err, buffer){}){ + encodeFile(file, callback = function (err, buffer) { }) { var self = this; return new Promise((resolve, reject) => { - var enc = cpoc.spawn(self.getCommand() , [ - "-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 + var enc = cpoc.spawn(self.getCommand(), [ + '-i', file, + '-f', 's16le', + '-ar', '48000', + '-ac', 1, + 'pipe:1' ]); - enc.stdout.once("readable", function() { + enc.stdout.once("readable", function () { callback(null, { - proc : enc, - stream : enc.stdout + proc: enc, + stream: enc.stdout }); resolve({ - proc : enc, - stream : enc.stdout + proc: enc, + stream: enc.stdout }); }); - enc.stdout.on("end", function() { + enc.stdout.on("end", function () { + console.log("end"); callback("end"); reject("end"); }); - enc.stdout.on("close", function() { + enc.stdout.on("close", function () { + console.log("close"); callback("close"); reject("close"); }); diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index 4e4cb01b..1505c620 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -93,11 +93,10 @@ export default class VoiceConnection extends EventEmitter { self.playingIntent = retStream; function send() { - if (!self.playingIntent || !self.playing) { self.setSpeaking(false); retStream.emit("end"); - self + console.log("ending 1"); return; } try { @@ -108,6 +107,7 @@ export default class VoiceConnection extends EventEmitter { if (onWarning) { retStream.emit("end"); self.setSpeaking(false); + console.log("ending 2"); return; } else { onWarning = true; diff --git a/test/msgbot.js b/test/msgbot.js new file mode 100644 index 00000000..fff9ee26 --- /dev/null +++ b/test/msgbot.js @@ -0,0 +1,38 @@ +/* global describe */ +/* global process */ + +var Discord = require("../"); +var client = new Discord.Client(); +var request = require("request"); + +client.on("ready", () => { + console.log("ready"); +}); + +client.on("message", msg => { + + if(!msg.sender.equals(client.user)) + console.log("received message from " + msg.sender.username); + + if (msg.content === "$bind") { + msg.channel.server.channels.get("type", "voice").join(); + } + + if (msg.content.startsWith("$play")) { + var url = msg.content.split(" ")[1]; + + client.voiceConnection.playRawStream(request(url)); + + } + + if (msg.content === "$$$") { + client.sendMessage(msg.sender, "hi!"); + } + +}); + +console.log("INIT"); + +client.on("debug", console.log) + +client.login(process.env["ds_email"], process.env["ds_password"]).catch(console.log); \ No newline at end of file From e16211c4fb52934e97be33ced4b777b02af4f36e Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Sat, 5 Dec 2015 21:19:38 +0000 Subject: [PATCH 2/2] Added stereo support --- lib/Client/Client.js | 32 ++++++++++ lib/Client/InternalClient.js | 109 +++++++++++++++++++++++++++++++---- lib/Voice/AudioEncoder.js | 18 +++--- lib/Voice/VoiceConnection.js | 9 +-- src/Voice/AudioEncoder.js | 18 +++--- src/Voice/VoiceConnection.js | 10 ++-- test/msgbot.js | 6 +- 7 files changed, 167 insertions(+), 35 deletions(-) diff --git a/lib/Client/Client.js b/lib/Client/Client.js index 5c3365da..b108a559 100644 --- a/lib/Client/Client.js +++ b/lib/Client/Client.js @@ -359,6 +359,38 @@ var Client = (function (_EventEmitter) { return this.removeMemberFromRole(member, role, callback); }; + //def addMemberToRole + + Client.prototype.addMemberToRoles = function addMemberToRoles(member, roles) { + var callback = arguments.length <= 2 || arguments[2] === undefined ? function () /*err*/{} : arguments[2]; + + return this.internal.addMemberToRoles(member, roles).then(callback, errCB(callback)); + }; + + // def addUserToRole + + Client.prototype.addUserToRoles = function addUserToRoles(member, roles) { + var callback = arguments.length <= 2 || arguments[2] === undefined ? function () /*err*/{} : arguments[2]; + + return this.addMemberToRoles(member, roles, callback); + }; + + // def removeMemberFromRole + + Client.prototype.removeMemberFromRoles = function removeMemberFromRoles(member, roles) { + var callback = arguments.length <= 2 || arguments[2] === undefined ? function () /*err*/{} : arguments[2]; + + return this.internal.removeMemberFromRoles(member, roles).then(callback, errCB(callback)); + }; + + // def removeUserFromRole + + Client.prototype.removeUserFromRoles = function removeUserFromRoles(member, roles) { + var callback = arguments.length <= 2 || arguments[2] === undefined ? function () /*err*/{} : arguments[2]; + + return this.removeMemberFromRoles(member, roles, callback); + }; + // def createInvite Client.prototype.createInvite = function createInvite(chanServ, options) { diff --git a/lib/Client/InternalClient.js b/lib/Client/InternalClient.js index 938e2b08..5b8b47ad 100644 --- a/lib/Client/InternalClient.js +++ b/lib/Client/InternalClient.js @@ -662,6 +662,48 @@ var InternalClient = (function () { }).end(); }; + //def addMemberToRole + + InternalClient.prototype.addMemberToRoles = function addMemberToRoles(member, roles) { + member = this.resolver.resolveUser(member); + + if (!member) { + return Promise.reject(new Error("member not in server")); + } + + if (!Array.isArray(roles) || roles.length === 0) { + return Promise.reject(new Error("invalid array of roles")); + } + + var roleIDS = roles[0].server.memberMap[member.id].roles.map(function (r) { + return r.id; + }); + + for (var _iterator3 = roles, _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 role = _ref3; + + if (!role.server.memberMap[member.id]) { + return Promise.reject(new Error("member not in server")); + } + roleIDS.concat(role.id); + } + + return _superagent2["default"].patch(_Constants.Endpoints.SERVER_MEMBERS(role.server.id) + "/" + member.id).set("authorization", this.token).send({ + roles: roleIDS + }).end(); + }; + //def removeMemberFromRole InternalClient.prototype.removeMemberFromRole = function removeMemberFromRole(member, role) { @@ -682,7 +724,54 @@ var InternalClient = (function () { for (var item in roleIDS) { if (roleIDS[item] === role.id) { roleIDS.splice(item, 1); - //missing break? + break; + } + } + + return _superagent2["default"].patch(_Constants.Endpoints.SERVER_MEMBERS(role.server.id) + "/" + member.id).set("authorization", this.token).send({ + roles: roleIDS + }).end(); + }; + + //def removeMemberFromRoles + + InternalClient.prototype.removeMemberFromRoles = function removeMemberFromRoles(member, roles) { + member = this.resolver.resolveUser(member); + + if (!member) { + return Promise.reject(new Error("member not in server")); + } + + if (!Array.isArray(roles) || roles.length === 0) { + return Promise.reject(new Error("invalid array of roles")); + } + + var roleIDS = roles[0].server.memberMap[member.id].roles.map(function (r) { + return r.id; + }); + + for (var _iterator4 = roles, _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { + var _ref4; + + if (_isArray4) { + if (_i4 >= _iterator4.length) break; + _ref4 = _iterator4[_i4++]; + } else { + _i4 = _iterator4.next(); + if (_i4.done) break; + _ref4 = _i4.value; + } + + var role = _ref4; + + if (!role.server.memberMap[member.id]) { + return Promise.reject(new Error("member not in server")); + } + for (var item in roleIDS) { + if (roleIDS[item] === role.id) { + roleIDS.splice(item, 1); + break; + } } } @@ -1140,19 +1229,19 @@ var InternalClient = (function () { var server = self.servers.get("id", data.id); if (server) { - for (var _iterator3 = server.channels, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { - var _ref3; + for (var _iterator5 = server.channels, _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { + var _ref5; - if (_isArray3) { - if (_i3 >= _iterator3.length) break; - _ref3 = _iterator3[_i3++]; + if (_isArray5) { + if (_i5 >= _iterator5.length) break; + _ref5 = _iterator5[_i5++]; } else { - _i3 = _iterator3.next(); - if (_i3.done) break; - _ref3 = _i3.value; + _i5 = _iterator5.next(); + if (_i5.done) break; + _ref5 = _i5.value; } - var channel = _ref3; + var channel = _ref5; self.channels.remove(channel); } diff --git a/lib/Voice/AudioEncoder.js b/lib/Voice/AudioEncoder.js index 37031d32..1abdcf20 100644 --- a/lib/Voice/AudioEncoder.js +++ b/lib/Voice/AudioEncoder.js @@ -22,7 +22,7 @@ var AudioEncoder = (function () { _classCallCheck(this, AudioEncoder); if (opus) { - this.opus = new opus.OpusEncoder(48000, 1); + this.opus = new opus.OpusEncoder(48000, 2); } this.choice = false; } @@ -67,7 +67,7 @@ var AudioEncoder = (function () { var self = this; return new Promise(function (resolve, reject) { - var enc = _child_process2["default"].spawn(self.getCommand(), ['-i', "-", '-f', 's16le', '-ar', '48000', '-ac', 1, 'pipe:1']); + var enc = _child_process2["default"].spawn(self.getCommand(), ['-i', "-", '-f', 's16le', '-ar', '48000', '-ac', 2, 'pipe:1']); stream.pipe(enc.stdin); @@ -75,12 +75,14 @@ var AudioEncoder = (function () { callback(null, { proc: enc, stream: enc.stdout, - instream: stream + instream: stream, + channels: 2 }); resolve({ proc: enc, stream: enc.stdout, - instream: stream + instream: stream, + channels: 2 }); }); @@ -101,16 +103,18 @@ var AudioEncoder = (function () { var self = this; return new Promise(function (resolve, reject) { - var enc = _child_process2["default"].spawn(self.getCommand(), ['-i', file, '-f', 's16le', '-ar', '48000', '-ac', 1, 'pipe:1']); + var enc = _child_process2["default"].spawn(self.getCommand(), ['-i', file, '-f', 's16le', '-ar', '48000', '-ac', 2, 'pipe:1']); enc.stdout.once("readable", function () { callback(null, { proc: enc, - stream: enc.stdout + stream: enc.stdout, + channels: 2 }); resolve({ proc: enc, - stream: enc.stdout + stream: enc.stdout, + channels: 2 }); }); diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index ac4125ee..96bde907 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -103,6 +103,7 @@ var VoiceConnection = (function (_EventEmitter) { }; VoiceConnection.prototype.playStream = function playStream(stream) { + var channels = arguments.length <= 1 || arguments[1] === undefined ? 2 : arguments[1]; var self = this; @@ -130,7 +131,7 @@ var VoiceConnection = (function (_EventEmitter) { } try { - var buffer = stream.read(1920); + var buffer = stream.read(1920 * channels); if (!buffer) { if (onWarning) { @@ -145,8 +146,8 @@ var VoiceConnection = (function (_EventEmitter) { } } - if (buffer.length !== 1920) { - var newBuffer = new Buffer(1920).fill(0); + if (buffer.length !== 1920 * channels) { + var newBuffer = new Buffer(1920 * channels).fill(0); buffer.copy(newBuffer); buffer = newBuffer; } @@ -238,7 +239,7 @@ 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.playStream(data.stream); + var intent = self.playStream(data.stream, 2); resolve(intent); callback(null, intent); }); diff --git a/src/Voice/AudioEncoder.js b/src/Voice/AudioEncoder.js index df94630a..99546ffe 100644 --- a/src/Voice/AudioEncoder.js +++ b/src/Voice/AudioEncoder.js @@ -12,7 +12,7 @@ try { export default class AudioEncoder { constructor() { if (opus) { - this.opus = new opus.OpusEncoder(48000, 1); + this.opus = new opus.OpusEncoder(48000, 2); } this.choice = false; } @@ -48,7 +48,7 @@ export default class AudioEncoder { '-i', "-", '-f', 's16le', '-ar', '48000', - '-ac', 1, + '-ac', 2, 'pipe:1' ]); @@ -58,12 +58,14 @@ export default class AudioEncoder { callback(null, { proc: enc, stream: enc.stdout, - instream: stream + instream: stream, + channels : 2 }); resolve({ proc: enc, stream: enc.stdout, - instream: stream + instream: stream, + channels : 2 }); }); @@ -86,18 +88,20 @@ export default class AudioEncoder { '-i', file, '-f', 's16le', '-ar', '48000', - '-ac', 1, + '-ac', 2, 'pipe:1' ]); enc.stdout.once("readable", function () { callback(null, { proc: enc, - stream: enc.stdout + stream: enc.stdout, + channels : 2 }); resolve({ proc: enc, - stream: enc.stdout + stream: enc.stdout, + channels : 2 }); }); diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index 1505c620..ecdc9356 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -73,7 +73,7 @@ export default class VoiceConnection extends EventEmitter { } } - playStream(stream) { + playStream(stream, channels=2) { var self = this; @@ -101,7 +101,7 @@ export default class VoiceConnection extends EventEmitter { } try { - var buffer = stream.read(1920); + var buffer = stream.read(1920 * channels); if (!buffer) { if (onWarning) { @@ -116,8 +116,8 @@ export default class VoiceConnection extends EventEmitter { } } - if(buffer.length !== 1920) { - var newBuffer = new Buffer(1920).fill(0); + if(buffer.length !== 1920 * channels) { + var newBuffer = new Buffer(1920 * channels).fill(0); buffer.copy(newBuffer); buffer = newBuffer; } @@ -213,7 +213,7 @@ export default class VoiceConnection extends EventEmitter { .catch(error) .then(data => { self.streamProc = data.proc; - var intent = self.playStream(data.stream); + var intent = self.playStream(data.stream, 2); resolve(intent); callback(null, intent); diff --git a/test/msgbot.js b/test/msgbot.js index fff9ee26..a651f66b 100644 --- a/test/msgbot.js +++ b/test/msgbot.js @@ -3,7 +3,7 @@ var Discord = require("../"); var client = new Discord.Client(); -var request = require("request"); +var request = require("superagent"); client.on("ready", () => { console.log("ready"); @@ -21,7 +21,9 @@ client.on("message", msg => { if (msg.content.startsWith("$play")) { var url = msg.content.split(" ")[1]; - client.voiceConnection.playRawStream(request(url)); + client.voiceConnection.playFile(url); + + console.log(request.get(url).end()); }