player utils

This commit is contained in:
Snowflake107 2021-06-12 12:22:41 +05:45
parent 502fe11392
commit 2206d68dfb
6 changed files with 72 additions and 24 deletions

View file

@ -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}**!`);

View file

@ -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>;
}
/**

View file

@ -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);

View file

@ -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;
}
/**

View file

@ -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) {

View file

@ -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;
}