From f227c4c00ddba11c4157447e846bb6e4bb5416e1 Mon Sep 17 00:00:00 2001 From: Samidy Date: Sat, 27 Dec 2025 21:21:35 +0300 Subject: [PATCH] Lyrics Update --- js/app.js | 63 ++++++------- js/downloads.js | 6 +- js/lyrics.js | 233 +++++++++++++++++++++++++++++++++--------------- 3 files changed, 193 insertions(+), 109 deletions(-) diff --git a/js/app.js b/js/app.js index 2b48bd4..06e453f 100644 --- a/js/app.js +++ b/js/app.js @@ -221,35 +221,18 @@ document.addEventListener('DOMContentLoaded', async () => { const mode = nowPlayingSettings.getMode(); if (mode === 'karaoke') { - lyricsPanel.classList.add('hidden'); - clearLyricsPanelSync(audioPlayer, lyricsPanel); + alert('We have updated how lyrics work, with this, we have unfortunately disabled karaoke for the time being.') - const lyricsData = await lyricsManager.fetchLyrics(player.currentTrack.id); - if (lyricsData) { - showKaraokeView(player.currentTrack, lyricsData, audioPlayer); - } else { - alert('No lyrics available for this track'); - } } else if (mode === 'lyrics') { const isHidden = lyricsPanel.classList.contains('hidden'); lyricsPanel.classList.toggle('hidden'); if (isHidden) { - const content = lyricsPanel.querySelector('.lyrics-content'); - content.innerHTML = '
Loading lyrics...
'; - - const lyricsData = await lyricsManager.fetchLyrics(player.currentTrack.id); - - if (lyricsData) { - lyricsManager.currentLyrics = lyricsData; - showSyncedLyricsPanel(lyricsData, audioPlayer, lyricsPanel); - } else { - content.innerHTML = '
Failed to load lyrics
'; - } + await showSyncedLyricsPanel(player.currentTrack, audioPlayer, lyricsPanel); } else { - // Clear sync when hiding clearLyricsPanelSync(audioPlayer, lyricsPanel); } + } else if (mode === 'cover') { const overlay = document.getElementById('fullscreen-cover-overlay'); if (overlay && overlay.style.display === 'flex') { @@ -280,13 +263,31 @@ document.addEventListener('DOMContentLoaded', async () => { clearLyricsPanelSync(audioPlayer, lyricsPanel); }); - document.getElementById('download-lrc-btn')?.addEventListener('click', (e) => { + document.getElementById('download-lrc-btn')?.addEventListener('click', async (e) => { e.stopPropagation(); - if (lyricsManager.currentLyrics && player.currentTrack) { - lyricsManager.downloadLRC(lyricsManager.currentLyrics, player.currentTrack); + if (!player.currentTrack) { + alert('No track is currently playing'); + return; + } + + const btn = e.target.closest('#download-lrc-btn'); + btn.disabled = true; + + try { + const lyricsData = await lyricsManager.fetchLyrics(player.currentTrack.id, player.currentTrack); + + if (lyricsData) { + lyricsManager.downloadLRC(lyricsData, player.currentTrack); + } else { + alert('No synced lyrics available for download'); + } + } catch (error) { + console.error('Failed to download lyrics!', error); + alert('Failed to Download Lyrics! check the console for more information.') + } finally { + btn.disabled = false; } }); - document.getElementById('download-current-btn')?.addEventListener('click', () => { if (player.currentTrack) { handleTrackAction('download', player.currentTrack, player, api, lyricsManager, 'track', ui); @@ -309,19 +310,9 @@ document.addEventListener('DOMContentLoaded', async () => { if (!lyricsPanel.classList.contains('hidden')) { const mode = nowPlayingSettings.getMode(); if (mode === 'lyrics') { - const content = lyricsPanel.querySelector('.lyrics-content'); - content.innerHTML = '
Loading lyrics...
'; + clearLyricsPanelSync(audioPlayer, lyricsPanel); + await showSyncedLyricsPanel(player.currentTrack, audioPlayer, lyricsPanel); - const lyricsData = await lyricsManager.fetchLyrics(player.currentTrack.id); - - if (lyricsData) { - lyricsManager.currentLyrics = lyricsData; - // Clear old sync before showing new - clearLyricsPanelSync(audioPlayer, lyricsPanel); - showSyncedLyricsPanel(lyricsData, audioPlayer, lyricsPanel); - } else { - content.innerHTML = '
No lyrics available for this track
'; - } } } diff --git a/js/downloads.js b/js/downloads.js index 9747888..ed62ac3 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -282,7 +282,7 @@ async function downloadTracksToZip(zip, tracks, folderName, api, quality, lyrics if (lyricsManager && lyricsSettings.shouldDownloadLyrics()) { try { - const lyricsData = await lyricsManager.fetchLyrics(track.id); + const lyricsData = await lyricsManager.fetchLyrics(track.id, track); if (lyricsData) { const lrcContent = lyricsManager.generateLRCContent(lyricsData, track); if (lrcContent) { @@ -399,7 +399,7 @@ export async function downloadDiscography(artist, api, quality, lyricsManager = if (lyricsManager && lyricsSettings.shouldDownloadLyrics()) { try { - const lyricsData = await lyricsManager.fetchLyrics(track.id); + const lyricsData = await lyricsManager.fetchLyrics(track.id, track); if (lyricsData) { const lrcContent = lyricsManager.generateLRCContent(lyricsData, track); if (lrcContent) { @@ -521,7 +521,7 @@ export async function downloadTrackWithMetadata(track, quality, api, lyricsManag if (lyricsManager && lyricsSettings.shouldDownloadLyrics()) { try { - const lyricsData = await lyricsManager.fetchLyrics(track.id); + const lyricsData = await lyricsManager.fetchLyrics(track.id, track); if (lyricsData) { lyricsManager.downloadLRC(lyricsData, track); } diff --git a/js/lyrics.js b/js/lyrics.js index f4c297f..85fbd84 100644 --- a/js/lyrics.js +++ b/js/lyrics.js @@ -7,33 +7,95 @@ export class LyricsManager { this.currentLyrics = null; this.syncedLyrics = []; this.lyricsCache = new Map(); + this.componentLoaded = false; + this.amLyricsElement = null; + this.animationFrameId = null; } - async fetchLyrics(trackId) { - if (this.lyricsCache.has(trackId)) { - return this.lyricsCache.get(trackId); + async ensureComponentLoaded() { + if (this.componentLoaded) return; + + if (typeof customElements !== 'undefined' && customElements.get('am-lyrics')) { + this.componentLoaded = true; + return; } - try { - const response = await this.api.fetchWithRetry(`/lyrics/?id=${trackId}`); - const data = await response.json(); + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.type = 'module'; + script.src = 'https://cdn.jsdelivr.net/npm/@uimaxbai/am-lyrics@0.5.4/dist/src/am-lyrics.min.js'; + + script.onload = () => { + if (typeof customElements !== 'undefined') { + customElements.whenDefined('am-lyrics') + .then(() => { + this.componentLoaded = true; + resolve(); + }) + .catch(reject); + } else { + resolve(); + } + }; + + script.onerror = () => reject(new Error('Failed to load lyrics component')); + document.head.appendChild(script); + }); + } - if (Array.isArray(data) && data.length > 0) { - const lyricsData = data[0]; - this.lyricsCache.set(trackId, lyricsData); - return lyricsData; + async fetchLyrics(trackId, track = null) { + // LRCLIB + if (track) { + if (this.lyricsCache.has(trackId)) { + return this.lyricsCache.get(trackId); } - return null; - } catch (error) { - console.error('Failed to fetch lyrics:', error); - return null; + try { + const artist = Array.isArray(track.artists) + ? track.artists.map(a => a.name || a).join(', ') + : track.artist?.name || ''; + const title = track.title || ''; + const album = track.album?.title || ''; + const duration = track.duration ? Math.round(track.duration) : null; + + if (!title || !artist) { + console.warn('Missing required fields for LRCLIB'); + return null; + } + + const params = new URLSearchParams({ + track_name: title, + artist_name: artist + }); + + if (album) params.append('album_name', album); + if (duration) params.append('duration', duration.toString()); + + const response = await fetch(`https://lrclib.net/api/get?${params.toString()}`); + + if (response.ok) { + const data = await response.json(); + + if (data.syncedLyrics) { + const lyricsData = { + subtitles: data.syncedLyrics, + lyricsProvider: 'LRCLIB' + }; + + this.lyricsCache.set(trackId, lyricsData); + return lyricsData; + } + } + } catch (error) { + console.warn('LRCLIB fetch failed:', error); + } } + + return null; } parseSyncedLyrics(subtitles) { if (!subtitles) return []; - const lines = subtitles.split('\n').filter(line => line.trim()); return lines.map(line => { const match = line.match(/\[(\d+):(\d+)\.(\d+)\]\s*(.+)/); @@ -82,7 +144,6 @@ export class LyricsManager { getCurrentLine(currentTime) { if (!this.syncedLyrics || this.syncedLyrics.length === 0) return -1; - let currentIndex = -1; for (let i = 0; i < this.syncedLyrics.length; i++) { if (currentTime >= this.syncedLyrics[i].time) { @@ -119,64 +180,92 @@ export function createLyricsPanel() { return panel; } -export function showSyncedLyricsPanel(lyricsData, audioPlayer, panel) { +export async function showSyncedLyricsPanel(track, audioPlayer, panel) { const content = panel.querySelector('.lyrics-content'); - - const syncedLyrics = lyricsData.subtitles - ? parseSyncedLyricsSimple(lyricsData.subtitles) - : null; - - if (syncedLyrics && syncedLyrics.length > 0) { - // Render synced lyrics + content.innerHTML = '
Loading lyrics...
'; + + const lyricsManager = new LyricsManager(); + + try { + await lyricsManager.ensureComponentLoaded(); + + const title = track.title; + const artist = getTrackArtists(track); + const album = track.album?.title; + const durationMs = track.duration ? Math.round(track.duration * 1000) : undefined; + const isrc = track.isrc || ''; + content.innerHTML = ''; - syncedLyrics.forEach((line, index) => { - const lineEl = document.createElement('p'); - lineEl.className = 'lyrics-line synced-line'; - lineEl.textContent = line.text || '♪'; - lineEl.dataset.index = index; - lineEl.dataset.time = line.time; - content.appendChild(lineEl); - }); - - let currentLineIndex = -1; - - const updateLyrics = () => { - const currentTime = audioPlayer.currentTime; - const newIndex = getCurrentLineIndex(syncedLyrics, currentTime); - - if (newIndex !== currentLineIndex) { - currentLineIndex = newIndex; - - content.querySelectorAll('.synced-line').forEach((line, index) => { - line.classList.remove('active', 'upcoming', 'past'); - - if (index === currentLineIndex) { - line.classList.add('active'); - // Smooth scroll to active line - line.scrollIntoView({ behavior: 'smooth', block: 'center' }); - } else if (index === currentLineIndex + 1) { - line.classList.add('upcoming'); - } else if (index < currentLineIndex) { - line.classList.add('past'); - } - }); + const amLyrics = document.createElement('am-lyrics'); + amLyrics.setAttribute('song-title', title); + amLyrics.setAttribute('song-artist', artist); + if (album) amLyrics.setAttribute('song-album', album); + if (durationMs) amLyrics.setAttribute('song-duration', durationMs); + amLyrics.setAttribute('query', `${title} ${artist}`.trim()); + if (isrc) amLyrics.setAttribute('isrc', isrc); + amLyrics.setAttribute('highlight-color', '#93c5fd'); + amLyrics.setAttribute('hover-background-color', 'rgba(59, 130, 246, 0.14)'); + amLyrics.setAttribute('autoscroll', ''); + amLyrics.setAttribute('interpolate', ''); + amLyrics.style.height = '100%'; + amLyrics.style.width = '100%'; + + content.appendChild(amLyrics); + lyricsManager.amLyricsElement = amLyrics; + + let baseTimeMs = 0; + let lastTimestamp = performance.now(); + + const updateTime = () => { + const currentMs = audioPlayer.currentTime * 1000; + baseTimeMs = currentMs; + lastTimestamp = performance.now(); + amLyrics.currentTime = currentMs; + }; + + const tick = () => { + if (!audioPlayer.paused) { + const now = performance.now(); + const elapsed = now - lastTimestamp; + const nextMs = baseTimeMs + elapsed; + amLyrics.currentTime = nextMs; + lyricsManager.animationFrameId = requestAnimationFrame(tick); } }; - - // Store the update function so we can remove it later - panel.lyricsUpdateHandler = updateLyrics; - audioPlayer.addEventListener('timeupdate', updateLyrics); - - // Initial update - updateLyrics(); - } else if (lyricsData.lyrics) { - // Fallback to static lyrics - const lines = lyricsData.lyrics.split('\n'); - content.innerHTML = lines.map(line => - `

${line || ' '}

` - ).join(''); - } else { - content.innerHTML = '
No lyrics available
'; + + audioPlayer.addEventListener('timeupdate', updateTime); + audioPlayer.addEventListener('play', () => { + baseTimeMs = audioPlayer.currentTime * 1000; + lastTimestamp = performance.now(); + tick(); + }); + audioPlayer.addEventListener('pause', () => { + if (lyricsManager.animationFrameId) { + cancelAnimationFrame(lyricsManager.animationFrameId); + } + }); + audioPlayer.addEventListener('seeked', updateTime); + + amLyrics.addEventListener('line-click', (e) => { + if (e.detail && e.detail.timestamp) { + audioPlayer.currentTime = e.detail.timestamp / 1000; + audioPlayer.play(); + } + }); + + if (!audioPlayer.paused) { + tick(); + } + + panel.lyricsCleanup = () => { + if (lyricsManager.animationFrameId) { + cancelAnimationFrame(lyricsManager.animationFrameId); + } + }; + + } catch (error) { + console.error('Failed to load lyrics:', error); + content.innerHTML = '
Failed to load lyrics! :(
'; } } @@ -185,6 +274,10 @@ export function clearLyricsPanelSync(audioPlayer, panel) { audioPlayer.removeEventListener('timeupdate', panel.lyricsUpdateHandler); panel.lyricsUpdateHandler = null; } + if (panel.lyricsCleanup) { + panel.lyricsCleanup(); + panel.lyricsCleanup = null; + } } export function showKaraokeView(track, lyricsData, audioPlayer) {