UNPKG

@discordx/music

Version:
392 lines (388 loc) 10.1 kB
import { AudioNodeEvent, ParentProcessEvent, QueueEvent, WorkerOperation } from "./chunk-WGQSBO3W.mjs"; // src/node.ts import { dirname, isESM } from "@discordx/importer"; import { EventEmitter } from "events"; import { Worker } from "worker_threads"; var Node = class extends EventEmitter { constructor(client) { super(); this.client = client; const folder = isESM() ? dirname(import.meta.url) : __dirname; this.worker = new Worker( `${folder}/worker/${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 import { AudioPlayerStatus } from "@discordjs/voice"; import shuffle from "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 = 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 === 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 = shuffle(this.tracks); } /** * Skips the current track. */ skip() { this.node.stop({ guildId: this.guildId }); } }; // src/queue-manager.ts import { AudioPlayerStatus as AudioPlayerStatus2 } from "@discordjs/voice"; import { Collection } from "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 === AudioPlayerStatus2.Playing) { queue.startPing(); } else { queue.stopPing(); } if (newState === AudioPlayerStatus2.Idle || newState === AudioPlayerStatus2.AutoPaused) { queue.playNext(); } if (queue.leaveOnFinish && !queue.currentPlaybackTrack && !queue.tracks.length) { queue.leave(); } } } }); } queues = new 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; } }; export { AudioNodeEvent, Node, ParentProcessEvent, Queue, QueueEvent, QueueManager, RepeatMode, WorkerOperation };