commit 3e5af0395b7a056fa02aaf7356a171b579ea7d86
parent 5ea48e22b8e1d867c33b678a5f157560ece5e9d4
Author: Demonstrandum <moi@knutsen.co>
Date: Wed, 11 Nov 2020 03:10:48 +0000
Prefetch media and deal with stream ending without explicit call to .end()
Diffstat:
2 files changed, 121 insertions(+), 60 deletions(-)
diff --git a/lib/commands/vc.ts b/lib/commands/vc.ts
@@ -1,8 +1,11 @@
import { TextChannel } from 'discord.js';
import ytdl from 'ytdl-core';
-
-const DL_OPTIONS : any = { filter: 'audioonly', dlChunkSize: 0 };
+const DL_OPTIONS : any = {
+ filter: 'audioonly',
+ dlChunkSize: 0,
+ quality: 'highestaudio'
+};
export default async(home_scope: HomeScope) => {
const { message, args, CONFIG, CLIENT, INSTANCE_VARIABLES } = home_scope;
@@ -17,69 +20,124 @@ export default async(home_scope: HomeScope) => {
if(!CONFIG.vc_queue) CONFIG.vc_queue = [];
+ const attempt_prefetch = (url: string): boolean => {
+ let stream = null;
+ try {
+ stream = ytdl(url, DL_OPTIONS);
+ } catch (e) { console.log(e); }
+
+ if (stream) {
+ GID.vc_prefetch[url] = stream;
+ return true;
+ }
+ return false;
+ };
+
switch (args[0]) {
- case "join":
- if (message.member.voice.channel) {
- GID.vc = await message.member.voice.channel.join();
- CONFIG.vc_channel = message.channel.id;
- } else {
- message.reply("Join A Channel First.");
- }
- break;
- case "leave":
- try {
- GID.vc.disconnect();
- } catch (error) {
- message.answer("```" + `${error}` + "```");
+ case "join": {
+ if (message.member.voice.channel) {
+ GID.vc = await message.member.voice.channel.join();
+ CONFIG.vc_channel = message.channel.id;
+ message.reply("Joined your voice chat.");
+ } else {
+ message.reply("Join a voice channel first.");
+ }
+ break;
+ } case "leave": {
+ try {
+ GID.vc.disconnect();
+ GID.vc.channel.leave();
+ } catch (error) {
+ message.answer("```" + `${error}` + "```");
+ }
+ break;
+ } case "pause": {
+ if (GID.vc_dispatcher) {
+ GID.vc_dispatcher.pause();
+ message.answer("Paused playback.");
+ }
+ else {
+ message.answer("Nothing is playing");
+ }
+ break;
+ } case "play": {
+ if (GID.vc_dispatcher) {
+ GID.vc_dispatcher.resume();
+ message.answer("Resuming playback.");
+ } else {
+ if (CONFIG.vc_queue.length === 0) {
+ message.answer("Please add a URL to the queue first.");
+ return;
}
- break;
- case "pause":
- if (GID.vc_dispatcher) GID.vc_dispatcher.pause();
- else message.answer("Nothing is playng");
- break;
- case "play":
- if (GID.vc_dispatcher) {
- GID.vc_dispatcher.resume();
- } else {
- GID.vc_dispatcher = GID.vc.play(
- ytdl(CONFIG.vc_queue.pop(), DL_OPTIONS));
- GID.vc_dispatcher.on("finish", () => {
- GID.vc_dispatcher.destroy();
- const next = CONFIG.vc_queue.pop();
- if (next) {
- GID.vc_dispatcher = GID.vc.play(
- ytdl(next, DL_OPTIONS));
- CLIENT.channels.fetch(CONFIG.vc_channel)
- .then((ch: TextChannel) =>
- ch.send(`Now playing: ${next}`));
- }
- })
+ const stream = GID.vc_prefetch[CONFIG.vc_queue.pop()];
+ GID.vc_current_stream = stream;
+ GID.vc_dispatcher = GID.vc.play(stream);
+ message.channel.send("Playing media from queue...");
+
+ const end_handler = () => {
+ GID.vc_dispatcher.destroy();
+ const next = CONFIG.vc_queue.pop();
+ if (next) {
+ const stream = GID.vc_prefetch[next];
+ GID.vc_current_stream = stream;
+ GID.vc_dispatcher = GID.vc.play(stream);
+ CLIENT.channels.fetch(CONFIG.vc_channel)
+ .then((ch: TextChannel) =>
+ ch.send(`Now playing: ${next}`));
+ }
}
- break;
- case "d":
- CONFIG.vc_queue.splice(Number(args[1]) - 1, 1);
- break;
- case "i":
- CONFIG.vc_queue.splice(Number(args[1]) - 1, 0, args[2]);
- break;
- case "ls":
- message.answer(ls(CONFIG.vc_queue));
- break;
- case "requeue":
- CONFIG.vc_queue = [];
- message.answer("Queue cleared");
- GID.vc_dispatcher.end();
- break;
- case "skip":
- GID.vc_dispatcher.end();
- break;
- default:
- // TODO: Add checking for valid URIs?
- CONFIG.vc_queue.push(args[0]);
+
+ GID.vc_dispatcher.on('finish', end_handler);
+ GID.vc_current_stream.on('end', () => {
+ GID.vc_dispatcher.end();
+ });
+ }
+ break;
+ } case "d": {
+ const pos = Number(args[1]);
+ CONFIG.vc_queue.splice(pos - 1, 1);
+ message.answer(`Removed media from queue at index ${pos}.`);
+ break;
+ } case "i": {
+ const pos = Number(args[1]);
+ const url = args[2];
+ const success = attempt_prefetch(url);
+ if (success) {
+ CONFIG.vc_queue.splice(pos - 1, 0, url);
+ message.answer(`Inserted into queue at index ${pos}.`);
+ } else {
+ message.answer("URL or media-type not valid.");
+ }
+ break;
+ } case "ls": {
+ message.answer(ls(CONFIG.vc_queue));
+ break;
+ } case "requeue": {
+ CONFIG.vc_queue = [];
+ GID.vc_current_stream = null;
+ message.answer("Queue cleared");
+ GID.vc_dispatcher.end();
+ break;
+ } case "skip": {
+ GID.vc_dispatcher.end();
+ GID.vc_current_stream.destroy();
+ message.answer("Skipping...");
+ break;
+ } default: {
+ const url = args[0];
+ const success = attempt_prefetch(url);
+ if (success) {
+ CONFIG.vc_queue.push(url);
+ message.answer("Succesfully added media to queue.");
+ } else {
+ message.answer("URL or media-type not valid.");
+ }
+ }
}
}
+
function ls(queue : string[]) { // TODO: This could be more sophisticated.
return "Queue size: " + queue.length
- + queue.map((e, i) => `\n ${i + 1}: ${e}`).join('');
+ + queue.map((e, i) => `\n ${i + 1}. ${e}`).join('');
}
diff --git a/lib/extensions.ts b/lib/extensions.ts
@@ -1,6 +1,7 @@
import { SimpOMatic } from './main';
import { Client } from '@typeit/discord';
import { VoiceConnection, StreamDispatcher } from 'discord.js';
+import stream from 'stream';
// Global Extensions:
declare global {
@@ -43,7 +44,9 @@ declare global {
export type GuildInstanceData = {
vc: VoiceConnection,
- vc_dispatcher: StreamDispatcher
+ vc_dispatcher: StreamDispatcher,
+ vc_prefetch: { [key: string]: stream.Readable },
+ vc_current_stream: stream.Readable
};
export type InstanceVariables = {