permanent-waves/track.ts

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