UNPKG

@jadestudios/discord-music-player

Version:

Complete framework to facilitate music commands using discord.js v13

216 lines (215 loc) 7.23 kB
"use strict"; /** * Main of the code comes from the @discordjs/voice repo: * @link https://github.com/discordjs/voice/blob/main/examples/music-bot/src/music/subscription.ts */ Object.defineProperty(exports, "__esModule", { value: true }); exports.StreamConnection = void 0; const events_1 = require("events"); const voice_1 = require("@discordjs/voice"); const util_1 = require("util"); const __1 = require(".."); const wait = (0, util_1.promisify)(setTimeout); class StreamConnection extends events_1.EventEmitter { /** * StreamConnection constructor * @param {VoiceConnection} connection * @param {VoiceChannel|StageChannel} channel */ constructor(connection, channel) { super(); this.paused = false; this.readyLock = false; /** * The VoiceConnection * @type {VoiceConnection} */ this.connection = connection; /** * The AudioPlayer * @type {AudioPlayer} */ this.player = (0, voice_1.createAudioPlayer)(); /** * The VoiceChannel or StageChannel * @type {VoiceChannel | StageChannel} */ this.channel = channel; this.connection.on('stateChange', async (_oldState, newState) => { if (newState.status === voice_1.VoiceConnectionStatus.Disconnected) { if (this.connection) { if (newState.reason === voice_1.VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) { try { // Attempting to re-join the voice channel, after possibly changing channels await (0, voice_1.entersState)(this.connection, voice_1.VoiceConnectionStatus.Connecting, 5000); } catch { // It was manually disconnected and the connection is closed in Player.js _voiceUpdate } } else if (this.connection.rejoinAttempts < 5) { await wait((this.connection.rejoinAttempts + 1) * 5000); this.connection.rejoin(); } else { this.leave(); } } } else if (newState.status === voice_1.VoiceConnectionStatus.Destroyed) { this.stop(); this.connection = undefined; //Local reference to connection should be undefined if connection is already destroyed } else if (!this.readyLock && (newState.status === voice_1.VoiceConnectionStatus.Connecting || newState.status === voice_1.VoiceConnectionStatus.Signalling)) { if (this.connection) { this.readyLock = true; try { await this._enterState(); } catch { if (this.connection.state.status !== voice_1.VoiceConnectionStatus.Destroyed) this.leave(); } finally { this.readyLock = false; } } } }); this.player.on('stateChange', (oldState, newState) => { if (newState.status === voice_1.AudioPlayerStatus.Idle && oldState.status !== voice_1.AudioPlayerStatus.Idle) { if (!this.paused) { //@ts-ignore :: Emit exists on EventEmitter, but TS is throwing a fit for type checking this.emit('end', this.resource); delete this.resource; return; } } else if (newState.status === voice_1.AudioPlayerStatus.Playing) { if (!this.paused) { //@ts-ignore this.emit('start', this.resource); return; } } }).on('error', data => { //@ts-ignore this.emit('error', data); }); this.connection.subscribe(this.player); } /** * * @param {Readable | string} stream * @param {{ inputType: StreamType, metadata: any|undefined }} options * @returns {AudioResource<Song>} */ createAudioStream(stream, options) { this.resource = (0, voice_1.createAudioResource)(stream, { inputType: options.inputType, inlineVolume: true, metadata: options.metadata }); return this.resource; } /** * @returns {void} * @private */ async _enterState() { if (this.connection) await (0, voice_1.entersState)(this.connection, voice_1.VoiceConnectionStatus.Ready, 20000); } /** * * @param {AudioResource<Song>} resource * @returns {Promise<StreamConnection>} */ async playAudioStream(resource) { if (!this.connection) throw new __1.DMPError(__1.DMPErrors.NO_VOICE_CONNECTION); if (!resource) throw new __1.DMPError(__1.DMPErrors.RESOURCE_NOT_READY); if (!this.resource) this.resource = resource; if (this.connection.state.status !== voice_1.VoiceConnectionStatus.Ready) await this._enterState(); this.player.play(resource); return this; } /** * Pauses/Resumes the connection * @param {boolean} state * @returns {boolean} */ setPauseState(state) { if (state) { this.player.pause(true); this.paused = true; return true; } else { this.player.unpause(); this.paused = false; return false; } } /** * Stops and ends the connection * @returns {boolean} */ stop() { return this.player.stop(); } /** * Disconnect and leave from the voice channel * @returns {void} */ leave() { this.player.stop(true); if (this.connection) this.connection.destroy(); } /** * Gets the current volume * @type {number} */ get volume() { if (!this.resource?.volume) return 100; const currentVol = this.resource.volume.volume; return Math.round(Math.pow(currentVol, 1 / 1.661) * 200); } /** * Gets the stream time * @type {number} */ get time() { if (!this.resource) return 0; return this.resource.playbackDuration; } /** * Sets the current volume * @param {number} volume * @returns {boolean} */ setVolume(volume) { if (!this.resource || this._invalidVolume(volume)) return false; this.resource.volume?.setVolumeLogarithmic(volume / 200); return true; } /** * * @param {number} volume * @returns {boolean} * @private */ _invalidVolume(volume) { return (isNaN(volume) || volume >= Infinity || volume < 0); } } exports.StreamConnection = StreamConnection;