// 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 { 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): Promise { 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; } }