diff --git a/index.html b/index.html index ce7591c..12dff77 100644 --- a/index.html +++ b/index.html @@ -519,29 +519,36 @@
- - - - - -
-
+
+ + + + + +
+
+ +
+
+
diff --git a/js/app.js b/js/app.js index 06e453f..1deb23e 100644 --- a/js/app.js +++ b/js/app.js @@ -228,7 +228,7 @@ document.addEventListener('DOMContentLoaded', async () => { lyricsPanel.classList.toggle('hidden'); if (isHidden) { - await showSyncedLyricsPanel(player.currentTrack, audioPlayer, lyricsPanel); + await showSyncedLyricsPanel(player.currentTrack, audioPlayer, lyricsPanel, lyricsManager); } else { clearLyricsPanelSync(audioPlayer, lyricsPanel); } @@ -257,6 +257,23 @@ document.addEventListener('DOMContentLoaded', async () => { ui.closeFullscreenCover(); }); + document.getElementById('toggle-lyrics-btn')?.addEventListener('click', async (e) => { + e.stopPropagation(); + if (!player.currentTrack) { + alert('No track is currently playing'); + return; + } + + const isHidden = lyricsPanel.classList.contains('hidden'); + lyricsPanel.classList.toggle('hidden'); + + if (isHidden) { + await showSyncedLyricsPanel(player.currentTrack, audioPlayer, lyricsPanel, lyricsManager); + } else { + clearLyricsPanelSync(audioPlayer, lyricsPanel); + } + }); + document.getElementById('close-lyrics-btn')?.addEventListener('click', (e) => { e.stopPropagation(); lyricsPanel.classList.add('hidden'); @@ -308,12 +325,8 @@ document.addEventListener('DOMContentLoaded', async () => { // Update lyrics panel if it's open if (!lyricsPanel.classList.contains('hidden')) { - const mode = nowPlayingSettings.getMode(); - if (mode === 'lyrics') { - clearLyricsPanelSync(audioPlayer, lyricsPanel); - await showSyncedLyricsPanel(player.currentTrack, audioPlayer, lyricsPanel); - - } + clearLyricsPanelSync(audioPlayer, lyricsPanel); + await showSyncedLyricsPanel(player.currentTrack, audioPlayer, lyricsPanel, lyricsManager); } // Update Fullscreen/Enlarged Cover if it's open diff --git a/js/lyrics.js b/js/lyrics.js index 85fbd84..aa736a2 100644 --- a/js/lyrics.js +++ b/js/lyrics.js @@ -180,14 +180,24 @@ export function createLyricsPanel() { return panel; } -export async function showSyncedLyricsPanel(track, audioPlayer, panel) { +export async function showSyncedLyricsPanel(track, audioPlayer, panel, lyricsManager = null) { const content = panel.querySelector('.lyrics-content'); + + // If no manager provided, create a temp one (though caching won't persist across calls if this happens) + const manager = lyricsManager || new LyricsManager(); + + // Check if we are already displaying this track + if (panel.dataset.lastTrackId === String(track.id) && content.querySelector('am-lyrics')) { + // Just re-attach listeners + setupLyricsSync(track, audioPlayer, panel, manager, content.querySelector('am-lyrics')); + return; + } + + panel.dataset.lastTrackId = String(track.id); content.innerHTML = '
Loading lyrics...
'; - const lyricsManager = new LyricsManager(); - try { - await lyricsManager.ensureComponentLoaded(); + await manager.ensureComponentLoaded(); const title = track.title; const artist = getTrackArtists(track); @@ -203,6 +213,7 @@ export async function showSyncedLyricsPanel(track, audioPlayer, panel) { 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', ''); @@ -211,57 +222,9 @@ export async function showSyncedLyricsPanel(track, audioPlayer, panel) { amLyrics.style.width = '100%'; content.appendChild(amLyrics); - lyricsManager.amLyricsElement = amLyrics; + manager.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); - } - }; - - 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); - } - }; + setupLyricsSync(track, audioPlayer, panel, manager, amLyrics); } catch (error) { console.error('Failed to load lyrics:', error); @@ -269,6 +232,82 @@ export async function showSyncedLyricsPanel(track, audioPlayer, panel) { } } +function setupLyricsSync(track, audioPlayer, panel, lyricsManager, 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); + } + }; + + const onPlay = () => { + baseTimeMs = audioPlayer.currentTime * 1000; + lastTimestamp = performance.now(); + tick(); + }; + + const onPause = () => { + if (lyricsManager.animationFrameId) { + cancelAnimationFrame(lyricsManager.animationFrameId); + } + }; + + // Remove old listeners if any (though clearLyricsPanelSync handles this, + // we might be calling this from the "same track" branch where clear wasn't called? + // No, clearLyricsPanelSync IS called when hiding. + // But when SHOWING, we need to add them. + + audioPlayer.addEventListener('timeupdate', updateTime); + audioPlayer.addEventListener('play', onPlay); + audioPlayer.addEventListener('pause', onPause); + audioPlayer.addEventListener('seeked', updateTime); + + // Store handlers for removal + panel.lyricsUpdateHandler = updateTime; + panel.lyricsPlayHandler = onPlay; + panel.lyricsPauseHandler = onPause; + panel.lyricsSeekHandler = updateTime; + + // We also need to remove these in clearLyricsPanelSync! + // The current clearLyricsPanelSync only removes 'timeupdate'. + + 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); + } + // Also remove listeners + audioPlayer.removeEventListener('timeupdate', updateTime); + audioPlayer.removeEventListener('play', onPlay); + audioPlayer.removeEventListener('pause', onPause); + audioPlayer.removeEventListener('seeked', updateTime); + }; +} + + export function clearLyricsPanelSync(audioPlayer, panel) { if (panel.lyricsUpdateHandler) { audioPlayer.removeEventListener('timeupdate', panel.lyricsUpdateHandler); diff --git a/styles.css b/styles.css index bb03500..6010b84 100644 --- a/styles.css +++ b/styles.css @@ -1276,7 +1276,9 @@ input:checked + .slider::before { color: var(--muted-foreground); cursor: pointer; transition: all var(--transition); - padding: 0.5rem; + padding: 0.25rem; + width: 28px; + height: 28px; border-radius: var(--radius); display: flex; align-items: center; @@ -3205,3 +3207,31 @@ input:checked + .slider::before { } } + +/* Updated Volume Controls Layout */ +.volume-controls { + flex-direction: column !important; + align-items: flex-end !important; + gap: 0.5rem !important; + justify-content: center !important; +} + +.player-actions-row { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.volume-slider-row { + display: flex; + align-items: center; + gap: 0.75rem; +} + +/* Ensure buttons in rows behave correctly */ +.player-actions-row button, +.volume-slider-row button { + display: flex; + align-items: center; + justify-content: center; +} diff --git a/sw.js b/sw.js index a6fc714..00c279f 100644 --- a/sw.js +++ b/sw.js @@ -19,9 +19,12 @@ const urlsToCache = [ '/js/lastfm.js', '/js/lyrics.js', '/js/downloads.js', + '/js/db.js', + '/js/metadata.js', '/manifest.json', '/assets/logo.svg', - '/assets/appicon.png' + '/assets/appicon.png', + '/assets/96.png' ]; self.addEventListener('install', event => {