@discordx/music
Version:
Powerful Discord music player library using YTDL
445 lines (437 loc) • 10.8 kB
JavaScript
import "../chunk-WGQSBO3W.mjs";
// src/worker/index.ts
import { parentPort as parentPort3 } from "worker_threads";
// src/worker/audio-node-manager.ts
import { joinVoiceChannel } from "@discordjs/voice";
import { parentPort as parentPort2 } from "worker_threads";
// src/worker/audio-node.ts
import {
AudioPlayer,
AudioPlayerStatus,
createAudioResource,
StreamType
} from "@discordjs/voice";
import { parentPort } from "worker_threads";
// src/worker/ytdl.ts
import ytdl from "@distube/ytdl-core";
import pkg from "prism-media";
var { FFmpeg, opus: Opus } = pkg;
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 = ytdl(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, ytdl);
// src/worker/audio-node.ts
var AudioNode = class {
constructor(connection) {
this.connection = connection;
this.audioPlayer = new 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 */
};
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 !== 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 = createAudioResource(stream, {
inlineVolume: true,
inputType: 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 === 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) {
parentPort2?.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 = 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 (parentPort3) {
parentPort3.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;
}
});
}