101 lines
3 KiB
TypeScript
101 lines
3 KiB
TypeScript
// based on https://github.com/discordjs/voice/blob/main/examples/music-bot/src/music/track.ts
|
|
|
|
import { AudioResource, createAudioResource } from '@discordjs/voice';
|
|
import ytdl from "@distube/ytdl-core";
|
|
|
|
/**
|
|
* 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(source, metadata): AudioResource<Track> {
|
|
const resource = createAudioResource(source, { inlineVolume: true, metadata: metadata });
|
|
resource.volume.setVolume(0.6);
|
|
|
|
return resource;
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
});
|
|
}
|
|
|
|
public getStream() {
|
|
const stream = ytdl(this.url, {
|
|
filter: "audioonly",
|
|
encoderArgs: ['-af', 'bass=g=10,dynaudnorm=f=200'],
|
|
liveBuffer: 40000,
|
|
highWaterMark: 1 << 30
|
|
});
|
|
|
|
return stream;
|
|
}
|
|
} |