119 lines
3.6 KiB
TypeScript
119 lines
3.6 KiB
TypeScript
// based on https://github.com/discordjs/voice/blob/main/examples/music-bot/src/music/track.ts
|
|
//import ytdl from 'ytdl-core';
|
|
|
|
import ytdl from 'ytdl-core';
|
|
import play from 'play-dl';
|
|
import { AudioResource, createAudioResource, demuxProbe } from '@discordjs/voice';
|
|
|
|
/**
|
|
* This is the data required to create a Track object.
|
|
*/
|
|
export interface TrackData {
|
|
url: string;
|
|
title: string;
|
|
onStart: () => void;
|
|
onFinish: () => void;
|
|
onError: (error: Error) => void;
|
|
addedBy: string;
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
const noop = () => {};
|
|
|
|
/**
|
|
* A Track represents information about a YouTube video (in this context) that can be added to a queue.
|
|
* It contains the title and URL of the video, as well as functions onStart, onFinish, onError, that act
|
|
* as callbacks that are triggered at certain points during the track's lifecycle.
|
|
*
|
|
* Rather than creating an AudioResource for each video immediately and then keeping those in a queue,
|
|
* we use tracks as they don't pre-emptively load the videos. Instead, once a Track is taken from the
|
|
* queue, it is converted into an AudioResource just in time for playback.
|
|
*/
|
|
export class Track implements TrackData {
|
|
public readonly url: string;
|
|
public readonly title: string;
|
|
public readonly onStart: () => void;
|
|
public readonly onFinish: () => void;
|
|
public readonly onError: (error: Error) => void;
|
|
public readonly addedBy: string;
|
|
|
|
private constructor({ url, title, onStart, onFinish, onError, addedBy }: TrackData) {
|
|
this.url = url;
|
|
this.title = title;
|
|
this.onStart = onStart;
|
|
this.onFinish = onFinish;
|
|
this.onError = onError;
|
|
this.addedBy = addedBy;
|
|
}
|
|
|
|
/**
|
|
* Creates an AudioResource from this Track.
|
|
*/
|
|
public async createAudioResource(): AudioResource<Track> {
|
|
const stream = await play.stream(this.url);
|
|
return createAudioResource(stream.stream, { metadata: this, inputType: stream.type });
|
|
/*return new Promise((resolve, reject) => {
|
|
const process = youtubedl(
|
|
this.url,
|
|
{
|
|
o: '-',
|
|
q: '',
|
|
f: 'bestaudio[ext=webm+acodec=opus+asr=48000]/bestaudio',
|
|
r: '100K',
|
|
},
|
|
{ stdio: ['ignore', 'pipe', 'ignore'] },
|
|
);
|
|
if (!process.stdout) {
|
|
reject(new Error('No stdout'));
|
|
return;
|
|
}
|
|
const stream = process.stdout;
|
|
const onError = (error: Error) => {
|
|
if (!process.killed) process.kill();
|
|
stream.resume();
|
|
reject(error);
|
|
};
|
|
process
|
|
.once('spawn', () => {
|
|
demuxProbe(stream)
|
|
.then((probe: { stream: any; type: any; }) => resolve(createAudioResource(probe.stream, { metadata: this, inputType: probe.type })))
|
|
.catch(onError);
|
|
})
|
|
.catch(onError);
|
|
});*/
|
|
}
|
|
|
|
/**
|
|
* Creates a Track from a video URL and lifecycle callback methods.
|
|
*
|
|
* @param url The URL of the video
|
|
* @param methods Lifecycle callbacks
|
|
*
|
|
* @returns The created Track
|
|
*/
|
|
public static async from(url: string, addedBy: string, methods: Pick<Track, 'onStart' | 'onFinish' | 'onError'>): Promise<Track> {
|
|
const info = await ytdl.getInfo(url);
|
|
// The methods are wrapped so that we can ensure that they are only called once.
|
|
const wrappedMethods = {
|
|
onStart() {
|
|
wrappedMethods.onStart = noop;
|
|
methods.onStart();
|
|
},
|
|
onFinish() {
|
|
wrappedMethods.onFinish = noop;
|
|
methods.onFinish();
|
|
},
|
|
onError(error: Error) {
|
|
wrappedMethods.onError = noop;
|
|
methods.onError(error);
|
|
},
|
|
};
|
|
|
|
return new Track({
|
|
title: info.videoDetails.title,
|
|
addedBy: addedBy,
|
|
url,
|
|
...wrappedMethods,
|
|
});
|
|
}
|
|
} |