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) {