@discordx/music
Version:
Powerful Discord music player library using YTDL
474 lines (464 loc) • 13.8 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
AudioNodeEvent: () => AudioNodeEvent,
Node: () => Node,
ParentProcessEvent: () => ParentProcessEvent,
Queue: () => Queue,
QueueEvent: () => QueueEvent,
QueueManager: () => QueueManager,
RepeatMode: () => RepeatMode,
WorkerOperation: () => WorkerOperation
});
module.exports = __toCommonJS(index_exports);
// src/node.ts
var import_importer = require("@discordx/importer");
var import_events = require("events");
var import_worker_threads = require("worker_threads");
// src/types/audio-node.ts
var AudioNodeEvent = /* @__PURE__ */ ((AudioNodeEvent2) => {
AudioNodeEvent2["Debug"] = "DEBUG";
AudioNodeEvent2["Error"] = "ERROR";
AudioNodeEvent2["PlaybackInfo"] = "PLAYBACK_INFO";
AudioNodeEvent2["StateChange"] = "STATE_CHANGE";
AudioNodeEvent2["Subscription"] = "SUBSCRIPTION";
AudioNodeEvent2["UnSubscription"] = "UN_SUBSCRIPTION";
return AudioNodeEvent2;
})(AudioNodeEvent || {});
// src/types/communication-parent.ts
var ParentProcessEvent = /* @__PURE__ */ ((ParentProcessEvent2) => {
ParentProcessEvent2["AudioNodeEvent"] = "AUDIO_NODE_EVENT";
ParentProcessEvent2["AudioNodeNotFound"] = "AUDIO_NODE_NOT_FOUND";
ParentProcessEvent2["ConnectionDestroy"] = "CONNECTION_DESTROY";
ParentProcessEvent2["VoiceStateUpdate"] = "VOICE_STATE_UPDATE";
return ParentProcessEvent2;
})(ParentProcessEvent || {});
// src/types/communication-worker.ts
var WorkerOperation = /* @__PURE__ */ ((WorkerOperation2) => {
WorkerOperation2["Disconnect"] = "DISCONNECT";
WorkerOperation2["DisconnectAll"] = "DISCONNECT_ALL";
WorkerOperation2["Join"] = "JOIN";
WorkerOperation2["OnVoiceServerUpdate"] = "ON_VOICE_SERVER_UPDATE";
WorkerOperation2["OnVoiceStateUpdate"] = "ON_VOICE_STATE_UPDATE";
WorkerOperation2["Pause"] = "PAUSE";
WorkerOperation2["PingPlaybackInfo"] = "PING_PLAYBACK_INFO";
WorkerOperation2["Play"] = "PLAY";
WorkerOperation2["Resume"] = "RESUME";
WorkerOperation2["SetVolume"] = "SET_VOLUME";
WorkerOperation2["Stop"] = "STOP";
return WorkerOperation2;
})(WorkerOperation || {});
// src/types/queue-node-event.ts
var QueueEvent = /* @__PURE__ */ ((QueueEvent2) => {
QueueEvent2["ParentProcessEvent"] = "PARENT_PROCESS_EVENT";
return QueueEvent2;
})(QueueEvent || {});
// src/node.ts
var import_meta = {};
var Node = class extends import_events.EventEmitter {
constructor(client) {
super();
this.client = client;
const folder = (0, import_importer.isESM)() ? (0, import_importer.dirname)(import_meta.url) : __dirname;
this.worker = new import_worker_threads.Worker(
`${folder}/worker/${(0, import_importer.isESM)() ? "index.mjs" : "index.js"}`
);
this.setupEventListeners();
this.setupWorkerMessageHandler();
}
worker;
sendOp(payload) {
this.worker.postMessage(payload);
}
setupEventListeners() {
this.client.on("raw", (event) => {
if (event.t === "VOICE_STATE_UPDATE") {
this.sendOp({ data: event.d, op: "ON_VOICE_STATE_UPDATE" /* OnVoiceStateUpdate */ });
} else if (event.t === "VOICE_SERVER_UPDATE") {
this.sendOp({
data: event.d,
op: "ON_VOICE_SERVER_UPDATE" /* OnVoiceServerUpdate */
});
}
});
}
setupWorkerMessageHandler() {
this.worker.on("message", (payload) => {
this.emit("PARENT_PROCESS_EVENT" /* ParentProcessEvent */, payload);
switch (payload.op) {
case "VOICE_STATE_UPDATE" /* VoiceStateUpdate */:
void this.handleWorkerVoiceStateUpdate(payload.data);
break;
case "CONNECTION_DESTROY" /* ConnectionDestroy */:
this.handleWorkerConnectionDestroy(payload.data);
break;
default:
break;
}
});
}
async handleWorkerVoiceStateUpdate(data) {
const guild = await this.client.guilds.fetch(data.guildId);
guild.shard.send(data.payload);
}
handleWorkerConnectionDestroy(data) {
const { channelId } = data;
this.client.voice.adapters.get(channelId)?.destroy();
}
join(data) {
this.sendOp({ data, op: "JOIN" /* Join */ });
}
play(data) {
this.sendOp({ data, op: "PLAY" /* Play */ });
}
pingPlaybackInfo(data) {
this.sendOp({ data, op: "PING_PLAYBACK_INFO" /* PingPlaybackInfo */ });
}
setVolume(data) {
this.sendOp({ data, op: "SET_VOLUME" /* SetVolume */ });
}
pause(data) {
this.sendOp({ data, op: "PAUSE" /* Pause */ });
}
resume(data) {
this.sendOp({ data, op: "RESUME" /* Resume */ });
}
stop(data) {
this.sendOp({ data, op: "STOP" /* Stop */ });
}
disconnect(data) {
this.sendOp({ data, op: "DISCONNECT" /* Disconnect */ });
}
disconnectAll() {
this.sendOp({ op: "DISCONNECT_ALL" /* DisconnectAll */ });
}
};
// src/queue.ts
var import_voice = require("@discordjs/voice");
var import_shuffle = __toESM(require("lodash/shuffle.js"));
var RepeatMode = /* @__PURE__ */ ((RepeatMode2) => {
RepeatMode2["OFF"] = "OFF";
RepeatMode2["REPEAT_ALL"] = "REPEAT_ALL";
RepeatMode2["REPEAT_ONE"] = "REPEAT_ONE";
return RepeatMode2;
})(RepeatMode || {});
var Queue = class {
constructor(node, guildId) {
this.node = node;
this.guildId = guildId;
}
_leaveOnFinish = true;
_currentPlaybackTrack = null;
_playbackInfo = null;
_playerState = import_voice.AudioPlayerStatus.Idle;
_repeatMode = "OFF" /* OFF */;
_volume = 100;
_tracks = [];
_pingIntervalId = null;
get leaveOnFinish() {
return this._leaveOnFinish;
}
get client() {
return this.node.client;
}
get tracks() {
return this._tracks;
}
get currentPlaybackTrack() {
return this._currentPlaybackTrack;
}
get nextTrack() {
return this.tracks[0] ?? null;
}
get playbackInfo() {
return this._playbackInfo;
}
get playerState() {
return this._playerState;
}
get isPlaying() {
return this.playerState === import_voice.AudioPlayerStatus.Playing;
}
get repeatMode() {
return this._repeatMode;
}
get volume() {
return this._volume;
}
get size() {
return this.tracks.length;
}
/**
* Starts a ping to keep the playback information updated.
*/
startPing() {
this.stopPing();
this._pingIntervalId = setInterval(() => {
this.node.pingPlaybackInfo({ guildId: this.guildId });
}, 1e3);
}
/**
* Stops the ping for updating playback information.
*/
stopPing() {
if (this._pingIntervalId !== null) {
clearInterval(this._pingIntervalId);
this._pingIntervalId = null;
}
}
/**
* Adds tracks to the end of the queue.
* @param track - The tracks to be added.
*/
addTrack(...track) {
this._tracks.push(...track);
}
/**
* Adds tracks to the beginning of the queue.
* @param track - The tracks to be added.
*/
addTrackFirst(...track) {
this._tracks.unshift(...track);
}
/**
* Changes the position of a track in the queue.
* @param oldIndex - The current index of the track.
* @param newIndex - The new index for the track.
* @throws Will throw an error if the indices are out of bounds.
*/
changeTrackPosition(oldIndex, newIndex) {
if (oldIndex < 0 || oldIndex >= this.size || newIndex < 0 || newIndex >= this.size) {
throw new Error("Invalid track position");
}
const [movedTrack] = this._tracks.splice(oldIndex, 1);
if (movedTrack) {
this._tracks.splice(newIndex, 0, movedTrack);
}
}
/**
* Exits the player, clearing the queue and destroying the LavaPlayer instance.
*/
exit() {
this._currentPlaybackTrack = null;
this.removeAllTracks();
this.leave();
}
/**
* Joins a voice channel.
* @param data - The data required to join the channel.
*/
join(data) {
this.node.join({ ...data, guildId: this.guildId });
}
/**
* Leaves the voice channel.
*/
leave() {
this.stopPing();
this.node.disconnect({ guildId: this.guildId });
}
/**
* Pauses the playback.
*/
pause() {
this.node.pause({ guildId: this.guildId });
}
/**
* Plays the next track in the queue.
* @returns The next track that was played or null if the queue is empty.
*/
playNext() {
if (this._currentPlaybackTrack !== null && this.repeatMode !== "OFF" /* OFF */) {
if (this.repeatMode === "REPEAT_ALL" /* REPEAT_ALL */) {
this.addTrack(this._currentPlaybackTrack);
} else {
this.addTrackFirst(this._currentPlaybackTrack);
}
}
this._currentPlaybackTrack = null;
const nextTrack = this._tracks.shift();
if (!nextTrack) {
return null;
}
this._currentPlaybackTrack = nextTrack;
this.node.play({
guildId: this.guildId,
payload: {
initialVolume: this.volume,
query: nextTrack.url,
seek: nextTrack.seek
}
});
return nextTrack;
}
/**
* Removes tracks from the queue by their indices.
* @param indices - The indices of the tracks to be removed.
*/
removeTracks(...indices) {
indices.sort((a, b) => b - a);
for (const index of indices) {
if (index >= 0 && index < this.size) {
this._tracks.splice(index, 1);
}
}
}
/**
* Removes all tracks from the queue.
*/
removeAllTracks() {
this._tracks = [];
}
/**
* Resumes the playback.
*/
resume() {
this.node.resume({ guildId: this.guildId });
}
/**
* Seek the playback to specific position
* @param seconds in milliseconds
*/
seek(seconds) {
if (!this.currentPlaybackTrack) {
return null;
}
this.node.play({
guildId: this.guildId,
payload: {
initialVolume: this.volume,
query: this.currentPlaybackTrack.url,
seek: seconds
}
});
return this.currentPlaybackTrack;
}
/**
* Set leave on finish state
*/
setLeaveOnFinish(value) {
this._leaveOnFinish = value;
}
/**
* Set playback info
*/
setPlaybackInfo(info) {
this._playbackInfo = info;
}
/**
* set player state
*/
setPlayerState(state) {
this._playerState = state;
}
/**
* Sets the repeat mode for the queue.
* @param mode - The repeat mode to be set.
*/
setRepeatMode(mode) {
this._repeatMode = mode;
}
/**
* Sets the volume for playback.
* @param volume - The volume level to be set.
*/
setVolume(volume) {
this._volume = volume;
this.node.setVolume({ guildId: this.guildId, volume });
}
/**
* Shuffles the tracks in the queue.
*/
shuffleTracks() {
this._tracks = (0, import_shuffle.default)(this.tracks);
}
/**
* Skips the current track.
*/
skip() {
this.node.stop({ guildId: this.guildId });
}
};
// src/queue-manager.ts
var import_voice2 = require("@discordjs/voice");
var import_discord = require("discord.js");
var QueueManager = class {
constructor(node) {
this.node = node;
node.on("PARENT_PROCESS_EVENT" /* ParentProcessEvent */, (payload) => {
const queue = this.queues.get(payload.data.guildId);
if (!queue) {
return;
}
if (payload.op === "AUDIO_NODE_EVENT" /* AudioNodeEvent */) {
if (payload.data.payload.type === "PLAYBACK_INFO" /* PlaybackInfo */) {
queue.setPlaybackInfo(payload.data.payload);
}
if (payload.data.payload.type === "STATE_CHANGE" /* StateChange */) {
const { newState } = payload.data.payload;
queue.setPlayerState(newState);
if (newState === import_voice2.AudioPlayerStatus.Playing) {
queue.startPing();
} else {
queue.stopPing();
}
if (newState === import_voice2.AudioPlayerStatus.Idle || newState === import_voice2.AudioPlayerStatus.AutoPaused) {
queue.playNext();
}
if (queue.leaveOnFinish && !queue.currentPlaybackTrack && !queue.tracks.length) {
queue.leave();
}
}
}
});
}
queues = new import_discord.Collection();
queue(guildId, resolver) {
const queue = this.queues.get(guildId);
if (queue) {
return queue;
}
const clazz = resolver ? resolver() : new Queue(this.node, guildId);
this.queues.set(guildId, clazz);
return clazz;
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AudioNodeEvent,
Node,
ParentProcessEvent,
Queue,
QueueEvent,
QueueManager,
RepeatMode,
WorkerOperation
});