import { getTrackTitle, getTrackArtists } from './utils.js'; export class LyricsManager { constructor(api) { this.api = api; this.currentLyrics = null; this.syncedLyrics = []; this.lyricsCache = new Map(); } async fetchLyrics(trackId) { if (this.lyricsCache.has(trackId)) { return this.lyricsCache.get(trackId); } try { const response = await this.api.fetchWithRetry(`/lyrics/?id=${trackId}`); const data = await response.json(); if (Array.isArray(data) && data.length > 0) { const lyricsData = data[0]; this.lyricsCache.set(trackId, lyricsData); return lyricsData; } return null; } catch (error) { console.error('Failed to fetch lyrics:', 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*(.+)/); if (match) { const [, minutes, seconds, centiseconds, text] = match; const timeInSeconds = parseInt(minutes) * 60 + parseInt(seconds) + parseInt(centiseconds) / 100; return { time: timeInSeconds, text: text.trim() }; } return null; }).filter(Boolean); } generateLRCContent(lyricsData, track) { if (!lyricsData || !lyricsData.subtitles) return null; const trackTitle = getTrackTitle(track); const trackArtist = getTrackArtists(track); let lrc = `[ti:${trackTitle}]\n`; lrc += `[ar:${trackArtist}]\n`; lrc += `[al:${track.album?.title || 'Unknown Album'}]\n`; lrc += `[by:${lyricsData.lyricsProvider || 'Unknown'}]\n`; lrc += '\n'; lrc += lyricsData.subtitles; return lrc; } downloadLRC(lyricsData, track) { const lrcContent = this.generateLRCContent(lyricsData, track); if (!lrcContent) { alert('No synced lyrics available for this track'); return; } const blob = new Blob([lrcContent], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${getTrackArtists(track)} - ${getTrackTitle(track)}.lrc`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } 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) { currentIndex = i; } else { break; } } return currentIndex; } } export function createLyricsPanel() { const panel = document.createElement('div'); panel.id = 'lyrics-panel'; panel.className = 'lyrics-panel hidden'; panel.innerHTML = `

Lyrics

Loading lyrics...
`; document.body.appendChild(panel); return panel; } export function showKaraokeView(track, lyricsData, audioPlayer) { const view = document.createElement('div'); view.id = 'karaoke-view'; view.className = 'karaoke-view'; const syncedLyrics = lyricsData.subtitles ? parseSyncedLyricsSimple(lyricsData.subtitles) : []; view.innerHTML = `
${getTrackTitle(track)}
${getTrackArtists(track)}
`; document.body.appendChild(view); const lyricsContainer = view.querySelector('#karaoke-lyrics'); syncedLyrics.forEach((line, index) => { const lineEl = document.createElement('div'); lineEl.className = 'karaoke-line'; lineEl.textContent = line.text; lineEl.dataset.index = index; lineEl.dataset.time = line.time; lyricsContainer.appendChild(lineEl); }); let updateInterval = setInterval(() => { const currentTime = audioPlayer.currentTime; const currentIndex = getCurrentLineIndex(syncedLyrics, currentTime); document.querySelectorAll('.karaoke-line').forEach((line, index) => { line.classList.toggle('active', index === currentIndex); line.classList.toggle('past', index < currentIndex); }); if (currentIndex >= 0) { const activeLine = lyricsContainer.children[currentIndex]; if (activeLine) { activeLine.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } }, 100); view.querySelector('#close-karaoke-btn').addEventListener('click', () => { clearInterval(updateInterval); view.remove(); }); return view; } function parseSyncedLyricsSimple(subtitles) { const lines = subtitles.split('\n').filter(line => line.trim()); return lines.map(line => { const match = line.match(/\[(\d+):(\d+)\.(\d+)\]\s*(.+)/); if (match) { const [, minutes, seconds, centiseconds, text] = match; const timeInSeconds = parseInt(minutes) * 60 + parseInt(seconds) + parseInt(centiseconds) / 100; return { time: timeInSeconds, text }; } return null; }).filter(Boolean); } function getCurrentLineIndex(syncedLyrics, currentTime) { let currentIndex = -1; for (let i = 0; i < syncedLyrics.length; i++) { if (currentTime >= syncedLyrics[i].time) { currentIndex = i; } else { break; } } return currentIndex; }