Files
discord.js/src/client/voice/VoiceConnection.js
Schuyler Cebulskie 68acf37fd4 Add stricter/better ESLint config (#589)
* Add stricter/better ESLint config

* Remove more unnecessary @returns
2016-09-03 20:45:23 +01:00

263 lines
7.5 KiB
JavaScript

const VoiceConnectionWebSocket = require('./VoiceConnectionWebSocket');
const VoiceConnectionUDPClient = require('./VoiceConnectionUDPClient');
const VoiceReceiver = require('./receiver/VoiceReceiver');
const Constants = require('../../util/Constants');
const EventEmitter = require('events').EventEmitter;
const DefaultPlayer = require('./player/DefaultPlayer');
/**
* Represents a connection to a Voice Channel in Discord
* @extends {EventEmitter}
*/
class VoiceConnection extends EventEmitter {
constructor(manager, channel, token, sessionID, endpoint, resolve, reject) {
super();
/**
* The voice manager of this connection
* @type {ClientVoiceManager}
* @private
*/
this.manager = manager;
/**
* The player
* @type {BasePlayer}
*/
this.player = new DefaultPlayer(this);
/**
* The endpoint of the connection
* @type {string}
*/
this.endpoint = endpoint;
/**
* The VoiceChannel for this connection
* @type {VoiceChannel}
*/
this.channel = channel;
/**
* The WebSocket connection for this voice connection
* @type {VoiceConnectionWebSocket}
* @private
*/
this.websocket = new VoiceConnectionWebSocket(this, channel.guild.id, token, sessionID, endpoint);
/**
* Whether or not the connection is ready
* @type {boolean}
*/
this.ready = false;
/**
* The resolve function for the promise associated with creating this connection
* @type {function}
* @private
*/
this._resolve = resolve;
/**
* The reject function for the promise associated with creating this connection
* @type {function}
* @private
*/
this._reject = reject;
this.ssrcMap = new Map();
this.queue = [];
this.receivers = [];
this.bindListeners();
}
/**
* Executed whenever an error occurs with the UDP/WebSocket sub-client
* @private
* @param {Error} err The error that occurred
*/
_onError(err) {
this._reject(err);
/**
* Emitted whenever the connection encounters a fatal error.
* @event VoiceConnection#error
* @param {Error} error the encountered error
*/
this.emit('error', err);
this._shutdown(err);
}
/**
* Disconnects the Client from the Voice Channel
* @param {string} [reason='user requested'] the reason of the disconnection
*/
disconnect(reason = 'user requested') {
this.manager.client.ws.send({
op: Constants.OPCodes.VOICE_STATE_UPDATE,
d: {
guild_id: this.channel.guild.id,
channel_id: null,
self_mute: false,
self_deaf: false,
},
});
this._shutdown(reason);
}
_onClose(e) {
e = e && e.code === 1000 ? null : e;
return this._shutdown(e);
}
_shutdown(e) {
if (!this.ready) {
return;
}
this.ready = false;
this.websocket._shutdown();
this.player._shutdown();
if (this.udp) {
this.udp._shutdown();
}
/**
* Emit once the voice connection has disconnected.
* @event VoiceConnection#disconnected
* @param {Error} error the error, if any
*/
this.emit('disconnected', e);
}
/**
* Binds listeners to the WebSocket and UDP sub-clients
* @private
*/
bindListeners() {
this.websocket.on('error', err => this._onError(err));
this.websocket.on('close', err => this._onClose(err));
this.websocket.on('ready-for-udp', data => {
this.udp = new VoiceConnectionUDPClient(this, data);
this.data = data;
this.udp.on('error', err => this._onError(err));
this.udp.on('close', err => this._onClose(err));
});
this.websocket.on('ready', secretKey => {
this.data.secret = secretKey;
this.ready = true;
/**
* Emitted once the connection is ready (joining voice channels resolves when the connection is ready anyway)
* @event VoiceConnection#ready
*/
this._resolve(this);
this.emit('ready');
});
this.once('ready', () => {
setImmediate(() => {
for (const item of this.queue) {
this.emit(...item);
}
this.queue = [];
});
});
this.manager.client.on(Constants.Events.VOICE_STATE_UPDATE, (oldM, newM) => {
if (oldM.voiceChannel && oldM.voiceChannel.guild.id === this.channel.guild.id && !newM.voiceChannel) {
const user = newM.user;
for (const receiver of this.receivers) {
const opusStream = receiver.opusStreams.get(user.id);
const pcmStream = receiver.pcmStreams.get(user.id);
if (opusStream) {
opusStream.push(null);
opusStream.open = false;
receiver.opusStreams.delete(user.id);
}
if (pcmStream) {
pcmStream.push(null);
pcmStream.open = false;
receiver.pcmStreams.delete(user.id);
}
}
}
});
this.websocket.on('speaking', data => {
const guild = this.channel.guild;
const user = this.manager.client.users.get(data.user_id);
this.ssrcMap.set(+data.ssrc, user);
if (!data.speaking) {
for (const receiver of this.receivers) {
const opusStream = receiver.opusStreams.get(user.id);
const pcmStream = receiver.pcmStreams.get(user.id);
if (opusStream) {
opusStream.push(null);
opusStream.open = false;
receiver.opusStreams.delete(user.id);
}
if (pcmStream) {
pcmStream.push(null);
pcmStream.open = false;
receiver.pcmStreams.delete(user.id);
}
}
}
/**
* Emitted whenever a user starts/stops speaking
* @event VoiceConnection#speaking
* @param {User} user the user that has started/stopped speaking
* @param {boolean} speaking whether or not the user is speaking
*/
if (this.ready) {
this.emit('speaking', user, data.speaking);
} else {
this.queue.push(['speaking', user, data.speaking]);
}
guild._memberSpeakUpdate(data.user_id, data.speaking);
});
}
/**
* Play the given file in the voice connection
* @param {string} file the path to the file
* @returns {StreamDispatcher}
* @example
* // play files natively
* voiceChannel.join()
* .then(connection => {
* const dispatcher = connection.playFile('C:/Users/Discord/Desktop/music.mp3');
* })
* .catch(console.log);
*/
playFile(file) {
return this.player.playFile(file);
}
/**
* Play the given stream in the voice connection
* @param {ReadableStream} stream the audio stream to play
* @returns {StreamDispatcher}
* @example
* // play streams using ytdl-core
* const ytdl = require('ytdl-core');
* voiceChannel.join()
* .then(connection => {
* const stream = ytdl('https://www.youtube.com/watch?v=XAWgeLF9EVQ', {filter : 'audioonly'});
* const dispatcher = connection.playStream(stream);
* })
* .catch(console.log);
*/
playStream(stream) {
return this.player.playStream(stream);
}
/**
* Plays a stream of PCM data
* @param {ReadableStream} pcmStream the PCM stream
* @returns {StreamDispatcher}
*/
playPCMStream(pcmStream) {
this._shutdown();
const dispatcher = this.player.playPCMStream(pcmStream);
return dispatcher;
}
/**
* Creates a VoiceReceiver so you can start listening to voice data. It's recommended to only create one of these.
* @returns {VoiceReceiver}
*/
createReceiver() {
const rcv = new VoiceReceiver(this);
this.receivers.push(rcv);
return rcv;
}
}
module.exports = VoiceConnection;