player utils
This commit is contained in:
parent
502fe11392
commit
2206d68dfb
6 changed files with 72 additions and 24 deletions
|
@ -1,5 +1,5 @@
|
|||
import { Client } from "discord.js";
|
||||
import { Player } from "../src/index";
|
||||
import { Client, Message } from "discord.js";
|
||||
import { Player, Queue } from "../src/index";
|
||||
import { config } from "./config";
|
||||
|
||||
const client = new Client({
|
||||
|
@ -7,7 +7,10 @@ const client = new Client({
|
|||
});
|
||||
const player = new Player(client);
|
||||
|
||||
// player.on("trackStart", (queue, track) => console.log(`Now playing: ${track.title} in ${queue.guild.name}!`));
|
||||
player.on("trackStart", (queue, track) => {
|
||||
const guildQueue = queue as Queue<Message>;
|
||||
guildQueue.metadata.channel.send(`🎶 | Now playing: **${track.title}** in **${guildQueue.connection.channel.name}**!`);
|
||||
});
|
||||
|
||||
client.on("ready", () => console.log("Bot is online!"));
|
||||
|
||||
|
@ -20,37 +23,45 @@ client.on("message", async message => {
|
|||
if (!conn) return;
|
||||
return void message.channel.send(`Now Playing: **${conn.current.title}** (Played **${Math.floor(conn.connection.streamTime / 1000)} seconds**)`);
|
||||
}
|
||||
|
||||
if (message.content.startsWith("!pause") && message.guild.me.voice.channelID) {
|
||||
const conn = player.getQueue(message.guild.id);
|
||||
if (!conn) return;
|
||||
conn.setPaused(true);
|
||||
return void message.channel.send("Paused!");
|
||||
}
|
||||
|
||||
if (message.content.startsWith("!resume") && message.guild.me.voice.channelID) {
|
||||
const conn = player.getQueue(message.guild.id);
|
||||
if (!conn) return;
|
||||
conn.setPaused(false);
|
||||
return void message.channel.send("Resumed!");
|
||||
}
|
||||
|
||||
if (message.content.startsWith("!skip") && message.guild.me.voice.channelID) {
|
||||
const conn = player.getQueue(message.guild.id);
|
||||
if (!conn) return;
|
||||
conn.skip();
|
||||
return void message.channel.send("Done!");
|
||||
}
|
||||
|
||||
if (message.content.startsWith("!queue") && message.guild.me.voice.channelID) {
|
||||
const conn = player.getQueue(message.guild.id);
|
||||
if (!conn) return;
|
||||
return void message.channel.send({ content: conn.toString(), split: true });
|
||||
}
|
||||
|
||||
if (message.content.startsWith("!vol") && message.guild.me.voice.channelID) {
|
||||
const conn = player.getQueue(message.guild.id);
|
||||
if (!conn) return;
|
||||
conn.connection.setVolume(parseInt(message.content.slice(4).trim()));
|
||||
return void message.channel.send("Volume changed!");
|
||||
}
|
||||
|
||||
if (message.content.startsWith("!p") && message.member.voice.channelID) {
|
||||
const queue = player.createQueue(message.guild);
|
||||
const queue = player.createQueue<Message>(message.guild, {
|
||||
metadata: message
|
||||
});
|
||||
const song = await player.search(message.content.slice(2).trim(), message.author).then(x => x[0]);
|
||||
queue.addTrack(song);
|
||||
|
||||
|
@ -58,7 +69,6 @@ client.on("message", async message => {
|
|||
queue.connect(message.member.voice.channel)
|
||||
.then(async q => {
|
||||
await q.play();
|
||||
message.channel.send(`🎶 | Playing: **${song.title}**!`);
|
||||
});
|
||||
} else {
|
||||
message.channel.send(`🎶 | Queued: **${song.title}**!`);
|
||||
|
|
|
@ -17,16 +17,18 @@ class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
|||
this.client = client;
|
||||
}
|
||||
|
||||
createQueue(guild: Guild, queueInitOptions?: PlayerOptions) {
|
||||
if (this.queues.has(guild.id)) return this.queues.get(guild.id);
|
||||
createQueue<T = unknown>(guild: Guild, queueInitOptions?: PlayerOptions & { metadata?: any }) {
|
||||
if (this.queues.has(guild.id)) return this.queues.get(guild.id) as Queue<T>;
|
||||
|
||||
const queue = new Queue(this, guild, queueInitOptions);
|
||||
queue.metadata = queueInitOptions.metadata;
|
||||
this.queues.set(guild.id, queue);
|
||||
|
||||
return queue;
|
||||
return queue as Queue<T>;
|
||||
}
|
||||
|
||||
getQueue(guild: Snowflake) {
|
||||
return this.queues.get(guild);
|
||||
getQueue<T = unknown>(guild: Snowflake) {
|
||||
return this.queues.get(guild) as Queue<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,17 +2,18 @@ import { Guild, StageChannel, VoiceChannel } from "discord.js";
|
|||
import { Player } from "../Player";
|
||||
import { StreamDispatcher } from "../VoiceInterface/BasicStreamDispatcher";
|
||||
import Track from "./Track";
|
||||
import { PlayerOptions } from "../types/types";
|
||||
import { PlayerOptions, PlayOptions } from "../types/types";
|
||||
import ytdl from "discord-ytdl-core";
|
||||
import { AudioResource, StreamType } from "@discordjs/voice";
|
||||
|
||||
class Queue {
|
||||
class Queue<T = unknown> {
|
||||
public readonly guild: Guild;
|
||||
public readonly player: Player;
|
||||
public connection: StreamDispatcher;
|
||||
public tracks: Track[] = [];
|
||||
public options: PlayerOptions;
|
||||
public playing = false;
|
||||
public metadata?: T = null;
|
||||
|
||||
constructor(player: Player, guild: Guild, options: PlayerOptions = {}) {
|
||||
this.player = player;
|
||||
|
@ -49,6 +50,8 @@ class Queue {
|
|||
const connection = await this.player.voiceUtils.connect(channel);
|
||||
this.connection = connection;
|
||||
|
||||
if (channel.type === "stage") await channel.guild.me.voice.setRequestToSpeak(true).catch((e) => {});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -77,10 +80,16 @@ class Queue {
|
|||
return paused ? this.connection.pause() : this.connection.resume();
|
||||
}
|
||||
|
||||
async play(src?: Track) {
|
||||
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 = {}) {
|
||||
if (!this.connection || !this.connection.voiceConnection)
|
||||
throw new Error("Voice connection is not available, use <Queue>.connect()!");
|
||||
const track = src ?? this.tracks.shift();
|
||||
const track = options.filtersUpdate ? this.current : src ?? this.tracks.shift();
|
||||
if (!track) return;
|
||||
|
||||
let resource: AudioResource<Track>;
|
||||
|
@ -89,7 +98,9 @@ class Queue {
|
|||
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,
|
||||
fmt: "s16le"
|
||||
fmt: "s16le",
|
||||
encoderArgs: options.encoderArgs ?? [],
|
||||
seek: options.seek
|
||||
});
|
||||
|
||||
resource = this.connection.createStream(stream, {
|
||||
|
@ -104,7 +115,9 @@ class Queue {
|
|||
{
|
||||
// because we don't wanna decode opus into pcm again just for volume, let discord.js handle that
|
||||
opusEncoded: false,
|
||||
fmt: "s16le"
|
||||
fmt: "s16le",
|
||||
encoderArgs: options.encoderArgs ?? [],
|
||||
seek: options.seek
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -119,11 +132,13 @@ class Queue {
|
|||
|
||||
dispatcher.on("start", () => {
|
||||
this.playing = true;
|
||||
this.player.emit("trackStart", this, this.current);
|
||||
if (!options.filtersUpdate) this.player.emit("trackStart", this, this.current);
|
||||
});
|
||||
|
||||
dispatcher.on("finish", () => {
|
||||
this.playing = false;
|
||||
if (options.filtersUpdate) return;
|
||||
|
||||
if (!this.tracks.length) {
|
||||
this.destroy();
|
||||
this.player.emit("queueEnd", this);
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
VoiceConnection,
|
||||
VoiceConnectionStatus
|
||||
} from "@discordjs/voice";
|
||||
import { StageChannel, VoiceChannel } from "discord.js";
|
||||
import { Duplex, Readable } from "stream";
|
||||
import { TypedEmitter as EventEmitter } from "tiny-typed-emitter";
|
||||
import Track from "../Structures/Track";
|
||||
|
@ -25,14 +26,17 @@ export interface VoiceEvents {
|
|||
class BasicStreamDispatcher extends EventEmitter<VoiceEvents> {
|
||||
public readonly voiceConnection: VoiceConnection;
|
||||
public readonly audioPlayer: AudioPlayer;
|
||||
public readonly channel: VoiceChannel | StageChannel;
|
||||
public connectPromise?: Promise<void>;
|
||||
public audioResource?: AudioResource<Track>;
|
||||
public paused = false;
|
||||
|
||||
constructor(connection: VoiceConnection) {
|
||||
constructor(connection: VoiceConnection, channel: VoiceChannel | StageChannel) {
|
||||
super();
|
||||
|
||||
this.voiceConnection = connection;
|
||||
this.audioPlayer = createAudioPlayer();
|
||||
this.channel = channel;
|
||||
|
||||
this.voiceConnection.on("stateChange", (_, newState) => {
|
||||
if (newState.status === VoiceConnectionStatus.Disconnected) {
|
||||
|
@ -64,10 +68,12 @@ class BasicStreamDispatcher extends EventEmitter<VoiceEvents> {
|
|||
|
||||
this.audioPlayer.on("stateChange", (oldState, newState) => {
|
||||
if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) {
|
||||
this.audioResource = null;
|
||||
void this.emit("finish");
|
||||
if (!this.paused) {
|
||||
this.audioResource = null;
|
||||
void this.emit("finish");
|
||||
}
|
||||
} else if (newState.status === AudioPlayerStatus.Playing) {
|
||||
void this.emit("start");
|
||||
if (!this.paused) void this.emit("start");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -116,11 +122,15 @@ class BasicStreamDispatcher extends EventEmitter<VoiceEvents> {
|
|||
}
|
||||
|
||||
pause(interpolateSilence?: boolean) {
|
||||
return this.audioPlayer.pause(interpolateSilence);
|
||||
const success = this.audioPlayer.pause(interpolateSilence);
|
||||
this.paused = success;
|
||||
return success;
|
||||
}
|
||||
|
||||
resume() {
|
||||
return this.audioPlayer.unpause();
|
||||
const success = this.audioPlayer.unpause();
|
||||
this.paused = !success;
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,7 +27,7 @@ class VoiceUtils {
|
|||
|
||||
try {
|
||||
conn = await entersState(conn, VoiceConnectionStatus.Ready, options?.maxTime ?? 20000);
|
||||
const sub = new StreamDispatcher(conn);
|
||||
const sub = new StreamDispatcher(conn, channel);
|
||||
this.cache.set(channel.guild.id, sub);
|
||||
return sub;
|
||||
} catch (err) {
|
||||
|
|
|
@ -133,3 +133,14 @@ export interface PlayerEvents {
|
|||
trackAdd: () => any;
|
||||
trackStart: (queue: Queue, track: Track) => any;
|
||||
}
|
||||
|
||||
export interface PlayOptions {
|
||||
/** If this play is triggered for filters update */
|
||||
filtersUpdate?: boolean;
|
||||
|
||||
/** ffmpeg args passed to encoder */
|
||||
encoderArgs?: string[];
|
||||
|
||||
/** Time to seek to before playing */
|
||||
seek?: number;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue