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