UNPKG

@discordx/music

Version:
462 lines (454 loc) 12.2 kB
"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 __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 )); // src/worker/index.ts var import_node_worker_threads = require("worker_threads"); // src/worker/audio-node-manager.ts var import_voice2 = require("@discordjs/voice"); var import_worker_threads2 = require("worker_threads"); // src/worker/audio-node.ts var import_voice = require("@discordjs/voice"); var import_worker_threads = require("worker_threads"); // src/worker/ytdl.ts var import_ytdl_core = __toESM(require("@distube/ytdl-core")); var import_prism_media = __toESM(require("prism-media")); var { FFmpeg, opus: Opus } = import_prism_media.default; var evn = [ "info", "progress", "abort", "request", "response", "error", "redirect", "retry", "reconnect" ]; var StreamDownloader = (url, options) => { if (!url) { throw new Error("No input url provided"); } if (typeof url !== "string") { throw new SyntaxError( `input URL must be a string. Received ${typeof url}!` ); } let FFmpegArgs = [ "-analyzeduration", "0", "-loglevel", "0", "-f", options?.fmt ?? "s16le", "-ar", "48000", "-ac", "2" ]; if (options?.seek) { FFmpegArgs.unshift("-ss", options.seek.toString()); } if (options?.encoderArgs) { FFmpegArgs = FFmpegArgs.concat(options.encoderArgs); } const transcoder = new FFmpeg({ args: FFmpegArgs }); const inputStream = (0, import_ytdl_core.default)(url, options); const output = inputStream.pipe(transcoder); if (options && !options.opusEncoded) { for (const event of evn) { inputStream.on(event, (...args) => output.emit(event, ...args)); } inputStream.on("error", () => transcoder.destroy()); output.on("close", () => transcoder.destroy()); return output; } const opus = new Opus.Encoder({ channels: 2, frameSize: 960, rate: 48e3 }); const outputStream = output.pipe(opus); output.on("error", (e) => outputStream.emit("error", e)); for (const event of evn) { inputStream.on(event, (...args) => outputStream.emit(event, ...args)); } outputStream.on("close", () => { transcoder.destroy(); opus.destroy(); }); return outputStream; }; var arbitraryStream = (stream, options) => { if (!stream) { throw new Error("No stream source provided"); } let FFmpegArgs; if (typeof stream === "string") { FFmpegArgs = [ "-reconnect", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "5", "-i", stream, "-analyzeduration", "0", "-loglevel", "0", "-f", options?.fmt ?? "s16le", "-ar", "48000", "-ac", "2" ]; } else { FFmpegArgs = [ "-analyzeduration", "0", "-loglevel", "0", "-f", options?.fmt ?? "s16le", "-ar", "48000", "-ac", "2" ]; } if (options?.seek) { FFmpegArgs.unshift("-ss", options.seek.toString()); } if (options?.encoderArgs) { FFmpegArgs = FFmpegArgs.concat(options.encoderArgs); } let transcoder = new FFmpeg({ args: FFmpegArgs }); if (typeof stream !== "string") { transcoder = stream.pipe(transcoder); stream.on("error", () => transcoder.destroy()); } if (options && !options.opusEncoded) { transcoder.on("close", () => transcoder.destroy()); return transcoder; } const opus = new Opus.Encoder({ channels: 2, frameSize: 960, rate: 48e3 }); const outputStream = transcoder.pipe(opus); outputStream.on("close", () => { transcoder.destroy(); opus.destroy(); }); return outputStream; }; StreamDownloader.arbitraryStream = arbitraryStream; var DiscordYTDLCore = Object.assign(StreamDownloader, import_ytdl_core.default); // src/worker/audio-node.ts var AudioNode = class { constructor(connection) { this.connection = connection; this.audioPlayer = new import_voice.AudioPlayer(); this.guildId = this.connection.joinConfig.guildId; this.channelId = this.connection.joinConfig.channelId; connection.subscribe(this.audioPlayer); this.setupEvents(); } audioPlayer; guildId; channelId; volume = 100; send(eventPayload) { const payloadData = { channelId: this.channelId, guildId: this.guildId, payload: eventPayload }; const payload = { data: payloadData, op: "AUDIO_NODE_EVENT" /* AudioNodeEvent */ }; import_worker_threads.parentPort?.postMessage(payload); } setupEvents() { this.audioPlayer.on("debug", (message) => { this.send({ message, type: "DEBUG" /* Debug */ }); }); this.audioPlayer.on("stateChange", (oldState, newState) => { this.send({ newState: newState.status, oldState: oldState.status, type: "STATE_CHANGE" /* StateChange */ }); }); } sendPlaybackInfo() { let ended = true; let playbackDuration = 0; const playerStatus = this.audioPlayer.state.status; if (playerStatus !== import_voice.AudioPlayerStatus.Idle) { ended = this.audioPlayer.state.resource.ended; playbackDuration = this.audioPlayer.state.resource.playbackDuration; } this.send({ ended, playbackDuration, playerStatus, type: "PLAYBACK_INFO" /* PlaybackInfo */ }); } play(options) { const stream = DiscordYTDLCore(options.query, { fmt: "s16le", highWaterMark: 1 << 25, opusEncoded: false, quality: "highestaudio", seek: options.seek }); const audioResource = (0, import_voice.createAudioResource)(stream, { inlineVolume: true, inputType: import_voice.StreamType.Raw, metadata: this }); audioResource.volume?.setVolumeLogarithmic(this.volume / 100); this.audioPlayer.play(audioResource); } destroy() { this.connection.destroy(); } setVolume(volume) { this.volume = volume; if (this.audioPlayer.state.status === import_voice.AudioPlayerStatus.Playing) { this.audioPlayer.state.resource.volume?.setVolume(this.volume / 100); } } pause() { this.audioPlayer.pause(); } resume() { this.audioPlayer.unpause(); } stop() { this.audioPlayer.stop(); } }; // src/worker/audio-node-manager.ts var AudioNodeManager = class { nodes = /* @__PURE__ */ new Map(); adapters = /* @__PURE__ */ new Map(); send(payload) { import_worker_threads2.parentPort?.postMessage(payload); } connect(config) { const adapterCreator = (adapter) => { this.adapters.set(config.guildId, adapter); return { destroy: () => { this.handleConnectionDestroy(config.guildId, config.channelId); }, sendPayload: (payload) => { this.handleVoiceStateUpdate( config.guildId, config.channelId, payload ); return true; } }; }; const voiceConnection = (0, import_voice2.joinVoiceChannel)({ adapterCreator, channelId: config.channelId, group: config.group, guildId: config.guildId, selfDeaf: config.deafen }); this.nodes.set(config.guildId, new AudioNode(voiceConnection)); } handleConnectionDestroy(guildId, channelId) { this.adapters.delete(guildId); this.nodes.delete(guildId); this.send({ data: { channelId, guildId }, op: "CONNECTION_DESTROY" /* ConnectionDestroy */ }); } handleVoiceStateUpdate(guildId, channelId, payload) { this.send({ data: { channelId, guildId, payload }, op: "VOICE_STATE_UPDATE" /* VoiceStateUpdate */ }); } disconnectAll() { for (const [id, node] of this.nodes) { node.destroy(); this.nodes.delete(id); } } disconnect(config) { const node = this.nodes.get(config.guildId); if (!node) { this.send({ data: { guildId: config.guildId }, op: "AUDIO_NODE_NOT_FOUND" /* AudioNodeNotFound */ }); return; } node.destroy(); this.nodes.delete(config.guildId); } sendPlaybackInfo(data) { const node = this.nodes.get(data.guildId); if (!node) { this.send({ data: { guildId: data.guildId }, op: "AUDIO_NODE_NOT_FOUND" /* AudioNodeNotFound */ }); return; } node.sendPlaybackInfo(); } play(data) { const node = this.nodes.get(data.guildId); if (!node) { this.send({ data: { guildId: data.guildId }, op: "AUDIO_NODE_NOT_FOUND" /* AudioNodeNotFound */ }); return; } node.play(data.payload); } setVolume(data) { const node = this.nodes.get(data.guildId); if (!node) { this.send({ data: { guildId: data.guildId }, op: "AUDIO_NODE_NOT_FOUND" /* AudioNodeNotFound */ }); return; } node.setVolume(data.volume); } pause(data) { const node = this.nodes.get(data.guildId); if (!node) { this.send({ data: { guildId: data.guildId }, op: "AUDIO_NODE_NOT_FOUND" /* AudioNodeNotFound */ }); return; } node.pause(); } resume(data) { const node = this.nodes.get(data.guildId); if (!node) { this.send({ data: { guildId: data.guildId }, op: "AUDIO_NODE_NOT_FOUND" /* AudioNodeNotFound */ }); return; } node.resume(); } stop(data) { const node = this.nodes.get(data.guildId); if (!node) { this.send({ data: { guildId: data.guildId }, op: "AUDIO_NODE_NOT_FOUND" /* AudioNodeNotFound */ }); return; } node.stop(); } }; // src/worker/index.ts var clients = new AudioNodeManager(); if (import_node_worker_threads.parentPort) { import_node_worker_threads.parentPort.on("message", ({ data, op }) => { switch (op) { case "DISCONNECT" /* Disconnect */: { clients.disconnect(data); break; } case "DISCONNECT_ALL" /* DisconnectAll */: { clients.disconnectAll(); break; } case "JOIN" /* Join */: { clients.connect(data); break; } case "ON_VOICE_SERVER_UPDATE" /* OnVoiceServerUpdate */: { clients.adapters.get(data.guild_id)?.onVoiceServerUpdate(data); break; } case "ON_VOICE_STATE_UPDATE" /* OnVoiceStateUpdate */: { clients.adapters.get(data.guild_id)?.onVoiceStateUpdate(data); break; } case "PLAY" /* Play */: { clients.play(data); break; } case "PAUSE" /* Pause */: { clients.pause(data); break; } case "RESUME" /* Resume */: { clients.resume(data); break; } case "STOP" /* Stop */: { clients.stop(data); break; } case "SET_VOLUME" /* SetVolume */: { clients.setVolume(data); break; } case "PING_PLAYBACK_INFO" /* PingPlaybackInfo */: { clients.sendPlaybackInfo(data); break; } default: break; } }); }