Skip to content

Commit

Permalink
Update lyrics.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
ThinLiquid authored May 31, 2024
1 parent caca29b commit 7f5773b
Showing 1 changed file with 61 additions and 214 deletions.
275 changes: 61 additions & 214 deletions src/lyrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,8 @@ class Lyrics {
private readonly next: HTML

private currentTrack: Track | null = null
private lyricsData: Lyric[] = []
private previousLyricId: string = ''

constructor(private readonly player: Player, private readonly queue: Queue) {
console.log(currentTrack)
this.container = new HTML('div').classOn('lyrics')

this.prev = new HTML('div')import HTML from '@datkat21/html'
import Player from './player'
import { Track } from '@spotify/web-api-ts-sdk'
import lrcParser from 'lrc-parser'
import * as uuid from 'uuid'
import Queue from './queue'
import { Lyric, LyricsResponse } from './types'

const LYRIC_OFFSET = 0.25
const APPEAR_DELAY = 250
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 lyricsData: Lyric[] = []
private previousLyricId: string = ''

constructor(private readonly player: Player, private readonly queue: Queue) {
constructor (private readonly player: Player, private readonly queue: Queue) {
this.container = new HTML('div').classOn('lyrics')

this.prev = new HTML('div')
Expand All @@ -65,7 +36,7 @@ class Lyrics {
* @param track The track to get the lyrics of
* @returns The lyrics of the track
*/
private async getLyrics(track: Track): Promise<LyricsResponse> {
private async getLyrics (track: Track): Promise<LyricsResponse> {
return await fetch(
`https://lrclib.net/api/get?track_name=${encodeURIComponent(
track.name
Expand All @@ -86,7 +57,7 @@ class Lyrics {
* @private
* @memberof Lyrics
*/
private init(): void {
private init (): void {
// Append the elements to the document
this.container.appendTo(document.body)

Expand All @@ -104,7 +75,7 @@ class Lyrics {
* @private
* @memberof Lyrics
*/
private handleNoLyrics(): void {
private handleNoLyrics (): void {
this.prev.text('')
this.current.text(DEFAULT_TEXT)
this.next.text("Can't find lyrics for this song.")
Expand All @@ -120,7 +91,7 @@ class Lyrics {
* @private
* @memberof Lyrics
*/
private registerEvents(): void {
private registerEvents (): void {
this.player.on('trackchange', () => {
this.handleLyrics().catch(console.error)
})
Expand All @@ -132,221 +103,97 @@ class Lyrics {
* @private
* @memberof Lyrics
*/
private async handleLyrics(): Promise<void> {
private async handleLyrics (): Promise<void> {
this.player.audio.currentTime = 0
const lyrics = await this.getLyrics(this.queue.currentTrack)
if (lyrics?.syncedLyrics == null) {
this.handleNoLyrics()
return
}
this.lyricsData = lrcParser(lyrics.syncedLyrics).scripts as unknown as Lyric[]
const lyricsData = lrcParser(lyrics.syncedLyrics).scripts as unknown as Lyric[]

this.lyricsData.forEach(lyric => {
lyricsData.forEach(lyric => {
lyric.id = uuid.v4()
})

this.previousLyricId = ''
let previousLyricId = ''

this.prev.text('')
this.current.text(DEFAULT_TEXT)
this.next.text(this.lyricsData[0].text)
this.next.text(lyricsData[0].text)

this.prev.classOn('appear')
this.current.classOn('appear')
this.next.classOn('appear')

this.player.audio.ontimeupdate = () => this.updateLyrics()
this.updateLyrics() // Initial call to set the first lyrics
}

/**
* Update the lyrics based on the current time of the audio
*
* @private
* @memberof Lyrics
*/
private updateLyrics(): void {
const currentTime = this.player.audio.currentTime
const isCurrentLyric = (lyric: Lyric, currentTime: number): boolean =>
currentTime >= (lyric.start ?? -Infinity) - LYRIC_OFFSET &&
currentTime <= lyric.end - LYRIC_OFFSET &&
lyric.id !== previousLyricId

const index = this.lyricsData.findIndex(
lyric =>
currentTime >= (lyric.start ?? -Infinity) - LYRIC_OFFSET &&
currentTime <= lyric.end - LYRIC_OFFSET
)
const updateLyrics = async (): Promise<void> => {
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 || this.lyricsData[index].id === this.previousLyricId) {
return
}
const lyric = lyricsData[index]

const lyric = this.lyricsData[index]
const nextLyricText = this.lyricsData[index + 1]?.text ?? ''
const prevLyricText = this.lyricsData[index - 1]?.text ?? DEFAULT_TEXT
// Check if index is -1, if so, return early
if (index === -1 || !isCurrentLyric(lyric, this.player.audio.currentTime)) {
return
}

this.current.classOff('appear')
this.prev.classOff('appear')
this.next.classOff('appear')
const getLyricText = (index: number, defaultText: string): string =>
lyricsData[index]?.text ?? defaultText

setTimeout(() => {
this.prev.classOn('appear')
this.current.classOn('appear')
this.next.classOn('appear')
const nextLyricText = getLyricText(index + 1, '')
const prevLyricText = getLyricText(index - 1, DEFAULT_TEXT)

this.prev.text(prevLyricText)
this.current.text(lyric.text)
this.next.text(nextLyricText)
const updateText = (element: InstanceType<typeof HTML>, text: string): void => {
const trimmedText = text.trim()
if (element.getText() !== trimmedText) {
element.text(trimmedText === '' ? DEFAULT_TEXT : trimmedText)
}
}

this.previousLyricId = lyric.id
}, APPEAR_DELAY)
}
}
this.current.classOff('appear')
this.prev.classOff('appear')
this.next.classOff('appear')

export default Lyrics

this.current = new HTML('div')
this.next = new HTML('div')

this.init()
}

/**
* 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<LyricsResponse> {
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)
await new Promise(resolve => {
setTimeout(resolve, APPEAR_DELAY)
})

this.prev.appendTo(this.container)
this.current.appendTo(this.container)
this.next.appendTo(this.container)

// Register the events
this.registerEvents()
}
this.prev.classOn('appear')
this.current.classOn('appear')
this.next.classOn('appear')

/**
* 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.")
updateText(this.next, index + 1 !== lyricsData.length ? nextLyricText : 'ᶻ 𝗓 𐰁 .ᐟ')
updateText(this.current, lyric.text)
updateText(this.prev, prevLyricText)

this.prev.classOn('appear')
this.current.classOn('appear')
this.next.classOn('appear')
}
index++
previousLyricId = lyric.id

/**
* Register the events
*
* @private
* @memberof Lyrics
*/
private registerEvents(): void {
this.player.on('trackchange', () => {
this.handleLyrics().catch(console.error)
})
}
if (index !== lyricsData.length) {
return
}

/**
* Handle the lyrics
*
* @private
* @memberof Lyrics
*/
private async handleLyrics(): Promise<void> {
this.player.audio.currentTime = 0
const lyrics = await this.getLyrics(this.queue.currentTrack)
if (lyrics?.syncedLyrics == null) {
this.handleNoLyrics()
return
this.player.audio.ontimeupdate = null
}
this.lyricsData = lrcParser(lyrics.syncedLyrics).scripts as unknown as Lyric[]

this.lyricsData.forEach(lyric => {
lyric.id = uuid.v4()
})

this.previousLyricId = ''

this.prev.text('')
this.current.text(DEFAULT_TEXT)
this.next.text(this.lyricsData[0].text)

this.prev.classOn('appear')
this.current.classOn('appear')
this.next.classOn('appear')

this.player.audio.ontimeupdate = () => this.updateLyrics()
this.updateLyrics() // Initial call to set the first lyrics
}

/**
* Update the lyrics based on the current time of the audio
*
* @private
* @memberof Lyrics
*/
private updateLyrics(): void {
const currentTime = this.player.audio.currentTime

const index = this.lyricsData.findIndex(
lyric =>
currentTime >= (lyric.start ?? -Infinity) - LYRIC_OFFSET &&
currentTime <= lyric.end - LYRIC_OFFSET
)
requestAnimationFrame(() => { updateLyrics().catch(console.error) })

if (index === -1 || this.lyricsData[index].id === this.previousLyricId) {
return
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) })
}

const lyric = this.lyricsData[index]
const nextLyricText = this.lyricsData[index + 1]?.text ?? ''
const prevLyricText = this.lyricsData[index - 1]?.text ?? DEFAULT_TEXT

this.current.classOff('appear')
this.prev.classOff('appear')
this.next.classOff('appear')

setTimeout(() => {
this.prev.classOn('appear')
this.current.classOn('appear')
this.next.classOn('appear')

this.prev.text(prevLyricText)
this.current.text(lyric.text)
this.next.text(nextLyricText)

this.previousLyricId = lyric.id
}, APPEAR_DELAY)
}
}

Expand Down

0 comments on commit 7f5773b

Please sign in to comment.