const { EventEmitter } = require('events')
const Queue = require('./structures/Queue')
/**
* Represents a guild Player instance
* @extends EventEmitter
*/
class GorilinkPlayer extends EventEmitter {
/**
* The constructor of GorilinkPlayer
* @param {GorilinkNode} node Your node instance
* @param {Object} options Player options
* @param {GorilinkManager} manager Your GorilinkManager instance
*/
constructor(node, options, manager) {
super()
/**
* {@link GorilinkManager} instance
*/
this.manager = manager
/**
* {@link GorilinkNode} instance
*/
this.node = node
/**
* Player guild id
* @type {String | Guild}
*/
this.guild = options.guild.id || options.guild
/**
* Player voiceChannel id
* @type {String | VoiceChannel}
*/
this.voiceChannel = options.voiceChannel.id || options.voiceChannel
/**
* Player textChannel
* @type {?TextChannel}
*/
this.textChannel = options.textChannel || null
/**
* Player states
* @type {Object}
*/
this.state = { volume: 100, equalizer: [] }
/**
* Player playing status
* @type {Boolean}
*/
this.playing = false
/**
* Created on method play is called
* @type {Number}
*/
this.timestamp = null
/**
* Player paused status
* @type {Boolean}
*/
this.paused = false
/**
* Player current track
* @type {Object}
*/
this.track = {}
/**
* Player voice state
* @type {Object}
*/
this.voiceUpdateState = null
/**
* Player looped status
* @type {Number}
*/
this.looped = 0
/**
* Player current track position
* @type {Number}
*/
this.position = 0
/**
* Create a Queue instance
* @type {Queue}
*/
this.queue = new Queue()
this.on('event', data => {
(this.getEvent(data).bind(this))()
}).on('playerUpdate', packet => {
this.state = { volume: this.state.volume, equalizer: this.state.equalizer, ...packet.state }
})
}
/**
* Plays a specific song based on Lavalink base64 string
* @param {String} track Track base64 will be played
* @param {Object} options Play options
*/
play(track, options = {}) {
const sound = this.queue.empty ? track : this.queue.first()
const packet = this.send('play', { ...options, track: sound.track })
this.playing = true
this.track = sound
this.timestamp = Date.now()
return packet
}
/**
* Send stop operation to Lavalink Node
*/
stop() {
const packet = this.send('stop')
this.playing = false
this.timestamp = null
return packet
}
/**
* Send pause operation to Lavalink Node
* @param {Boolean} pause Pause state
*/
pause(pause) {
const packet = this.send('pause', { pause })
this.paused = pause
return packet
}
/**
* Send volume operation to Lavalink Node
* @param {Number} vol Volume to be set
*/
volume(vol) {
const packet = this.send('volume', { volume: vol })
this.state.volume = vol
return packet
}
/**
* Send seek operation to Lavalink Node
* @param {Number} pos Position to be set
*/
seek(pos) {
return this.send('seek', { position: pos })
}
/**
* Set a loop to player
* * `0` off
* * `1` loop single
* * `2` loop all
* @param {Number} op Number of operation
*/
loop(op) {
if (op >= 2 && op <= 0 && !isNaN(op)) throw Error('Invalid op.')
return this.looped = op
}
/**
* Send equalizer operation to Lavalink Node
* @param {Array} bands Equalizer bands
*/
setEQ(bands) {
const packet = this.send('equalizer', { bands })
this.state.equalizer = bands
return packet
}
/**
* Connects in voiceChannel
* @param {Object} data Discord packet
*/
connect(data) {
this.voiceUpdateState = data
return this.send('voiceUpdate', data)
}
/**
* Destroys the guild player
*/
destroy() {
return this.manager.leave(this.guild.id || this.guild)
}
/**
* Handle events cames from Lavalink WebSocket connection
* @param {Object} data Lavalink packet
*/
getEvent(data) {
const events = {
'TrackStartEvent': function () {
/**
* Emitted when the player track starts
* @event GorilinkManager#trackStart
* @property {GorilinkPlayer} player - Player started to play
* @property {Object} track - Track data
*/
this.manager.emit('trackStart', this, this.track)
},
'TrackEndEvent': function () {
/**
* Emitted when the player track ends
* @event GorilinkManager#trackEnd
* @property {GorilinkManager} player - Player ended the track
* @property {Object} track - Track data
*/
if (this.track && this.looped == 1) {
this.manager.emit('trackEnd', this, this.track)
return this.play()
} else if (this.track && this.looped == 2) {
this.manager.emit('trackEnd', this, this.track)
this.queue.add(this.queue.shift())
return this.play()
} else if (this.queue.length <= 1) {
this.queue.shift()
this.playing = false
if (['REPLACED', 'FINISHED', 'STOPPED'].includes(data.reason)) {
/**
* Emitted when the player queue ends
* @event GorilinkManager#queueEnd
* @property {GorilinkManager} player - Player where the queue ended
*/
this.manager.emit('queueEnd', this)
}
} else if (this.queue.length > 0) {
this.queue.shift()
this.manager.emit('trackEnd', this, this.track)
return this.play()
}
},
'TrackStuckEvent': function () {
this.queue.shift()
/**
* Emitted when events TrackStuckEvent comes from Lavalink node
* @event GorilinkManager#trackStuck
* @property {GorilinkPlayer} player - Player who emitted the event
* @property {Object} track - Track data
* @property {Object} data - Lavalink node packet data
*/
this.manager.emit('trackStuck', this, this.track, data)
},
'TrackExceptionEvent': function () {
this.queue.shift()
/**
* Emitted when events on a track received an error
* @event GorilinkManager#trackError
* @property {GorilinkPlayer} player - Player who error as ocurred
* @property {Object} track - Track data
* @property {Object} data - Lavalink node packet data
*/
this.manager.emit('trackError', this, this.track, data)
},
'WebSocketClosedEvent': function () {
if ([4015, 4009].includes(data.code)) {
this.manager.sendWS({
op: 4,
d: {
guild_id: data.guildId,
channel_id: this.voiceChannel.id || this.voiceChannel,
self_mute: this.options.selfMute || false,
self_deaf: this.options.selfDeaf || false,
},
})
}
/**
* Emitted when events TrackStuckEvent comes from Lavalink node
* @event GorilinkManager#socketClosed
* @property {GorilinkPlayer} player - Player in which the connection to the Discord was closed
* @property {Object} data - Lavalink node packet data
*/
this.manager.emit('socketClosed', this, data)
},
'default': function () { throw new Error(`Unknown event '${data}'.`) }
}
return events[data.type] || events['default']
}
/**
* Send packets to Lavalink Node
* You should only use operations if your lavalink supports filters
* @param {String} op Operation string
* @param {Object} data Packet data
* @param {Object} [operations] Filtering operations
*/
send(op, data, operations = {}) {
if (!this.node.connected) throw new Error('No avaliable websocket connection for this node.')
return this.node.send({ ...data, op, guildId: this.guild, operations })
}
}
module.exports = GorilinkPlayer