2021-06-11 15:32:22 +05:00
|
|
|
import { Guild, StageChannel, VoiceChannel } from "discord.js";
|
|
|
|
import { Player } from "../Player";
|
2021-06-11 23:19:52 +05:00
|
|
|
import { StreamDispatcher } from "../VoiceInterface/BasicStreamDispatcher";
|
2021-06-11 15:32:22 +05:00
|
|
|
import Track from "./Track";
|
2021-06-12 11:37:41 +05:00
|
|
|
import { PlayerOptions, PlayOptions } from "../types/types";
|
2021-06-11 23:19:52 +05:00
|
|
|
import ytdl from "discord-ytdl-core";
|
|
|
|
import { AudioResource, StreamType } from "@discordjs/voice";
|
2021-06-11 15:32:22 +05:00
|
|
|
|
2021-06-12 11:37:41 +05:00
|
|
|
class Queue<T = unknown> {
|
2021-06-11 15:32:22 +05:00
|
|
|
public readonly guild: Guild;
|
|
|
|
public readonly player: Player;
|
2021-06-11 23:19:52 +05:00
|
|
|
public connection: StreamDispatcher;
|
2021-06-11 15:32:22 +05:00
|
|
|
public tracks: Track[] = [];
|
|
|
|
public options: PlayerOptions;
|
2021-06-11 23:19:52 +05:00
|
|
|
public playing = false;
|
2021-06-12 11:37:41 +05:00
|
|
|
public metadata?: T = null;
|
2021-06-11 15:32:22 +05:00
|
|
|
|
|
|
|
constructor(player: Player, guild: Guild, options: PlayerOptions = {}) {
|
|
|
|
this.player = player;
|
|
|
|
this.guild = guild;
|
|
|
|
this.options = {};
|
|
|
|
|
|
|
|
Object.assign(
|
|
|
|
this.options,
|
|
|
|
{
|
|
|
|
leaveOnEnd: true,
|
|
|
|
leaveOnEndCooldown: 1000,
|
|
|
|
leaveOnStop: true,
|
|
|
|
leaveOnEmpty: true,
|
|
|
|
leaveOnEmptyCooldown: 1000,
|
|
|
|
autoSelfDeaf: true,
|
|
|
|
enableLive: false,
|
|
|
|
ytdlDownloadOptions: {},
|
|
|
|
useSafeSearch: false,
|
|
|
|
disableAutoRegister: false,
|
2021-06-11 23:19:52 +05:00
|
|
|
fetchBeforeQueued: false,
|
|
|
|
initialVolume: 100
|
2021-06-11 15:32:22 +05:00
|
|
|
} as PlayerOptions,
|
|
|
|
options
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-06-11 16:50:43 +05:00
|
|
|
get current() {
|
2021-06-11 23:19:52 +05:00
|
|
|
return this.connection.audioResource?.metadata ?? this.tracks[0];
|
2021-06-11 16:50:43 +05:00
|
|
|
}
|
|
|
|
|
2021-06-11 23:19:52 +05:00
|
|
|
async connect(channel: StageChannel | VoiceChannel) {
|
|
|
|
if (!["stage", "voice"].includes(channel?.type))
|
|
|
|
throw new TypeError(`Channel type must be voice or stage, got ${channel?.type}!`);
|
2021-06-11 16:50:43 +05:00
|
|
|
const connection = await this.player.voiceUtils.connect(channel);
|
2021-06-11 23:19:52 +05:00
|
|
|
this.connection = connection;
|
2021-06-11 15:32:22 +05:00
|
|
|
|
2021-06-12 11:37:41 +05:00
|
|
|
if (channel.type === "stage") await channel.guild.me.voice.setRequestToSpeak(true).catch((e) => {});
|
|
|
|
|
2021-06-11 15:32:22 +05:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
destroy() {
|
2021-06-11 23:19:52 +05:00
|
|
|
this.connection.end();
|
|
|
|
this.connection.disconnect();
|
2021-06-11 15:32:22 +05:00
|
|
|
this.player.queues.delete(this.guild.id);
|
|
|
|
}
|
2021-06-11 16:50:43 +05:00
|
|
|
|
2021-06-11 23:19:52 +05:00
|
|
|
skip() {
|
|
|
|
if (!this.connection) return false;
|
2021-06-12 00:18:53 +05:00
|
|
|
this.connection.end();
|
|
|
|
return true;
|
2021-06-11 23:19:52 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
addTrack(track: Track) {
|
|
|
|
this.addTracks([track]);
|
|
|
|
}
|
|
|
|
|
|
|
|
addTracks(tracks: Track[]) {
|
|
|
|
this.tracks.push(...tracks);
|
|
|
|
}
|
|
|
|
|
2021-06-12 00:18:53 +05:00
|
|
|
setPaused(paused?: boolean) {
|
|
|
|
if (!this.connection) return false;
|
|
|
|
return paused ? this.connection.pause() : this.connection.resume();
|
|
|
|
}
|
|
|
|
|
2021-06-12 11:37:41 +05:00
|
|
|
setBitrate(bitrate: number | "auto") {
|
|
|
|
if (!this.connection?.audioResource?.encoder) return;
|
|
|
|
if (bitrate === "auto") bitrate = this.connection.channel?.bitrate ?? 64000;
|
|
|
|
this.connection.audioResource.encoder.setBitrate(bitrate);
|
|
|
|
}
|
|
|
|
|
|
|
|
async play(src?: Track, options: PlayOptions = {}) {
|
2021-06-11 23:19:52 +05:00
|
|
|
if (!this.connection || !this.connection.voiceConnection)
|
|
|
|
throw new Error("Voice connection is not available, use <Queue>.connect()!");
|
2021-06-12 11:37:41 +05:00
|
|
|
const track = options.filtersUpdate ? this.current : src ?? this.tracks.shift();
|
2021-06-11 23:19:52 +05:00
|
|
|
if (!track) return;
|
|
|
|
|
|
|
|
let resource: AudioResource<Track>;
|
|
|
|
|
|
|
|
if (["youtube", "spotify"].includes(track.raw.source)) {
|
|
|
|
const stream = ytdl(track.raw.source === "spotify" ? track.raw.engine : track.url, {
|
|
|
|
// because we don't wanna decode opus into pcm again just for volume, let discord.js handle that
|
|
|
|
opusEncoded: false,
|
2021-06-12 11:37:41 +05:00
|
|
|
fmt: "s16le",
|
|
|
|
encoderArgs: options.encoderArgs ?? [],
|
|
|
|
seek: options.seek
|
2021-06-11 23:19:52 +05:00
|
|
|
});
|
|
|
|
|
|
|
|
resource = this.connection.createStream(stream, {
|
|
|
|
type: StreamType.Raw,
|
|
|
|
data: track
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
const stream = ytdl.arbitraryStream(
|
|
|
|
track.raw.source === "soundcloud"
|
|
|
|
? await track.raw.engine.downloadProgressive()
|
|
|
|
: (track.raw.engine as string),
|
|
|
|
{
|
|
|
|
// because we don't wanna decode opus into pcm again just for volume, let discord.js handle that
|
|
|
|
opusEncoded: false,
|
2021-06-12 11:37:41 +05:00
|
|
|
fmt: "s16le",
|
|
|
|
encoderArgs: options.encoderArgs ?? [],
|
|
|
|
seek: options.seek
|
2021-06-11 23:19:52 +05:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
resource = this.connection.createStream(stream, {
|
|
|
|
type: StreamType.Raw,
|
|
|
|
data: track
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-06-12 00:18:53 +05:00
|
|
|
const dispatcher = await this.connection.playStream(resource);
|
2021-06-11 23:19:52 +05:00
|
|
|
dispatcher.setVolume(this.options.initialVolume);
|
|
|
|
|
|
|
|
dispatcher.on("start", () => {
|
|
|
|
this.playing = true;
|
2021-06-12 11:37:41 +05:00
|
|
|
if (!options.filtersUpdate) this.player.emit("trackStart", this, this.current);
|
2021-06-11 23:19:52 +05:00
|
|
|
});
|
|
|
|
|
|
|
|
dispatcher.on("finish", () => {
|
|
|
|
this.playing = false;
|
2021-06-12 11:37:41 +05:00
|
|
|
if (options.filtersUpdate) return;
|
|
|
|
|
2021-06-11 23:19:52 +05:00
|
|
|
if (!this.tracks.length) {
|
|
|
|
this.destroy();
|
|
|
|
this.player.emit("queueEnd", this);
|
|
|
|
} else {
|
|
|
|
const nextTrack = this.tracks.shift();
|
|
|
|
this.play(nextTrack);
|
|
|
|
}
|
|
|
|
});
|
2021-06-11 16:50:43 +05:00
|
|
|
}
|
2021-06-11 19:57:49 +05:00
|
|
|
|
|
|
|
*[Symbol.iterator]() {
|
|
|
|
yield* this.tracks;
|
|
|
|
}
|
2021-06-11 23:19:52 +05:00
|
|
|
|
|
|
|
toJSON() {
|
|
|
|
return {
|
|
|
|
guild: this.guild.id,
|
|
|
|
options: this.options,
|
|
|
|
tracks: this.tracks.map((m) => m.toJSON())
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
toString() {
|
|
|
|
if (!this.tracks.length) return "No songs available to display!";
|
|
|
|
return `**Upcoming Songs:**\n${this.tracks.map((m, i) => `${i + 1}. **${m.title}**`).join("\n")}`;
|
|
|
|
}
|
2021-06-11 15:32:22 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
export { Queue };
|