larakazagumo
Version:
A shoukaku wrapper with built-in queue support.
388 lines (387 loc) • 16.7 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.KazagumoPlayer = void 0;
const KazagumoQueue_1 = require("./Supports/KazagumoQueue");
const Interfaces_1 = require("../Modules/Interfaces");
const KazagumoTrack_1 = require("./Supports/KazagumoTrack");
class KazagumoPlayer {
/**
* Initialize the player
* @param kazagumo Kazagumo instance
* @param player Shoukaku's Player instance
* @param options Kazagumo options
* @param customData private readonly customData
*/
constructor(kazagumo, player, options, customData) {
var _a, _b;
this.customData = customData;
/**
* Get the current state of the player
*/
this.state = Interfaces_1.PlayerState.CONNECTING;
/**
* Paused state of the player
*/
this.paused = false;
/**
* Whether the player is playing or not
*/
this.playing = false;
/**
* Loop status
*/
this.loop = 'none';
/**
* Player's volume in percentage (default 100%)
*/
this.volume = 100;
/**
* Player's custom data
*/
this.data = new Map();
this.options = options;
this.kazagumo = kazagumo;
this.shoukaku = player;
this.guildId = options.guildId;
this.voiceId = options.voiceId;
this.textId = options.textId;
this.queue = new ((_b = (_a = this.options.extends) === null || _a === void 0 ? void 0 : _a.queue) !== null && _b !== void 0 ? _b : KazagumoQueue_1.KazagumoQueue)(this);
if (options.volume !== 100)
this.setVolume(options.volume);
this.search = (typeof this.options.searchWithSameNode === 'boolean' ? this.options.searchWithSameNode : true)
? (query, opt) => kazagumo.search.bind(kazagumo)(query, opt ? Object.assign(Object.assign({}, opt), { nodeName: this.shoukaku.node.name }) : undefined)
: kazagumo.search.bind(kazagumo);
this.shoukaku.on('start', () => {
this.playing = true;
this.emit(Interfaces_1.Events.PlayerStart, this, this.queue.current);
});
this.shoukaku.on('end', (data) => {
var _a, _b, _c;
// This event emits STOPPED reason when destroying, so return to prevent double emit
if (this.state === Interfaces_1.PlayerState.DESTROYING || this.state === Interfaces_1.PlayerState.DESTROYED)
return this.emit(Interfaces_1.Events.Debug, `Player ${this.guildId} destroyed from end event`);
if (data.reason === 'replaced')
return this.emit(Interfaces_1.Events.PlayerEnd, this);
if (['loadFailed', 'cleanup'].includes(data.reason)) {
if (this.queue.current &&
!((_a = this.queue.previous) !== null && _a !== void 0 ? _a : []).find((x) => { var _a, _b; return x.identifier === ((_a = this.queue.current) === null || _a === void 0 ? void 0 : _a.identifier) && x.title === ((_b = this.queue.current) === null || _b === void 0 ? void 0 : _b.title); }))
this.queue.previous = (_b = [this.queue.current].concat(this.queue.previous)) !== null && _b !== void 0 ? _b : [];
this.emit(Interfaces_1.Events.PlayerEnd, this, this.queue.current);
this.queue.current = null;
this.playing = false;
if (!this.queue.length)
return this.emit(Interfaces_1.Events.PlayerEmpty, this);
return this.play();
}
if (this.loop === 'track' && this.queue.current)
this.queue.unshift(this.queue.current);
if (this.loop === 'queue' && this.queue.current)
this.queue.push(this.queue.current);
if (this.queue.current &&
!this.queue.previous.find((x) => { var _a, _b; return x.identifier === ((_a = this.queue.current) === null || _a === void 0 ? void 0 : _a.identifier) && x.title === ((_b = this.queue.current) === null || _b === void 0 ? void 0 : _b.title); }))
this.queue.previous = (_c = [this.queue.current].concat(this.queue.previous)) !== null && _c !== void 0 ? _c : [];
const currentSong = this.queue.current;
this.emit(Interfaces_1.Events.PlayerEnd, this, currentSong);
this.queue.current = null;
if (!this.queue.length) {
this.playing = false;
return this.emit(Interfaces_1.Events.PlayerEmpty, this);
}
return this.play();
});
this.shoukaku.on('closed', (data) => {
this.playing = false;
this.emit(Interfaces_1.Events.PlayerClosed, this, data);
});
this.shoukaku.on('exception', (data) => {
this.playing = false;
this.emit(Interfaces_1.Events.PlayerException, this, data);
});
this.shoukaku.on('update', (data) => this.emit(Interfaces_1.Events.PlayerUpdate, this, data));
this.shoukaku.on('stuck', (data) => this.emit(Interfaces_1.Events.PlayerStuck, this, data));
this.shoukaku.on('resumed', () => this.emit(Interfaces_1.Events.PlayerResumed, this));
// @ts-ignore
this.shoukaku.on(Interfaces_1.Events.QueueUpdate, (referencePlayer, queue) => this.kazagumo.emit(Interfaces_1.Events.QueueUpdate, referencePlayer, queue));
}
// /**
// * Get volume
// */
// public get volume(): number {
// return this.shoukaku.filters.volume || 1;
// }
/**
* Get player position
*/
get position() {
return this.shoukaku.position;
}
/**
* Get filters
*/
get filters() {
return this.shoukaku.filters;
}
get node() {
return this.shoukaku.node;
}
/**
* Pause the player
* @param pause Whether to pause or not
* @returns KazagumoPlayer
*/
pause(pause) {
if (typeof pause !== 'boolean')
throw new Interfaces_1.KazagumoError(1, 'pause must be a boolean');
if (this.paused === pause || !this.queue.totalSize)
return this;
this.paused = pause;
this.playing = !pause;
this.shoukaku.setPaused(pause);
return this;
}
/**
* Set text channel
* @param textId Text channel ID
* @returns KazagumoPlayer
*/
setTextChannel(textId) {
if (this.state === Interfaces_1.PlayerState.DESTROYED)
throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed');
this.textId = textId;
return this;
}
/**
* Set voice channel and move the player to the voice channel
* @param voiceId Voice channel ID
* @returns KazagumoPlayer
*/
setVoiceChannel(voiceId) {
if (this.state === Interfaces_1.PlayerState.DESTROYED)
throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed');
this.state = Interfaces_1.PlayerState.CONNECTING;
this.voiceId = voiceId;
this.kazagumo.KazagumoOptions.send(this.guildId, {
op: 4,
d: {
guild_id: this.guildId,
channel_id: this.voiceId,
self_mute: false,
self_deaf: this.options.deaf,
},
});
this.emit(Interfaces_1.Events.Debug, `Player ${this.guildId} moved to voice channel ${voiceId}`);
return this;
}
/**
* Get the previous track from the queue
* @param remove Whether to remove the track from the previous list or not. Best to set to true if you want to play it
*/
getPrevious(remove = false) {
if (remove)
return this.queue.previous.shift();
return this.queue.previous[0];
}
/**
* Set loop mode
* @param [loop] Loop mode
* @returns KazagumoPlayer
*/
setLoop(loop) {
if (loop === undefined) {
if (this.loop === 'none')
this.loop = 'queue';
else if (this.loop === 'queue')
this.loop = 'track';
else if (this.loop === 'track')
this.loop = 'none';
return this;
}
if (loop === 'none' || loop === 'queue' || loop === 'track') {
this.loop = loop;
return this;
}
throw new Interfaces_1.KazagumoError(1, "loop must be one of 'none', 'queue', 'track'");
}
/**
* Play a track
* @param track Track to play
* @param options Play options
* @returns KazagumoPlayer
*/
play(track, options) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
if (this.state === Interfaces_1.PlayerState.DESTROYED)
throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed');
if (track && !(track instanceof KazagumoTrack_1.KazagumoTrack))
throw new Interfaces_1.KazagumoError(1, 'track must be a KazagumoTrack');
if (!track && !this.queue.totalSize)
throw new Interfaces_1.KazagumoError(1, 'No track is available to play');
if (!options || typeof options.replaceCurrent !== 'boolean')
options = Object.assign(Object.assign({}, options), { replaceCurrent: false });
if (track) {
if (!options.replaceCurrent && this.queue.current)
this.queue.unshift(this.queue.current);
this.queue.current = track;
}
else if (!this.queue.current)
this.queue.current = this.queue.shift();
if (!this.queue.current)
throw new Interfaces_1.KazagumoError(1, 'No track is available to play');
const current = this.queue.current;
current.setKazagumo(this.kazagumo);
let errorMessage;
const resolveResult = yield current.resolve({ player: this }).catch((e) => {
errorMessage = e.message;
return null;
});
if (!resolveResult) {
this.emit(Interfaces_1.Events.PlayerResolveError, this, current, errorMessage);
this.emit(Interfaces_1.Events.Debug, `Player ${this.guildId} resolve error: ${errorMessage}`);
this.queue.current = null;
this.queue.size ? yield this.play() : this.emit(Interfaces_1.Events.PlayerEmpty, this);
return this;
}
let playOptions = { track: { encoded: current.track, userData: (_a = current.requester) !== null && _a !== void 0 ? _a : {} } };
if (options)
playOptions = Object.assign(Object.assign({}, playOptions), options);
yield this.shoukaku.playTrack(playOptions);
return this;
});
}
/**
* Skip the current track
* @returns KazagumoPlayer
*/
skip() {
if (this.state === Interfaces_1.PlayerState.DESTROYED)
throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed');
this.shoukaku.stopTrack();
return this;
}
/**
* Seek to a position
* @param position Position in seconds
* @returns KazagumoPlayer
*/
seek(position) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
if (this.state === Interfaces_1.PlayerState.DESTROYED)
throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed');
if (!this.queue.current)
throw new Interfaces_1.KazagumoError(1, "Player has no current track in it's queue");
if (!this.queue.current.isSeekable)
throw new Interfaces_1.KazagumoError(1, "The current track isn't seekable");
position = Number(position);
if (isNaN(position))
throw new Interfaces_1.KazagumoError(1, 'position must be a number');
if (position < 0 || position > ((_a = this.queue.current.length) !== null && _a !== void 0 ? _a : 0))
position = Math.max(Math.min(position, (_b = this.queue.current.length) !== null && _b !== void 0 ? _b : 0), 0);
this.queue.current.position = position;
yield this.shoukaku.seekTo(position);
return this;
});
}
/**
* Set the volume in percentage (default 100%)
* @param volume Volume
* @returns KazagumoPlayer
*/
setVolume(volume) {
return __awaiter(this, void 0, void 0, function* () {
if (this.state === Interfaces_1.PlayerState.DESTROYED)
throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed');
if (isNaN(volume))
throw new Interfaces_1.KazagumoError(1, 'volume must be a number');
yield this.node.rest.updatePlayer({
guildId: this.guildId,
playerOptions: {
volume,
},
});
this.volume = volume;
return this;
});
}
/**
* Connect to the voice channel
* @returns KazagumoPlayer
*/
connect() {
if (this.state === Interfaces_1.PlayerState.DESTROYED)
throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed');
if (this.state === Interfaces_1.PlayerState.CONNECTED || !!this.voiceId)
throw new Interfaces_1.KazagumoError(1, 'Player is already connected');
this.state = Interfaces_1.PlayerState.CONNECTING;
this.kazagumo.KazagumoOptions.send(this.guildId, {
op: 4,
d: {
guild_id: this.guildId,
channel_id: this.voiceId,
self_mute: false,
self_deaf: this.options.deaf,
},
});
this.state = Interfaces_1.PlayerState.CONNECTED;
this.emit(Interfaces_1.Events.Debug, `Player ${this.guildId} connected`);
return this;
}
/**
* Disconnect from the voice channel
* @returns KazagumoPlayer
*/
disconnect() {
if (this.state === Interfaces_1.PlayerState.DISCONNECTED || !this.voiceId)
throw new Interfaces_1.KazagumoError(1, 'Player is already disconnected');
this.state = Interfaces_1.PlayerState.DISCONNECTING;
this.pause(true);
this.kazagumo.KazagumoOptions.send(this.guildId, {
op: 4,
d: {
guild_id: this.guildId,
channel_id: null,
self_mute: false,
self_deaf: false,
},
});
this.voiceId = null;
this.state = Interfaces_1.PlayerState.DISCONNECTED;
this.emit(Interfaces_1.Events.Debug, `Player disconnected; Guild id: ${this.guildId}`);
return this;
}
/**
* Destroy the player
* @returns KazagumoPlayer
*/
destroy() {
return __awaiter(this, void 0, void 0, function* () {
if (this.state === Interfaces_1.PlayerState.DESTROYING || this.state === Interfaces_1.PlayerState.DESTROYED)
throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed');
this.disconnect();
this.state = Interfaces_1.PlayerState.DESTROYING;
this.shoukaku.clean();
yield this.kazagumo.shoukaku.leaveVoiceChannel(this.guildId);
yield this.shoukaku.destroy();
this.shoukaku.removeAllListeners();
this.kazagumo.players.delete(this.guildId);
this.state = Interfaces_1.PlayerState.DESTROYED;
this.emit(Interfaces_1.Events.PlayerDestroy, this);
this.emit(Interfaces_1.Events.Debug, `Player destroyed; Guild id: ${this.guildId}`);
return this;
});
}
emit(event, ...args) {
this.kazagumo.emit(event, ...args);
}
}
exports.KazagumoPlayer = KazagumoPlayer;