Simulator first commit
This commit is contained in:
97
node_modules/mumble-client/README.md
generated
vendored
Normal file
97
node_modules/mumble-client/README.md
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
# mumble-client
|
||||
|
||||
This module implements the client side of the Mumble protocol for use in both Nodejs and the browser.
|
||||
It does not enforce any particular transport protocol nor does it provide the audio encoder/decoder.
|
||||
See [mumble-client-tcp] and [mumble-client-udp] and [mumble-client-codecs-node] for creating a Nodejs based application or
|
||||
[mumble-client-websocket] and [mumble-client-codecs-browser] for a browser app.
|
||||
|
||||
### Usage
|
||||
|
||||
Node: All functions that take a Node-style callback as their last argument may also be used without that callback and will then instead return a [Promise].
|
||||
|
||||
Most transport modules will have their own MumbleClient factory functions.
|
||||
Therefore this is only of importance if you do not wish to use any transport module or are implementing your one.
|
||||
Note that encryption of the data stream has to be done by the transport module while the voice stream is encrypted by this module.
|
||||
```javascript
|
||||
var MumbleClient = require('mumble-client')
|
||||
var client = new MumbleClient({
|
||||
username: 'Test',
|
||||
password: 'Pass123'
|
||||
})
|
||||
// someDuplexStream is used for data transmission and as a voice channel fallback
|
||||
client.connectDataStream(someDuplexStream, function (err, client) {
|
||||
if (err) throw err
|
||||
|
||||
// Connection established
|
||||
console.log('Welcome message:', client.welcomeMessage)
|
||||
console.log('Actual username:', client.self.username)
|
||||
|
||||
// Optionally connect a potentially lossy, udp-like voice channel
|
||||
client.connectVoiceStream(someOtherDuplexStream)
|
||||
|
||||
var testChannel = client.getChannel('Test Channel')
|
||||
if (testChannel) {
|
||||
client.self.setChannel(testChannel)
|
||||
}
|
||||
|
||||
client.users.forEach(function (user) {
|
||||
console.log('User', user.username, 'is in channel', user.channel.name)
|
||||
})
|
||||
})
|
||||
|
||||
// You may then register listeners for the 'voice' event to receive a stream
|
||||
// of raw PCM frames.
|
||||
client.on('newUser', function (user) {
|
||||
user.on('voice', function (stream) {
|
||||
stream.on('data', function (data) {
|
||||
// Interleaved IEEE754 32-bit linear PCM with nominal range between -1 and +1
|
||||
// May be of zero length which is usually only the case when voice.end is true
|
||||
console.log(data.pcm) // Float32Array
|
||||
console.log(data.numberOfChannels)
|
||||
|
||||
// Target indicates who the user is talking to
|
||||
// Can be one of: 'normal', 'whisper', 'shout'
|
||||
console.log(data.target)
|
||||
|
||||
// Neither numberOfChannels nor target should normally change during one
|
||||
// transmission however this cannot be guaranteed
|
||||
}).on('end', function () {
|
||||
// The current voice transmission has ended, stateful decoders have been reset
|
||||
// Can be used to disable the 'talking' indicator of this use
|
||||
// Because the last packet might get lost due to packet loss, a timer is
|
||||
// used to always fire this event. As a result a single transmission might
|
||||
// be split when packets are delayed.
|
||||
console.log(user.username, 'stopped talking.')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// To send audio, pipe your raw PCM samples (as Float32Array or Buffer) at
|
||||
// 48kHz into an outgoing stream created with Client#createVoiceStream.
|
||||
// Any audio piped in the stream while muted, suppressed or not yet connected
|
||||
// will be silently dropped.
|
||||
// First argument is the target: 0 is normal talking, 1-31 are voice targets
|
||||
var voiceStream = client.createVoiceStream(0)
|
||||
myPcmSource.pipe(voiceStream)
|
||||
// Make sure the stream is ended when the transmission should end
|
||||
// For positional audio the Float32Array is wrapped in an object which
|
||||
// also contains the position:
|
||||
myPcmSource.on('data', function (chunk) {
|
||||
voiceStream.write({
|
||||
pcm: chunk,
|
||||
x: 1,
|
||||
y: 2.5,
|
||||
z: 3
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### License
|
||||
MIT
|
||||
|
||||
[mumble-client-tcp]: https://github.com/johni0702/mumble-client-tcp
|
||||
[mumble-client-udp]: https://github.com/johni0702/mumble-client-udp
|
||||
[mumble-client-websocket]: https://github.com/johni0702/mumble-client-websocket
|
||||
[mumble-client-codecs-node]: https://github.com/johni0702/mumble-client-codecs-node
|
||||
[mumble-client-codecs-browser]: https://github.com/johni0702/mumble-client-codecs-browser
|
||||
[Promise]: https://github.com/then/promise
|
1
node_modules/mumble-client/index.js
generated
vendored
Normal file
1
node_modules/mumble-client/index.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./lib/client.js').default
|
294
node_modules/mumble-client/lib/channel.js
generated
vendored
Normal file
294
node_modules/mumble-client/lib/channel.js
generated
vendored
Normal file
@ -0,0 +1,294 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
|
||||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||||
|
||||
var _events = require('events');
|
||||
|
||||
var _removeValue = require('remove-value');
|
||||
|
||||
var _removeValue2 = _interopRequireDefault(_removeValue);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
|
||||
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
|
||||
|
||||
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 Channel = function (_EventEmitter) {
|
||||
_inherits(Channel, _EventEmitter);
|
||||
|
||||
function Channel(client, id) {
|
||||
_classCallCheck(this, Channel);
|
||||
|
||||
var _this = _possibleConstructorReturn(this, (Channel.__proto__ || Object.getPrototypeOf(Channel)).call(this));
|
||||
|
||||
_this._client = client;
|
||||
_this._id = id;
|
||||
_this._links = [];
|
||||
_this.users = [];
|
||||
_this.children = [];
|
||||
_this._haveRequestedDescription = false;
|
||||
return _this;
|
||||
}
|
||||
|
||||
_createClass(Channel, [{
|
||||
key: '_remove',
|
||||
value: function _remove() {
|
||||
if (this.parent) {
|
||||
(0, _removeValue2.default)(this.parent.children, this);
|
||||
}
|
||||
this.emit('remove');
|
||||
}
|
||||
}, {
|
||||
key: '_update',
|
||||
value: function _update(msg) {
|
||||
var _this2 = this;
|
||||
|
||||
var changes = {};
|
||||
if (msg.name != null) {
|
||||
changes.name = this._name = msg.name;
|
||||
}
|
||||
if (msg.description != null) {
|
||||
changes.description = this._description = msg.description;
|
||||
}
|
||||
if (msg.description_hash != null) {
|
||||
changes.descriptionHash = this._descriptionHash = msg.description_hash;
|
||||
this._haveRequestedDescription = false; // invalidate previous request
|
||||
}
|
||||
if (msg.temporary != null) {
|
||||
changes.temporary = this._temporary = msg.temporary;
|
||||
}
|
||||
if (msg.position != null) {
|
||||
changes.position = this._position = msg.position;
|
||||
}
|
||||
if (msg.max_users != null) {
|
||||
changes.maxUsers = this._maxUsers = msg.max_users;
|
||||
}
|
||||
if (msg.links) {
|
||||
this._links = msg.links;
|
||||
changes.links = this.links;
|
||||
}
|
||||
if (msg.links_remove) {
|
||||
this._links = this._links.filter(function (e) {
|
||||
return !msg.links_remove.includes(e);
|
||||
});
|
||||
changes.links = this.links;
|
||||
}
|
||||
if (msg.links_add) {
|
||||
msg.links_add.filter(function (e) {
|
||||
return !_this2._links.includes(e);
|
||||
}).forEach(function (e) {
|
||||
return _this2._links.push(e);
|
||||
});
|
||||
changes.links = this.links;
|
||||
}
|
||||
if (msg.parent != null) {
|
||||
if (this.parent) {
|
||||
(0, _removeValue2.default)(this.parent.children, this);
|
||||
}
|
||||
this._parentId = msg.parent;
|
||||
if (this.parent) {
|
||||
this.parent.children.push(this);
|
||||
}
|
||||
changes.parent = this.parent;
|
||||
}
|
||||
this.emit('update', changes);
|
||||
}
|
||||
}, {
|
||||
key: 'setName',
|
||||
value: function setName(name) {
|
||||
this._client._send({
|
||||
name: 'ChannelState',
|
||||
payload: {
|
||||
channel_id: this._id,
|
||||
name: name
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'setParent',
|
||||
value: function setParent(parent) {
|
||||
this._client._send({
|
||||
name: 'ChannelState',
|
||||
payload: {
|
||||
channel_id: this._id,
|
||||
parent: parent._id
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'setTemporary',
|
||||
value: function setTemporary(temporary) {
|
||||
this._client._send({
|
||||
name: 'ChannelState',
|
||||
payload: {
|
||||
channel_id: this._id,
|
||||
temporary: temporary
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'setDescription',
|
||||
value: function setDescription(description) {
|
||||
this._client._send({
|
||||
name: 'ChannelState',
|
||||
payload: {
|
||||
channel_id: this._id,
|
||||
description: description
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'setPosition',
|
||||
value: function setPosition(position) {
|
||||
this._client._send({
|
||||
name: 'ChannelState',
|
||||
payload: {
|
||||
channel_id: this._id,
|
||||
position: position
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'setLinks',
|
||||
value: function setLinks(links) {
|
||||
this._client._send({
|
||||
name: 'ChannelState',
|
||||
payload: {
|
||||
channel_id: this._id,
|
||||
links: links.map(function (c) {
|
||||
return c._id;
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'setMaxUsers',
|
||||
value: function setMaxUsers(maxUsers) {
|
||||
this._client._send({
|
||||
name: 'ChannelState',
|
||||
payload: {
|
||||
channel_id: this._id,
|
||||
max_users: maxUsers
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'sendMessage',
|
||||
value: function sendMessage(message) {
|
||||
this._client._send({
|
||||
name: 'TextMessage',
|
||||
payload: {
|
||||
channel_id: [this._id],
|
||||
message: message
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'sendTreeMessage',
|
||||
value: function sendTreeMessage(message) {
|
||||
this._client._send({
|
||||
name: 'TextMessage',
|
||||
payload: {
|
||||
tree_id: [this._id],
|
||||
message: message
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'requestDescription',
|
||||
value: function requestDescription() {
|
||||
if (this._haveRequestedDescription) return;
|
||||
this._client._send({
|
||||
name: 'RequestBlob',
|
||||
payload: {
|
||||
channel_description: this._id
|
||||
}
|
||||
});
|
||||
this._haveRequestedDescription = true;
|
||||
}
|
||||
}, {
|
||||
key: 'id',
|
||||
get: function get() {
|
||||
return this._id;
|
||||
}
|
||||
}, {
|
||||
key: 'name',
|
||||
get: function get() {
|
||||
return this._name;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set name. Use #setName(name) instead.');
|
||||
}
|
||||
}, {
|
||||
key: 'parent',
|
||||
get: function get() {
|
||||
return this._client._channelById[this._parentId];
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set parent. Use #setParent(channel) instead.');
|
||||
}
|
||||
}, {
|
||||
key: 'description',
|
||||
get: function get() {
|
||||
return this._description;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set description. Use #setDescription(desc) instead.');
|
||||
}
|
||||
}, {
|
||||
key: 'descriptionHash',
|
||||
get: function get() {
|
||||
return this._descriptionHash;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set descriptionHash.');
|
||||
}
|
||||
}, {
|
||||
key: 'temporary',
|
||||
get: function get() {
|
||||
return this._temporary;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set temporary. Use #setTemporary(tmp) instead.');
|
||||
}
|
||||
}, {
|
||||
key: 'position',
|
||||
get: function get() {
|
||||
return this._position;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set position.');
|
||||
}
|
||||
}, {
|
||||
key: 'maxUsers',
|
||||
get: function get() {
|
||||
return this._maxUsers;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set maxUsers.');
|
||||
}
|
||||
}, {
|
||||
key: 'links',
|
||||
get: function get() {
|
||||
var _this3 = this;
|
||||
|
||||
return this._links.map(function (id) {
|
||||
return _this3._client._channelById[id];
|
||||
});
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set links. Use #setLinks(links) instead.');
|
||||
}
|
||||
}]);
|
||||
|
||||
return Channel;
|
||||
}(_events.EventEmitter);
|
||||
|
||||
exports.default = Channel;
|
860
node_modules/mumble-client/lib/client.js
generated
vendored
Normal file
860
node_modules/mumble-client/lib/client.js
generated
vendored
Normal file
@ -0,0 +1,860 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
|
||||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||||
|
||||
var _mumbleStreams = require('mumble-streams');
|
||||
|
||||
var _mumbleStreams2 = _interopRequireDefault(_mumbleStreams);
|
||||
|
||||
var _reduplexer = require('reduplexer');
|
||||
|
||||
var _reduplexer2 = _interopRequireDefault(_reduplexer);
|
||||
|
||||
var _events = require('events');
|
||||
|
||||
var _through = require('through2');
|
||||
|
||||
var _through2 = _interopRequireDefault(_through);
|
||||
|
||||
var _promise = require('promise');
|
||||
|
||||
var _promise2 = _interopRequireDefault(_promise);
|
||||
|
||||
var _dropStream = require('drop-stream');
|
||||
|
||||
var _dropStream2 = _interopRequireDefault(_dropStream);
|
||||
|
||||
var _utils = require('./utils.js');
|
||||
|
||||
var _user2 = require('./user');
|
||||
|
||||
var _user3 = _interopRequireDefault(_user2);
|
||||
|
||||
var _channel = require('./channel');
|
||||
|
||||
var _channel2 = _interopRequireDefault(_channel);
|
||||
|
||||
var _removeValue = require('remove-value');
|
||||
|
||||
var _removeValue2 = _interopRequireDefault(_removeValue);
|
||||
|
||||
var _statsIncremental = require('stats-incremental');
|
||||
|
||||
var _statsIncremental2 = _interopRequireDefault(_statsIncremental);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
|
||||
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
|
||||
|
||||
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 DenyType = _mumbleStreams2.default.data.messages.PermissionDenied.DenyType;
|
||||
|
||||
/*
|
||||
* @typedef {'Opus'} Codec
|
||||
*/
|
||||
|
||||
/**
|
||||
* Number of the voice target when outgoing (0 for normal talking, 1-31 for
|
||||
* a voice target).
|
||||
* String describing the source when incoming.
|
||||
* @typedef {number|'normal'|'shout'|'whisper'} VoiceTarget
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} VoiceData
|
||||
* @property {VoiceTarget} target - Target of the audio
|
||||
* @property {Codec} codec - The codec of the audio packet
|
||||
* @property {Buffer} frame - Encoded audio frame, null indicates a lost frame
|
||||
* @property {?Position} position - Position of audio source
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interleaved 32-bit float PCM frames in [-1; 1] range with sample rate of 48k.
|
||||
* @typedef {object} PCMData
|
||||
* @property {VoiceTarget} target - Target of the audio
|
||||
* @property {Float32Array} pcm - The pcm data
|
||||
* @property {number} numberOfChannels - Number of channels
|
||||
* @property {?Position} position - Position of audio source
|
||||
* @property {?number} bitrate - Target bitrate hint for encoder, see for default {@link MumbleClient#setAudioQuality}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transforms {@link VoiceData} to {@link PCMData}.
|
||||
* Should ignore any unknown codecs.
|
||||
*
|
||||
* @interface DecoderStream
|
||||
* @extends stream.Transform
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transforms {@link PCMData} to {@link VoiceData}.
|
||||
*
|
||||
* @interface EncoderStream
|
||||
* @extends stream.Transform
|
||||
*/
|
||||
|
||||
/**
|
||||
* @interface Codecs
|
||||
* @property {number[]} celt - List of celt versions supported by this implementation
|
||||
* @property {boolean} opus - Whether this implementation supports the Opus codec
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the duration of encoded voice data without actually decoding it.
|
||||
*
|
||||
* @function Codecs#getDuration
|
||||
* @param {Codec} codec - The codec
|
||||
* @param {Buffer} buffer - The encoded data
|
||||
* @return {number} The duration in milliseconds (has to be a multiple of 10)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new decoder stream for a transmission of the specified user.
|
||||
* This method is called for every single transmission (whenever a user starts
|
||||
* speaking), as such it must not be expensive.
|
||||
*
|
||||
* @function Codecs#createDecoderStream
|
||||
* @param {User} user - The user
|
||||
* @return {DecoderStream} The decoder stream
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new encoder stream for a outgoing transmission.
|
||||
* This method is called for every single transmission (whenever the user
|
||||
* starts speaking), as such it must not be expensive.
|
||||
*
|
||||
* @function Codecs#createEncoderStream
|
||||
* @param {Codec} codec - The codec
|
||||
* @return {EncoderStream} The endecoder stream
|
||||
*/
|
||||
|
||||
/**
|
||||
* Single use Mumble client.
|
||||
*/
|
||||
|
||||
var MumbleClient = function (_EventEmitter) {
|
||||
_inherits(MumbleClient, _EventEmitter);
|
||||
|
||||
/**
|
||||
* A mumble client.
|
||||
* This object may only be connected to one server and cannot be reused.
|
||||
*
|
||||
* @param {object} options - Options
|
||||
* @param {string} options.username - User name of the client
|
||||
* @param {string} [options.password] - Server password to use
|
||||
* @param {string[]} [options.tokens] - Array of access tokens to use
|
||||
* @param {string} [options.clientSoftware] - Client software name/version
|
||||
* @param {string} [options.osName] - Client operating system name
|
||||
* @param {string} [options.osVersion] - Client operating system version
|
||||
* @param {Codecs} [options.codecs] - Codecs used for voice
|
||||
* @param {number} [options.userVoiceTimeout] - Milliseconds after which an
|
||||
* inactive voice transmissions is timed out
|
||||
* @param {number} [options.maxInFlightDataPings] - Amount of data pings without response
|
||||
* after which the connection is considered timed out
|
||||
* @param {number} [options.dataPingInterval] - Interval of data pings (in ms)
|
||||
*/
|
||||
function MumbleClient(options) {
|
||||
_classCallCheck(this, MumbleClient);
|
||||
|
||||
var _this = _possibleConstructorReturn(this, (MumbleClient.__proto__ || Object.getPrototypeOf(MumbleClient)).call(this));
|
||||
|
||||
if (!options.username) {
|
||||
throw new Error('No username given');
|
||||
}
|
||||
|
||||
_this._options = options || {};
|
||||
_this._username = options.username;
|
||||
_this._password = options.password;
|
||||
_this._tokens = options.tokens;
|
||||
_this._codecs = options.codecs;
|
||||
|
||||
_this._dataPingInterval = options.dataPingInterval || 5000;
|
||||
_this._maxInFlightDataPings = options.maxInFlightDataPings || 2;
|
||||
_this._dataStats = new _statsIncremental2.default();
|
||||
_this._voiceStats = new _statsIncremental2.default();
|
||||
|
||||
_this._userById = {};
|
||||
_this._channelById = {};
|
||||
|
||||
_this.users = [];
|
||||
_this.channels = [];
|
||||
|
||||
_this._dataEncoder = new _mumbleStreams2.default.data.Encoder();
|
||||
_this._dataDecoder = new _mumbleStreams2.default.data.Decoder();
|
||||
_this._voiceEncoder = new _mumbleStreams2.default.voice.Encoder('server');
|
||||
_this._voiceDecoder = new _mumbleStreams2.default.voice.Decoder('server');
|
||||
_this._data = (0, _reduplexer2.default)(_this._dataEncoder, _this._dataDecoder, { objectMode: true });
|
||||
_this._voice = (0, _reduplexer2.default)(_this._voiceEncoder, _this._voiceDecoder, { objectMode: true });
|
||||
|
||||
_this._data.on('data', _this._onData.bind(_this));
|
||||
_this._voice.on('data', _this._onVoice.bind(_this));
|
||||
_this._voiceEncoder.on('data', function (data) {
|
||||
// TODO This should only be the fallback option
|
||||
_this._data.write({
|
||||
name: 'UDPTunnel',
|
||||
payload: data
|
||||
});
|
||||
});
|
||||
_this._voiceDecoder.on('unknown_codec', function (codecId) {
|
||||
return _this.emit('unknown_codec', codecId);
|
||||
});
|
||||
_this._data.on('end', _this.disconnect.bind(_this));
|
||||
|
||||
_this._registerErrorHandler(_this._data, _this._voice, _this._dataEncoder, _this._dataDecoder, _this._voiceEncoder, _this._voiceDecoder);
|
||||
|
||||
_this._disconnected = false;
|
||||
return _this;
|
||||
}
|
||||
|
||||
_createClass(MumbleClient, [{
|
||||
key: '_registerErrorHandler',
|
||||
value: function _registerErrorHandler() {
|
||||
var _iteratorNormalCompletion = true;
|
||||
var _didIteratorError = false;
|
||||
var _iteratorError = undefined;
|
||||
|
||||
try {
|
||||
for (var _iterator = arguments[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
||||
var obj = _step.value;
|
||||
|
||||
obj.on('error', this._error.bind(this));
|
||||
}
|
||||
} catch (err) {
|
||||
_didIteratorError = true;
|
||||
_iteratorError = err;
|
||||
} finally {
|
||||
try {
|
||||
if (!_iteratorNormalCompletion && _iterator.return) {
|
||||
_iterator.return();
|
||||
}
|
||||
} finally {
|
||||
if (_didIteratorError) {
|
||||
throw _iteratorError;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: '_error',
|
||||
value: function _error(reason) {
|
||||
this.emit('error', reason);
|
||||
this.disconnect();
|
||||
}
|
||||
}, {
|
||||
key: '_send',
|
||||
value: function _send(msg) {
|
||||
this._data.write(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects this client to a duplex stream that is used for the data channel.
|
||||
* The provided duplex stream is expected to be valid and usable.
|
||||
* Calling this method will begin the initialization of the connection.
|
||||
*
|
||||
* @param stream - The stream used for the data channel.
|
||||
* @param callback - Optional callback that is invoked when the connection has been established.
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'connectDataStream',
|
||||
value: function connectDataStream(stream, callback) {
|
||||
var _this2 = this;
|
||||
|
||||
if (this._dataStream) throw Error('Already connected!');
|
||||
this._dataStream = stream;
|
||||
|
||||
// Connect the supplied stream to the data channel encoder and decoder
|
||||
this._registerErrorHandler(stream);
|
||||
this._dataEncoder.pipe(stream).pipe(this._dataDecoder);
|
||||
|
||||
// Send the initial two packets
|
||||
this._send({
|
||||
name: 'Version',
|
||||
payload: {
|
||||
version: _mumbleStreams2.default.version.toUInt8(),
|
||||
release: this._options.clientSoftware || 'Node.js mumble-client',
|
||||
os: this._options.osName || (0, _utils.getOSName)(),
|
||||
os_version: this._options.osVersion || (0, _utils.getOSVersion)()
|
||||
}
|
||||
});
|
||||
this._send({
|
||||
name: 'Authenticate',
|
||||
payload: {
|
||||
username: this._username,
|
||||
password: this._password,
|
||||
tokens: this._tokens,
|
||||
celt_versions: (this._codecs || { celt: [] }).celt,
|
||||
opus: (this._codecs || { opus: false }).opus
|
||||
}
|
||||
});
|
||||
|
||||
return new _promise2.default(function (resolve, reject) {
|
||||
_this2.once('connected', function () {
|
||||
return resolve(_this2);
|
||||
});
|
||||
_this2.once('reject', reject);
|
||||
_this2.once('error', reject);
|
||||
}).nodeify(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects this client to a duplex stream that is used for the voice channel.
|
||||
* The provided duplex stream is expected to be valid and usable.
|
||||
* The stream may be unreliable. That is, it may lose packets or deliver them
|
||||
* out of order.
|
||||
* It must however gurantee that packets arrive unmodified and/or are dropped
|
||||
* when corrupted.
|
||||
* It is also responsible for any encryption that is necessary.
|
||||
*
|
||||
* Connecting a voice channel is entirely optional. If no voice channel
|
||||
* is connected, all voice data is tunneled through the data channel.
|
||||
*
|
||||
* @param stream - The stream used for the data channel.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'connectVoiceStream',
|
||||
value: function connectVoiceStream(stream) {
|
||||
// Connect the stream to the voice channel encoder and decoder
|
||||
this._registerErrorHandler(stream);
|
||||
this._voiceEncoder.pipe(stream).pipe(this._voiceDecoder);
|
||||
|
||||
// TODO: Ping packet
|
||||
}
|
||||
}, {
|
||||
key: 'createVoiceStream',
|
||||
value: function createVoiceStream() {
|
||||
var _this3 = this;
|
||||
|
||||
var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
||||
var numberOfChannels = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
|
||||
|
||||
if (!this._codecs) {
|
||||
return _dropStream2.default.obj();
|
||||
}
|
||||
var voiceStream = _through2.default.obj(function (chunk, encoding, callback) {
|
||||
if (chunk instanceof Buffer) {
|
||||
chunk = new Float32Array(chunk.buffer, chunk.byteOffset, chunk.byteLength / 4);
|
||||
}
|
||||
if (chunk instanceof Float32Array) {
|
||||
chunk = {
|
||||
target: target,
|
||||
pcm: chunk,
|
||||
numberOfChannels: numberOfChannels
|
||||
};
|
||||
} else {
|
||||
chunk = {
|
||||
target: target,
|
||||
pcm: chunk.pcm,
|
||||
numberOfChannels: numberOfChannels,
|
||||
position: { x: chunk.x, y: chunk.y, z: chunk.z }
|
||||
};
|
||||
}
|
||||
var samples = _this3._samplesPerPacket || chunk.pcm.length / numberOfChannels;
|
||||
chunk.bitrate = _this3.getActualBitrate(samples, chunk.position != null);
|
||||
callback(null, chunk);
|
||||
});
|
||||
var codec = 'Opus'; // TODO
|
||||
var seqNum = 0;
|
||||
voiceStream.pipe(this._codecs.createEncoderStream(codec)).on('data', function (data) {
|
||||
var duration = _this3._codecs.getDuration(codec, data.frame) / 10;
|
||||
_this3._voice.write({
|
||||
seqNum: seqNum,
|
||||
codec: codec,
|
||||
mode: target,
|
||||
frames: [data.frame],
|
||||
position: data.position,
|
||||
end: false
|
||||
});
|
||||
seqNum += duration;
|
||||
}).on('end', function () {
|
||||
_this3._voice.write({
|
||||
seqNum: seqNum,
|
||||
codec: codec,
|
||||
mode: target,
|
||||
frames: [],
|
||||
end: true
|
||||
});
|
||||
});
|
||||
return voiceStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called when new voice packets arrive.
|
||||
* Forwards the packet to the source user.
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: '_onVoice',
|
||||
value: function _onVoice(chunk) {
|
||||
var user = this._userById[chunk.source];
|
||||
user._onVoice(chunk.seqNum, chunk.codec, chunk.target, chunk.frames, chunk.position, chunk.end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called when new data packets arrive.
|
||||
* If there is a method named '_onPacketName', the data is forwarded to
|
||||
* that method, otherwise it is logged as unhandled.
|
||||
*
|
||||
* @param {object} chunk - The data packet
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: '_onData',
|
||||
value: function _onData(chunk) {
|
||||
if (this['_on' + chunk.name]) {
|
||||
this['_on' + chunk.name](chunk.payload);
|
||||
} else {
|
||||
console.log('Unhandled data packet:', chunk);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: '_onUDPTunnel',
|
||||
value: function _onUDPTunnel(payload) {
|
||||
// Forward tunneled udp packets to the voice pipeline
|
||||
this._voiceDecoder.write(payload);
|
||||
}
|
||||
}, {
|
||||
key: '_onVersion',
|
||||
value: function _onVersion(payload) {
|
||||
this.serverVersion = {
|
||||
major: payload.version >> 16,
|
||||
minor: payload.version >> 8 & 0xff,
|
||||
patch: payload.version >> 0 & 0xff,
|
||||
release: payload.release,
|
||||
os: payload.os,
|
||||
osVersion: payload.os_version
|
||||
};
|
||||
}
|
||||
}, {
|
||||
key: '_onServerSync',
|
||||
value: function _onServerSync(payload) {
|
||||
var _this4 = this;
|
||||
|
||||
// This packet finishes the initialization phase
|
||||
this.self = this._userById[payload.session];
|
||||
this.maxBandwidth = payload.max_bandwidth;
|
||||
this.welcomeMessage = payload.welcome_text;
|
||||
|
||||
// Make sure we send regular ping packets to not get disconnected
|
||||
this._pinger = setInterval(function () {
|
||||
if (_this4._inFlightDataPings >= _this4._maxInFlightDataPings) {
|
||||
_this4._error('timeout');
|
||||
return;
|
||||
}
|
||||
var dataStats = _this4._dataStats.getAll();
|
||||
var voiceStats = _this4._voiceStats.getAll();
|
||||
var timestamp = new Date().getTime();
|
||||
var payload = {
|
||||
timestamp: timestamp
|
||||
};
|
||||
if (dataStats) {
|
||||
payload.tcp_packets = dataStats.n;
|
||||
payload.tcp_ping_avg = dataStats.mean;
|
||||
payload.tcp_ping_var = dataStats.variance;
|
||||
}
|
||||
if (voiceStats) {
|
||||
payload.udp_packets = voiceStats.n;
|
||||
payload.udp_ping_avg = voiceStats.mean;
|
||||
payload.udp_ping_var = voiceStats.variance;
|
||||
}
|
||||
_this4._send({
|
||||
name: 'Ping',
|
||||
payload: payload
|
||||
});
|
||||
_this4._inFlightDataPings++;
|
||||
}, this._dataPingInterval);
|
||||
|
||||
// We are now connected
|
||||
this.emit('connected');
|
||||
}
|
||||
}, {
|
||||
key: '_onPing',
|
||||
value: function _onPing(payload) {
|
||||
if (this._inFlightDataPings <= 0) {
|
||||
console.warn('Got unexpected ping message:', payload);
|
||||
return;
|
||||
}
|
||||
this._inFlightDataPings--;
|
||||
|
||||
var now = new Date().getTime();
|
||||
var duration = now - payload.timestamp.toNumber();
|
||||
this._dataStats.update(duration);
|
||||
this.emit('dataPing', duration);
|
||||
}
|
||||
}, {
|
||||
key: '_onReject',
|
||||
value: function _onReject(payload) {
|
||||
// We got rejected from the server for some reason.
|
||||
this.emit('reject', payload);
|
||||
this.disconnect();
|
||||
}
|
||||
}, {
|
||||
key: '_onPermissionDenied',
|
||||
value: function _onPermissionDenied(payload) {
|
||||
if (payload.type === DenyType.Text) {
|
||||
this.emit('denied', 'Text', null, null, payload.reason);
|
||||
} else if (payload.type === DenyType.Permission) {
|
||||
var user = this._userById[payload.session];
|
||||
var channel = this._channelById[payload.channel_id];
|
||||
this.emit('denied', 'Permission', user, channel, payload.permission);
|
||||
} else if (payload.type === DenyType.SuperUser) {
|
||||
this.emit('denied', 'SuperUser', null, null, null);
|
||||
} else if (payload.type === DenyType.ChannelName) {
|
||||
this.emit('denied', 'ChannelName', null, null, payload.name);
|
||||
} else if (payload.type === DenyType.TextTooLong) {
|
||||
this.emit('denied', 'TextTooLong', null, null, null);
|
||||
} else if (payload.type === DenyType.TemporaryChannel) {
|
||||
this.emit('denied', 'TemporaryChannel', null, null, null);
|
||||
} else if (payload.type === DenyType.MissingCertificate) {
|
||||
var _user = this._userById[payload.session];
|
||||
this.emit('denied', 'MissingCertificate', _user, null, null);
|
||||
} else if (payload.type === DenyType.UserName) {
|
||||
this.emit('denied', 'UserName', null, null, payload.name);
|
||||
} else if (payload.type === DenyType.ChannelFull) {
|
||||
this.emit('denied', 'ChannelFull', null, null, null);
|
||||
} else if (payload.type === DenyType.NestingLimit) {
|
||||
this.emit('denied', 'NestingLimit', null, null, null);
|
||||
} else {
|
||||
throw Error('Invalid DenyType: ' + payload.type);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: '_onTextMessage',
|
||||
value: function _onTextMessage(payload) {
|
||||
var _this5 = this;
|
||||
|
||||
this.emit('message', this._userById[payload.actor], payload.message, payload.session.map(function (id) {
|
||||
return _this5._userById[id];
|
||||
}), payload.channel_id.map(function (id) {
|
||||
return _this5._channelById[id];
|
||||
}), payload.tree_id.map(function (id) {
|
||||
return _this5._channelById[id];
|
||||
}));
|
||||
}
|
||||
}, {
|
||||
key: '_onChannelState',
|
||||
value: function _onChannelState(payload) {
|
||||
var _this6 = this;
|
||||
|
||||
var channel = this._channelById[payload.channel_id];
|
||||
if (!channel) {
|
||||
channel = new _channel2.default(this, payload.channel_id);
|
||||
this._channelById[channel._id] = channel;
|
||||
this.channels.push(channel);
|
||||
this.emit('newChannel', channel);
|
||||
}
|
||||
(payload.links_remove || []).forEach(function (otherId) {
|
||||
var otherChannel = _this6._channelById[otherId];
|
||||
if (otherChannel && otherChannel.links.indexOf(channel) !== -1) {
|
||||
otherChannel._update({
|
||||
links_remove: [payload.channel_id]
|
||||
});
|
||||
}
|
||||
});
|
||||
channel._update(payload);
|
||||
}
|
||||
}, {
|
||||
key: '_onChannelRemove',
|
||||
value: function _onChannelRemove(payload) {
|
||||
var channel = this._channelById[payload.channel_id];
|
||||
if (channel) {
|
||||
channel._remove();
|
||||
delete this._channelById[channel._id];
|
||||
(0, _removeValue2.default)(this.channels, channel);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: '_onUserState',
|
||||
value: function _onUserState(payload) {
|
||||
var user = this._userById[payload.session];
|
||||
if (!user) {
|
||||
user = new _user3.default(this, payload.session);
|
||||
this._userById[user._id] = user;
|
||||
this.users.push(user);
|
||||
this.emit('newUser', user);
|
||||
|
||||
// For some reason, the mumble protocol does not send the initial
|
||||
// channel of a client if it is the root channel
|
||||
payload.channel_id = payload.channel_id || 0;
|
||||
}
|
||||
user._update(payload);
|
||||
}
|
||||
}, {
|
||||
key: '_onUserRemove',
|
||||
value: function _onUserRemove(payload) {
|
||||
var user = this._userById[payload.session];
|
||||
if (user) {
|
||||
user._remove(this._userById[payload.actor], payload.reason, payload.ban);
|
||||
delete this._userById[user._id];
|
||||
(0, _removeValue2.default)(this.users, user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the remote server.
|
||||
* Once disconnected, this client may not be used again.
|
||||
* Does nothing when not connected.
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'disconnect',
|
||||
value: function disconnect() {
|
||||
if (this._disconnected) {
|
||||
return;
|
||||
}
|
||||
this._disconnected = true;
|
||||
this._voice.end();
|
||||
this._data.end();
|
||||
clearInterval(this._pinger);
|
||||
|
||||
this.emit('disconnected');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set preferred audio bitrate and samples per packet.
|
||||
*
|
||||
* The {@link PCMData} passed to the stream returned by {@link createVoiceStream} must
|
||||
* contain the appropriate amount of samples per channel for bandwidth control to
|
||||
* function as expected.
|
||||
*
|
||||
* If this method is never called or false is passed as one of the values, then the
|
||||
* samplesPerPacket are determined by inspecting the {@link PCMData} passed and the
|
||||
* bitrate is calculated from the maximum bitrate advertised by the server.
|
||||
*
|
||||
* @param {number} bitrate - Preferred audio bitrate, sensible values are 8k to 96k
|
||||
* @param {number} samplesPerPacket - Amount of samples per packet, valid values depend on the codec used but all should support 10ms (i.e. 480), 20ms, 40ms and 60ms
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'setAudioQuality',
|
||||
value: function setAudioQuality(bitrate, samplesPerPacket) {
|
||||
this._preferredBitrate = bitrate;
|
||||
this._samplesPerPacket = samplesPerPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the actual bitrate taking into account maximum and preferred bitrate.
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'getActualBitrate',
|
||||
value: function getActualBitrate(samplesPerPacket, sendPosition) {
|
||||
var bitrate = this.getPreferredBitrate(samplesPerPacket, sendPosition);
|
||||
var bandwidth = MumbleClient.calcEnforcableBandwidth(bitrate, samplesPerPacket, sendPosition);
|
||||
if (bandwidth <= this.maxBandwidth) {
|
||||
return bitrate;
|
||||
} else {
|
||||
return this.getMaxBitrate(samplesPerPacket, sendPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the preferred bitrate set by {@link setAudioQuality} or
|
||||
* {@link getMaxBitrate} if not set.
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'getPreferredBitrate',
|
||||
value: function getPreferredBitrate(samplesPerPacket, sendPosition) {
|
||||
if (this._preferredBitrate) {
|
||||
return this._preferredBitrate;
|
||||
}
|
||||
return this.getMaxBitrate(samplesPerPacket, sendPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the maximum bitrate possible given the current server bandwidth limit.
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'getMaxBitrate',
|
||||
value: function getMaxBitrate(samplesPerPacket, sendPosition) {
|
||||
var overhead = MumbleClient.calcEnforcableBandwidth(0, samplesPerPacket, sendPosition);
|
||||
return this.maxBandwidth - overhead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the bandwidth used if IP/UDP packets were used to transmit audio.
|
||||
* This matches the value used by Mumble servers to enforce bandwidth limits.
|
||||
* @returns {number} bits per second
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'getChannel',
|
||||
|
||||
|
||||
/**
|
||||
* Find a channel by name.
|
||||
* If no such channel exists, return null.
|
||||
*
|
||||
* @param {string} name - The full name of the channel
|
||||
* @returns {?Channel}
|
||||
*/
|
||||
value: function getChannel(name) {
|
||||
var _iteratorNormalCompletion2 = true;
|
||||
var _didIteratorError2 = false;
|
||||
var _iteratorError2 = undefined;
|
||||
|
||||
try {
|
||||
for (var _iterator2 = this.channels[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
|
||||
var channel = _step2.value;
|
||||
|
||||
if (channel.name === name) {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
_didIteratorError2 = true;
|
||||
_iteratorError2 = err;
|
||||
} finally {
|
||||
try {
|
||||
if (!_iteratorNormalCompletion2 && _iterator2.return) {
|
||||
_iterator2.return();
|
||||
}
|
||||
} finally {
|
||||
if (_didIteratorError2) {
|
||||
throw _iteratorError2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}, {
|
||||
key: 'setSelfMute',
|
||||
value: function setSelfMute(mute) {
|
||||
var message = {
|
||||
name: 'UserState',
|
||||
payload: {
|
||||
session: this.self._id,
|
||||
self_mute: mute
|
||||
}
|
||||
};
|
||||
if (!mute) message.payload.self_deaf = false;
|
||||
this._send(message);
|
||||
}
|
||||
}, {
|
||||
key: 'setSelfDeaf',
|
||||
value: function setSelfDeaf(deaf) {
|
||||
var message = {
|
||||
name: 'UserState',
|
||||
payload: {
|
||||
session: this.self._id,
|
||||
self_deaf: deaf
|
||||
}
|
||||
};
|
||||
if (deaf) message.payload.self_mute = true;
|
||||
this._send(message);
|
||||
}
|
||||
}, {
|
||||
key: 'setSelfTexture',
|
||||
value: function setSelfTexture(texture) {
|
||||
this._send({
|
||||
name: 'UserState',
|
||||
payload: {
|
||||
session: this.self._id,
|
||||
texture: texture
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'setSelfComment',
|
||||
value: function setSelfComment(comment) {
|
||||
this._send({
|
||||
name: 'UserState',
|
||||
payload: {
|
||||
session: this.self._id,
|
||||
comment: comment
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'setPluginContext',
|
||||
value: function setPluginContext(context) {
|
||||
this._send({
|
||||
name: 'UserState',
|
||||
payload: {
|
||||
session: this.self._id,
|
||||
plugin_context: context
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'setPluginIdentity',
|
||||
value: function setPluginIdentity(identity) {
|
||||
this._send({
|
||||
name: 'UserState',
|
||||
payload: {
|
||||
session: this.self._id,
|
||||
plugin_identity: identity
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'setRecording',
|
||||
value: function setRecording(recording) {
|
||||
this._send({
|
||||
name: 'UserState',
|
||||
payload: {
|
||||
session: this.self._id,
|
||||
recording: recording
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'getChannelById',
|
||||
value: function getChannelById(id) {
|
||||
return this._channelById[id];
|
||||
}
|
||||
}, {
|
||||
key: 'getUserById',
|
||||
value: function getUserById(id) {
|
||||
return this._userById[id];
|
||||
}
|
||||
}, {
|
||||
key: 'root',
|
||||
get: function get() {
|
||||
return this._channelById[0];
|
||||
}
|
||||
}, {
|
||||
key: 'connected',
|
||||
get: function get() {
|
||||
return !this._disconnected && this._dataStream != null;
|
||||
}
|
||||
}, {
|
||||
key: 'dataStats',
|
||||
get: function get() {
|
||||
return this._dataStats.getAll();
|
||||
}
|
||||
}, {
|
||||
key: 'voiceStats',
|
||||
get: function get() {
|
||||
return this._voiceStats.getAll();
|
||||
}
|
||||
}], [{
|
||||
key: 'calcEnforcableBandwidth',
|
||||
value: function calcEnforcableBandwidth(bitrate, samplesPerPacket, sendPosition) {
|
||||
// IP + UDP + Crypt + Header + SeqNum (VarInt) + Codec Header + Optional Position
|
||||
// Codec Header depends on codec:
|
||||
// - Opus is always 4 (just the length as VarInt)
|
||||
// - CELT/Speex depends on frames (10ms) per packet (1 byte each)
|
||||
var codecHeaderBytes = Math.max(4, samplesPerPacket / 480);
|
||||
var packetBytes = 20 + 8 + 4 + 1 + 4 + codecHeaderBytes + (sendPosition ? 12 : 0);
|
||||
var packetsPerSecond = 48000 / samplesPerPacket;
|
||||
return Math.round(packetBytes * 8 * packetsPerSecond + bitrate);
|
||||
}
|
||||
}]);
|
||||
|
||||
return MumbleClient;
|
||||
}(_events.EventEmitter);
|
||||
|
||||
exports.default = MumbleClient;
|
450
node_modules/mumble-client/lib/user.js
generated
vendored
Normal file
450
node_modules/mumble-client/lib/user.js
generated
vendored
Normal file
@ -0,0 +1,450 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
|
||||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||||
|
||||
var _events = require('events');
|
||||
|
||||
var _dropStream = require('drop-stream');
|
||||
|
||||
var _dropStream2 = _interopRequireDefault(_dropStream);
|
||||
|
||||
var _removeValue = require('remove-value');
|
||||
|
||||
var _removeValue2 = _interopRequireDefault(_removeValue);
|
||||
|
||||
var _rtimer = require('rtimer');
|
||||
|
||||
var _rtimer2 = _interopRequireDefault(_rtimer);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
|
||||
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
|
||||
|
||||
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 User = function (_EventEmitter) {
|
||||
_inherits(User, _EventEmitter);
|
||||
|
||||
function User(client, id) {
|
||||
_classCallCheck(this, User);
|
||||
|
||||
var _this = _possibleConstructorReturn(this, (User.__proto__ || Object.getPrototypeOf(User)).call(this));
|
||||
|
||||
_this._client = client;
|
||||
_this._id = id;
|
||||
_this._haveRequestedTexture = false;
|
||||
_this._haveRequestedComment = false;
|
||||
return _this;
|
||||
}
|
||||
|
||||
_createClass(User, [{
|
||||
key: '_update',
|
||||
value: function _update(msg) {
|
||||
var changes = {};
|
||||
if (msg.name != null) {
|
||||
changes.username = this._username = msg.name;
|
||||
}
|
||||
if (msg.user_id != null) {
|
||||
changes.uniqueId = this._uniqueId = msg.user_id;
|
||||
}
|
||||
if (msg.mute != null) {
|
||||
changes.mute = this._mute = msg.mute;
|
||||
}
|
||||
if (msg.deaf != null) {
|
||||
changes.deaf = this._deaf = msg.deaf;
|
||||
}
|
||||
if (msg.suppress != null) {
|
||||
changes.suppress = this._suppress = msg.suppress;
|
||||
}
|
||||
if (msg.self_mute != null) {
|
||||
changes.selfMute = this._selfMute = msg.self_mute;
|
||||
}
|
||||
if (msg.self_deaf != null) {
|
||||
changes.selfDeaf = this._selfDeaf = msg.self_deaf;
|
||||
}
|
||||
if (msg.texture != null) {
|
||||
changes.texture = this._texture = msg.texture;
|
||||
}
|
||||
if (msg.texture_hash != null) {
|
||||
changes.textureHash = this._textureHash = msg.texture_hash;
|
||||
this._haveRequestedTexture = false; // invalidate previous request
|
||||
}
|
||||
if (msg.comment != null) {
|
||||
changes.comment = this._comment = msg.comment;
|
||||
}
|
||||
if (msg.comment_hash != null) {
|
||||
changes.commentHash = this._commentHash = msg.comment_hash;
|
||||
this._haveRequestedComment = false; // invalidate previous request
|
||||
}
|
||||
if (msg.priority_speaker != null) {
|
||||
changes.prioritySpeaker = this._prioritySpeaker = msg.priority_speaker;
|
||||
}
|
||||
if (msg.recording != null) {
|
||||
changes.recording = this._recording = msg.recording;
|
||||
}
|
||||
if (msg.hash != null) {
|
||||
changes.certHash = this._certHash = msg.hash;
|
||||
}
|
||||
if (msg.channel_id != null) {
|
||||
if (this.channel) {
|
||||
(0, _removeValue2.default)(this.channel.users, this);
|
||||
}
|
||||
this._channelId = msg.channel_id;
|
||||
if (this.channel) {
|
||||
this.channel.users.push(this);
|
||||
}
|
||||
changes.channel = this.channel;
|
||||
}
|
||||
this.emit('update', this._client._userById[msg.actor], changes);
|
||||
}
|
||||
}, {
|
||||
key: '_remove',
|
||||
value: function _remove(actor, reason, ban) {
|
||||
if (this.channel) {
|
||||
(0, _removeValue2.default)(this.channel.users, this);
|
||||
}
|
||||
this.emit('remove', actor, reason, ban);
|
||||
}
|
||||
}, {
|
||||
key: '_getOrCreateVoiceStream',
|
||||
value: function _getOrCreateVoiceStream() {
|
||||
var _this2 = this;
|
||||
|
||||
if (!this._voice) {
|
||||
// New transmission
|
||||
if (!this._client._codecs) {
|
||||
// No codecs available, cannot decode
|
||||
this._voice = _dropStream2.default.obj();
|
||||
} else {
|
||||
this._voice = this._client._codecs.createDecoderStream(this);
|
||||
}
|
||||
this._voice.once('close', function () {
|
||||
_this2._voice = null;
|
||||
});
|
||||
this._voiceTimeout = new _rtimer2.default(function () {
|
||||
_this2._voice.end();
|
||||
_this2._voice = null;
|
||||
}, this._client._options.userVoiceTimeout || 200).set();
|
||||
this.emit('voice', this._voice);
|
||||
}
|
||||
return this._voice;
|
||||
}
|
||||
}, {
|
||||
key: '_getDuration',
|
||||
value: function _getDuration(codec, frames) {
|
||||
var _this3 = this;
|
||||
|
||||
if (this._client._codecs) {
|
||||
var duration = 0;
|
||||
frames.forEach(function (frame) {
|
||||
duration += _this3._client._codecs.getDuration(codec, frame);
|
||||
});
|
||||
return duration;
|
||||
} else {
|
||||
return frames.length * 10;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method filters and inserts empty frames as needed to accout
|
||||
* for packet loss and then writes to the {@link #_voice} stream.
|
||||
* If this is a new transmission it emits the 'voice' event and if
|
||||
* the transmission has ended it closes the stream.
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: '_onVoice',
|
||||
value: function _onVoice(seqNum, codec, target, frames, position, end) {
|
||||
var _this4 = this;
|
||||
|
||||
if (frames.length > 0) {
|
||||
var duration = this._getDuration(codec, frames);
|
||||
if (this._voice != null) {
|
||||
// This is not the first packet in this transmission
|
||||
|
||||
// So drop it if it's late
|
||||
if (this._lastVoiceSeqId > seqNum) {
|
||||
return;
|
||||
}
|
||||
|
||||
// And make up for lost packets
|
||||
if (this._lastVoiceSeqId < seqNum - duration / 10) {
|
||||
var lost = seqNum - this._lastVoiceSeqId - 1;
|
||||
// Cap at 10 lost frames, the audio will sound broken at that point anyway
|
||||
if (lost > 10) {
|
||||
lost = 10;
|
||||
}
|
||||
for (var i = 0; i < lost; i++) {
|
||||
this._getOrCreateVoiceStream().write({
|
||||
target: target,
|
||||
codec: codec,
|
||||
frame: null,
|
||||
position: position
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
frames.forEach(function (frame) {
|
||||
_this4._getOrCreateVoiceStream().write({
|
||||
target: target,
|
||||
codec: codec,
|
||||
frame: frame,
|
||||
position: position
|
||||
});
|
||||
});
|
||||
this._voiceTimeout.set();
|
||||
this._lastVoiceSeqId = seqNum + duration / 10 - 1;
|
||||
}
|
||||
if (end && this._voice) {
|
||||
this._voiceTimeout.clear();
|
||||
this._voiceTimeout = null;
|
||||
this._voice.end();
|
||||
this._voice = null;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'setMute',
|
||||
value: function setMute(mute) {
|
||||
var message = {
|
||||
name: 'UserState',
|
||||
payload: {
|
||||
session: this._id,
|
||||
mute: mute
|
||||
}
|
||||
};
|
||||
if (!mute) message.payload.deaf = false;
|
||||
this._client._send(message);
|
||||
}
|
||||
}, {
|
||||
key: 'setDeaf',
|
||||
value: function setDeaf(deaf) {
|
||||
var message = {
|
||||
name: 'UserState',
|
||||
payload: {
|
||||
session: this._id,
|
||||
deaf: deaf
|
||||
}
|
||||
};
|
||||
if (deaf) message.payload.mute = true;
|
||||
this._client._send(message);
|
||||
}
|
||||
}, {
|
||||
key: 'clearComment',
|
||||
value: function clearComment() {
|
||||
this._client._send({
|
||||
name: 'UserState',
|
||||
payload: {
|
||||
session: this._id,
|
||||
comment: ''
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'clearTexture',
|
||||
value: function clearTexture() {
|
||||
this._client._send({
|
||||
name: 'UserState',
|
||||
payload: {
|
||||
session: this._id,
|
||||
texture: ''
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'requestComment',
|
||||
value: function requestComment() {
|
||||
if (this._haveRequestedComment) return;
|
||||
this._client._send({
|
||||
name: 'RequestBlob',
|
||||
payload: {
|
||||
session_comment: this._id
|
||||
}
|
||||
});
|
||||
this._haveRequestedComment = true;
|
||||
}
|
||||
}, {
|
||||
key: 'requestTexture',
|
||||
value: function requestTexture() {
|
||||
if (this._haveRequestedTexture) return;
|
||||
this._client._send({
|
||||
name: 'RequestBlob',
|
||||
payload: {
|
||||
session_texture: this._id
|
||||
}
|
||||
});
|
||||
this._haveRequestedTexture = true;
|
||||
}
|
||||
}, {
|
||||
key: 'register',
|
||||
value: function register() {
|
||||
this._client._send({
|
||||
name: 'UserState',
|
||||
payload: {
|
||||
session: this._id,
|
||||
user_id: 0
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'sendMessage',
|
||||
value: function sendMessage(message) {
|
||||
this._client._send({
|
||||
name: 'TextMessage',
|
||||
payload: {
|
||||
session: this._id,
|
||||
message: message
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'setChannel',
|
||||
value: function setChannel(channel) {
|
||||
this._client._send({
|
||||
name: 'UserState',
|
||||
payload: {
|
||||
session: this._id,
|
||||
channel_id: channel._id
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'id',
|
||||
get: function get() {
|
||||
return this._id;
|
||||
}
|
||||
}, {
|
||||
key: 'username',
|
||||
get: function get() {
|
||||
return this._username;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set username.');
|
||||
}
|
||||
}, {
|
||||
key: 'uniqueId',
|
||||
get: function get() {
|
||||
return this._uniqueId;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set uniqueId. Maybe try #register()?');
|
||||
}
|
||||
}, {
|
||||
key: 'mute',
|
||||
get: function get() {
|
||||
return this._mute;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set mute. Use #setMute(mute) instead.');
|
||||
}
|
||||
}, {
|
||||
key: 'deaf',
|
||||
get: function get() {
|
||||
return this._deaf;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set deaf. Use #setDeaf(deaf) instead.');
|
||||
}
|
||||
}, {
|
||||
key: 'selfMute',
|
||||
get: function get() {
|
||||
return this._selfMute;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set selfMute. Use Client#setSelfMute(mute) instead.');
|
||||
}
|
||||
}, {
|
||||
key: 'selfDeaf',
|
||||
get: function get() {
|
||||
return this._selfDeaf;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set selfDeaf. Use Client#setSelfDeaf(deaf) instead.');
|
||||
}
|
||||
}, {
|
||||
key: 'suppress',
|
||||
get: function get() {
|
||||
return this._suppress;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set suppress.');
|
||||
}
|
||||
}, {
|
||||
key: 'texture',
|
||||
get: function get() {
|
||||
return this._texture;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set texture. Use Client#setSelfTexture(texture) or #clearTexture() instead.');
|
||||
}
|
||||
}, {
|
||||
key: 'textureHash',
|
||||
get: function get() {
|
||||
return this._textureHash;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set textureHash.');
|
||||
}
|
||||
}, {
|
||||
key: 'comment',
|
||||
get: function get() {
|
||||
return this._comment;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set comment. Use Client#setSelfTexture(texture) or #clearComment() instead.');
|
||||
}
|
||||
}, {
|
||||
key: 'commentHash',
|
||||
get: function get() {
|
||||
return this._commentHash;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set commentHash.');
|
||||
}
|
||||
}, {
|
||||
key: 'prioritySpeaker',
|
||||
get: function get() {
|
||||
return this._prioritySpeaker;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set prioritySpeaker. Use #setPrioritySpeaker(prioSpeaker) instead.');
|
||||
}
|
||||
}, {
|
||||
key: 'recording',
|
||||
get: function get() {
|
||||
return this._recording;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set recording. Use Client#setSelfRecording(recording) instead.');
|
||||
}
|
||||
}, {
|
||||
key: 'certHash',
|
||||
get: function get() {
|
||||
return this._certHash;
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set certHash.');
|
||||
}
|
||||
}, {
|
||||
key: 'channel',
|
||||
get: function get() {
|
||||
if (this._channelId != null) {
|
||||
return this._client._channelById[this._channelId];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
set: function set(to) {
|
||||
throw new Error('Cannot set channel. Use #setChannel(channel) instead.');
|
||||
}
|
||||
}]);
|
||||
|
||||
return User;
|
||||
}(_events.EventEmitter);
|
||||
|
||||
exports.default = User;
|
22
node_modules/mumble-client/lib/utils.js
generated
vendored
Normal file
22
node_modules/mumble-client/lib/utils.js
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.getOSName = getOSName;
|
||||
exports.getOSVersion = getOSVersion;
|
||||
function getOSName() {
|
||||
if (process.browser) {
|
||||
return 'Browser';
|
||||
} else {
|
||||
return 'Node.js';
|
||||
}
|
||||
}
|
||||
|
||||
function getOSVersion() {
|
||||
if (process.browser) {
|
||||
return navigator.userAgent;
|
||||
} else {
|
||||
return process.version;
|
||||
}
|
||||
}
|
72
node_modules/mumble-client/package.json
generated
vendored
Normal file
72
node_modules/mumble-client/package.json
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
{
|
||||
"_from": "mumble-client@^1.1.1",
|
||||
"_id": "mumble-client@1.3.0",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-4z/Frp+XwTsE0u+7g6BUQbYumV17iEaMBCZ5Oo5lQ5Jjq3sBnZYRH9pXDX1bU4/3HFU99/AVGcScH2R67olPPQ==",
|
||||
"_location": "/mumble-client",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "mumble-client@^1.1.1",
|
||||
"name": "mumble-client",
|
||||
"escapedName": "mumble-client",
|
||||
"rawSpec": "^1.1.1",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^1.1.1"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/mumble-client/-/mumble-client-1.3.0.tgz",
|
||||
"_shasum": "f045ad302260ed15b5e22d9c7fbc4683d5b4d1dd",
|
||||
"_spec": "mumble-client@^1.1.1",
|
||||
"_where": "/home/sergiu/linx-audio-simulator",
|
||||
"author": {
|
||||
"name": "Jonas Herzig",
|
||||
"email": "me@johni0702.de"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/johni0702/mumble-client/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"drop-stream": "^0.1.1",
|
||||
"mumble-streams": "0.0.4",
|
||||
"promise": "^7.1.1",
|
||||
"reduplexer": "^1.1.0",
|
||||
"remove-value": "^1.0.0",
|
||||
"rtimer": "^0.1.0",
|
||||
"stats-incremental": "^1.2.1",
|
||||
"through2": "^2.0.2"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "Mumble protocol client library",
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.14.0",
|
||||
"babel-preset-es2015": "^6.14.0",
|
||||
"chai": "^3.5.0",
|
||||
"mocha": "^3.0.2",
|
||||
"mocha-standard": "^1.0.0",
|
||||
"standard": "^8.0.0"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"lib"
|
||||
],
|
||||
"homepage": "https://github.com/johni0702/mumble-client#readme",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"name": "mumble-client",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/johni0702/mumble-client.git"
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "babel -d lib/ src/",
|
||||
"mocha": "mocha --compilers js:babel-core/register",
|
||||
"prepublish": "npm run compile",
|
||||
"test": "npm run compile && npm run mocha"
|
||||
},
|
||||
"version": "1.3.0"
|
||||
}
|
Reference in New Issue
Block a user