From d1e7af0682c91504f81f2a3ea59b6e0fa9e9c777 Mon Sep 17 00:00:00 2001 From: ThinLiquid Date: Fri, 31 May 2024 10:29:14 +0100 Subject: [PATCH] Update lyrics.ts --- src/lyrics.ts | 143 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 94 insertions(+), 49 deletions(-) diff --git a/src/lyrics.ts b/src/lyrics.ts index a387e2d..2698593 100644 --- a/src/lyrics.ts +++ b/src/lyrics.ts @@ -12,83 +12,115 @@ const DEFAULT_TEXT = '♫⋆。♪ ₊˚♬ ゚.' class Lyrics { private readonly container: HTML + private readonly prev: HTML private readonly current: HTML private readonly next: HTML private currentTrack: Track | null = null - private intervalId: ReturnType | null = null constructor (private readonly player: Player, private readonly queue: Queue) { this.container = new HTML('div').classOn('lyrics') + this.prev = new HTML('div') this.current = new HTML('div') this.next = new HTML('div') + this.init() } - private async getLyrics (track: Track): Promise { - try { - const res = await fetch( - `https://lrclib.net/api/get?track_name=${encodeURIComponent(track.name)}&album_name=${encodeURIComponent(track.album.name)}&artist_name=${encodeURIComponent(track.artists[0].name)}&duration=${track.duration_ms / 1000}` - ) - if (res.ok) { - return await res.json() - } else { - console.error(`Failed to fetch lyrics: ${res.statusText}`) - return null - } - } catch (error) { - console.error(`Error fetching lyrics: ${error}`) + /** + * Get the lyrics of a track + * + * @private + * @param track The track to get the lyrics of + * @returns The lyrics of the track + */ + private async getLyrics (track: Track): Promise { + return await fetch( + `https://lrclib.net/api/get?track_name=${encodeURIComponent( + track.name + )}&album_name=${encodeURIComponent( + track.album.name + )}&artist_name=${encodeURIComponent(track.artists[0].name)}&duration=${ + track.duration_ms / 1000 + }` + ).then(async res => { + if (res.ok) return await res.json() return null - } + }) } + /** + * Initialize the lyrics + * + * @private + * @memberof Lyrics + */ private init (): void { + // Append the elements to the document this.container.appendTo(document.body) + this.prev.appendTo(this.container) this.current.appendTo(this.container) this.next.appendTo(this.container) + + // Register the events this.registerEvents() } + /** + * Handle when no lyrics are found + * + * @private + * @memberof Lyrics + */ private handleNoLyrics (): void { this.prev.text('') this.current.text(DEFAULT_TEXT) this.next.text("Can't find lyrics for this song.") + this.prev.classOn('appear') this.current.classOn('appear') this.next.classOn('appear') } + /** + * Register the events + * + * @private + * @memberof Lyrics + */ private registerEvents (): void { this.player.on('trackchange', () => { this.handleLyrics().catch(console.error) }) } + /** + * Handle the lyrics + * + * @private + * @memberof Lyrics + */ private async handleLyrics (): Promise { - if (!this.queue.currentTrack) { - console.error('No current track found in the queue.') - this.handleNoLyrics() - return - } - this.player.audio.currentTime = 0 const lyrics = await this.getLyrics(this.queue.currentTrack) - if (!lyrics || !lyrics.syncedLyrics) { + if (lyrics?.syncedLyrics == null) { this.handleNoLyrics() return } - const lyricsData = lrcParser(lyrics.syncedLyrics).scripts as unknown as Lyric[] - lyricsData.forEach(lyric => { lyric.id = uuid.v4() }) + + lyricsData.forEach(lyric => { + lyric.id = uuid.v4() + }) let previousLyricId = '' this.prev.text('') this.current.text(DEFAULT_TEXT) - this.next.text(lyricsData[0]?.text ?? DEFAULT_TEXT) + this.next.text(lyricsData[0].text) this.prev.classOn('appear') this.current.classOn('appear') @@ -99,19 +131,26 @@ class Lyrics { currentTime <= lyric.end - LYRIC_OFFSET && lyric.id !== previousLyricId - const updateLyrics = (): void => { - const currentTime = this.player.audio.currentTime - const index = lyricsData.findIndex( - lyric => currentTime >= (lyric.start ?? -Infinity) - LYRIC_OFFSET && currentTime <= lyric.end - LYRIC_OFFSET + const updateLyrics = async (): Promise => { + this.currentTrack = this.queue.currentTrack + let index: number = lyricsData.findIndex( + lyric => + this.player.audio.currentTime >= (lyric.start ?? -Infinity) - LYRIC_OFFSET && + this.player.audio.currentTime <= lyric.end - LYRIC_OFFSET ) - if (index === -1 || !isCurrentLyric(lyricsData[index], currentTime)) { + const lyric = lyricsData[index] + + // Check if index is -1, if so, return early + if (index === -1 || !isCurrentLyric(lyric, this.player.audio.currentTime)) { return } - const lyric = lyricsData[index] - const nextLyricText = lyricsData[index + 1]?.text ?? '' - const prevLyricText = lyricsData[index - 1]?.text ?? DEFAULT_TEXT + const getLyricText = (index: number, defaultText: string): string => + lyricsData[index]?.text ?? defaultText + + const nextLyricText = getLyricText(index + 1, '') + const prevLyricText = getLyricText(index - 1, DEFAULT_TEXT) const updateText = (element: InstanceType, text: string): void => { const trimmedText = text.trim() @@ -124,31 +163,37 @@ class Lyrics { this.prev.classOff('appear') this.next.classOff('appear') - setTimeout(() => { - this.prev.classOn('appear') - this.current.classOn('appear') - this.next.classOn('appear') - }, APPEAR_DELAY) + await new Promise(resolve => { + setTimeout(resolve, APPEAR_DELAY) + }) + + this.prev.classOn('appear') + this.current.classOn('appear') + this.next.classOn('appear') updateText(this.next, index + 1 !== lyricsData.length ? nextLyricText : 'ᶻ 𝗓 𐰁 .ᐟ') updateText(this.current, lyric.text) updateText(this.prev, prevLyricText) + index++ previousLyricId = lyric.id - } - if (this.intervalId !== null) { - clearInterval(this.intervalId) + if (index !== lyricsData.length) { + return + } + + this.player.audio.ontimeupdate = null } - this.intervalId = setInterval(() => { - if (this.queue.currentTrack !== this.currentTrack) { - clearInterval(this.intervalId!) - this.intervalId = null - } else { - updateLyrics() - } - }, 100) + requestAnimationFrame(() => { updateLyrics().catch(console.error) }) + + this.player.audio.ontimeupdate = () => { + if ( + (this.queue.currentTrack as any)._id !== + (this.currentTrack as any)._id + ) { this.player.audio.ontimeupdate = null } + requestAnimationFrame(() => { updateLyrics().catch(console.error) }) + } } }