IMP: fullscreen lyrics, various css fixes

This commit is contained in:
Julien Maille 2026-01-01 23:50:06 +01:00
parent b828322a72
commit 801fa264cd
4 changed files with 63 additions and 236 deletions

View file

@ -65,8 +65,6 @@
</div>
</div>
</div>
<div class="fullscreen-lyrics-container" id="fullscreen-lyrics-container" style="display: none;">
</div>
</div>
</div>

View file

@ -193,125 +193,16 @@ export async function openLyricsPanel(track, audioPlayer, lyricsManager) {
};
const renderContent = async (container) => {
container.innerHTML = '<div class="lyrics-loading">Loading lyrics...</div>';
// Clean up any previous sync (though sidePanelManager might handle cleanup, we ensure it here)
clearLyricsPanelSync(audioPlayer, sidePanelManager.panel);
try {
await manager.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 || '';
container.innerHTML = '';
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%';
container.appendChild(amLyrics);
manager.amLyricsElement = amLyrics;
// Clean up any previous sync
clearLyricsPanelSync(audioPlayer, sidePanelManager.panel);
setupLyricsSync(track, audioPlayer, sidePanelManager.panel, manager, amLyrics);
} catch (error) {
console.error('Failed to load lyrics:', error);
container.innerHTML = '<div class="lyrics-error">Failed to load lyrics! :(</div>';
}
await renderLyricsComponent(container, track, audioPlayer, manager);
};
sidePanelManager.open('lyrics', 'Lyrics', renderControls, renderContent);
}
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);
}
};
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;
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);
}
audioPlayer.removeEventListener('timeupdate', updateTime);
audioPlayer.removeEventListener('play', onPlay);
audioPlayer.removeEventListener('pause', onPause);
audioPlayer.removeEventListener('seeked', updateTime);
};
}
export function clearLyricsPanelSync(audioPlayer, panel) {
if (panel && panel.lyricsCleanup) {
panel.lyricsCleanup();
panel.lyricsCleanup = null;
}
}
export async function renderLyricsInFullscreen(track, audioPlayer, lyricsManager, container) {
async function renderLyricsComponent(container, track, audioPlayer, lyricsManager) {
container.innerHTML = '<div class="lyrics-loading">Loading lyrics...</div>';
try {
@ -340,21 +231,25 @@ export async function renderLyricsInFullscreen(track, audioPlayer, lyricsManager
amLyrics.style.width = '100%';
container.appendChild(amLyrics);
setupFullscreenLyricsSync(track, audioPlayer, container, lyricsManager, amLyrics);
const cleanup = setupSync(track, audioPlayer, amLyrics);
// Attach cleanup to container for easy access
container.lyricsCleanup = cleanup;
return amLyrics;
} catch (error) {
console.error('Failed to load lyrics in fullscreen:', error);
console.error('Failed to load lyrics:', error);
container.innerHTML = '<div class="lyrics-error">Failed to load lyrics</div>';
return null;
}
}
function setupFullscreenLyricsSync(track, audioPlayer, container, lyricsManager, amLyrics) {
function setupSync(track, audioPlayer, amLyrics) {
let baseTimeMs = 0;
let lastTimestamp = performance.now();
let animationFrameId = null;
const updateTime = () => {
const currentMs = audioPlayer.currentTime * 1000;
baseTimeMs = currentMs;
@ -368,7 +263,7 @@ function setupFullscreenLyricsSync(track, audioPlayer, container, lyricsManager,
const elapsed = now - lastTimestamp;
const nextMs = baseTimeMs + elapsed;
amLyrics.currentTime = nextMs;
lyricsManager.fullscreenAnimationFrameId = requestAnimationFrame(tick);
animationFrameId = requestAnimationFrame(tick);
}
};
@ -379,9 +274,16 @@ function setupFullscreenLyricsSync(track, audioPlayer, container, lyricsManager,
};
const onPause = () => {
if (lyricsManager.fullscreenAnimationFrameId) {
cancelAnimationFrame(lyricsManager.fullscreenAnimationFrameId);
lyricsManager.fullscreenAnimationFrameId = null;
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
};
const onLineClick = (e) => {
if (e.detail && e.detail.timestamp) {
audioPlayer.currentTime = e.detail.timestamp / 1000;
audioPlayer.play();
}
};
@ -389,33 +291,26 @@ function setupFullscreenLyricsSync(track, audioPlayer, container, lyricsManager,
audioPlayer.addEventListener('play', onPlay);
audioPlayer.addEventListener('pause', onPause);
audioPlayer.addEventListener('seeked', updateTime);
container.lyricsUpdateHandler = updateTime;
container.lyricsPlayHandler = onPlay;
container.lyricsPauseHandler = onPause;
container.lyricsSeekHandler = updateTime;
container.lyricsCleanup = () => {
if (lyricsManager.fullscreenAnimationFrameId) {
cancelAnimationFrame(lyricsManager.fullscreenAnimationFrameId);
lyricsManager.fullscreenAnimationFrameId = null;
amLyrics.addEventListener('line-click', onLineClick);
if (!audioPlayer.paused) {
tick();
}
return () => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
audioPlayer.removeEventListener('timeupdate', updateTime);
audioPlayer.removeEventListener('play', onPlay);
audioPlayer.removeEventListener('pause', onPause);
audioPlayer.removeEventListener('seeked', updateTime);
amLyrics.removeEventListener('line-click', onLineClick);
};
}
amLyrics.addEventListener('line-click', (e) => {
if (e.detail && e.detail.timestamp) {
audioPlayer.currentTime = e.detail.timestamp / 1000;
audioPlayer.play();
}
});
if (!audioPlayer.paused) {
tick();
}
export async function renderLyricsInFullscreen(track, audioPlayer, lyricsManager, container) {
return renderLyricsComponent(container, track, audioPlayer, lyricsManager);
}
export function clearFullscreenLyricsSync(container) {
@ -424,3 +319,10 @@ export function clearFullscreenLyricsSync(container) {
container.lyricsCleanup = null;
}
}
export function clearLyricsPanelSync(audioPlayer, panel) {
if (panel && panel.lyricsCleanup) {
panel.lyricsCleanup();
panel.lyricsCleanup = null;
}
}

View file

@ -1,6 +1,6 @@
//js/ui.js
import { SVG_PLAY, SVG_DOWNLOAD, SVG_MENU, SVG_HEART, formatTime, createPlaceholder, trackDataStore, hasExplicitContent, getTrackArtists, getTrackTitle, calculateTotalDuration, formatDuration } from './utils.js';
import { renderLyricsInFullscreen, clearFullscreenLyricsSync } from './lyrics.js';
import { openLyricsPanel } from './lyrics.js';
import { recentActivityManager, backgroundSettings, trackListSettings } from './storage.js';
import { db } from './db.js';
@ -491,25 +491,11 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
if (lyricsManager && audioPlayer) {
lyricsToggleBtn.style.display = 'flex';
lyricsContainer.style.display = 'none';
lyricsContainer.classList.remove('active');
lyricsToggleBtn.classList.remove('active');
const toggleLyrics = async () => {
const isActive = lyricsContainer.classList.contains('active');
if (isActive) {
lyricsContainer.classList.remove('active');
lyricsToggleBtn.classList.remove('active');
setTimeout(() => {
lyricsContainer.style.display = 'none';
clearFullscreenLyricsSync(lyricsContainer);
}, 300);
} else {
lyricsContainer.style.display = 'block';
setTimeout(() => lyricsContainer.classList.add('active'), 10);
lyricsToggleBtn.classList.add('active');
await renderLyricsInFullscreen(track, audioPlayer, lyricsManager, lyricsContainer);
}
const toggleLyrics = () => {
openLyricsPanel(track, audioPlayer, lyricsManager);
lyricsToggleBtn.classList.toggle('active');
};
const newToggleBtn = lyricsToggleBtn.cloneNode(true);
@ -524,13 +510,6 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
closeFullscreenCover() {
const overlay = document.getElementById('fullscreen-cover-overlay');
const lyricsContainer = document.getElementById('fullscreen-lyrics-container');
clearFullscreenLyricsSync(lyricsContainer);
lyricsContainer.style.display = 'none';
lyricsContainer.classList.remove('active');
lyricsContainer.innderHTML = '';
overlay.style.display = 'none';
}
@ -1198,7 +1177,8 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
// Set play button action
playBtn.onclick = () => {
player.playTracks(tracks, 0);
this.player.setQueue(tracks, 0);
this.player.playTrackFromQueue();
};
document.title = `${displayTitle} - Monochrome`;

View file

@ -1,5 +1,6 @@
:root {
color-scheme: light dark;
--spacing-xs: 0.4rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
@ -16,6 +17,7 @@
}
:root[data-theme="monochrome"] {
color-scheme: dark;
--background: #000;
--foreground: #fafafa;
--card: #111;
@ -37,6 +39,7 @@
}
:root[data-theme="dark"] {
color-scheme: dark;
--background: #0a0a0a;
--foreground: #ededed;
--card: #1a1a1a;
@ -57,6 +60,7 @@
}
:root[data-theme="ocean"] {
color-scheme: dark;
--background: #0c1821;
--foreground: #e0f4ff;
--card: #1b2838;
@ -77,6 +81,7 @@
}
:root[data-theme="purple"] {
color-scheme: dark;
--background: #0f0514;
--foreground: #f3e8ff;
--card: #1e0a2e;
@ -97,6 +102,7 @@
}
:root[data-theme="forest"] {
color-scheme: dark;
--background: #0a1409;
--foreground: #e8f5e9;
--card: #1a2e1a;
@ -117,6 +123,7 @@
}
:root[data-theme="mocha"] {
color-scheme: dark;
--background: #1e1e2e;
--foreground: #cdd6f4;
--card: #313244;
@ -137,6 +144,7 @@
}
:root[data-theme="machiatto"] {
color-scheme: dark;
--background: #24273a;
--foreground: #cad3f5;
--card: #363a4f;
@ -157,6 +165,7 @@
}
:root[data-theme="frappe"] {
color-scheme: dark;
--background: #303446;
--foreground:#c6d0f5;
--card: #414559;
@ -177,6 +186,7 @@
}
:root[data-theme="latte"] {
color-scheme: light;
--background: #eff1f5;
--foreground: #4c4f69;
--card: #ccd0da;
@ -197,6 +207,7 @@
}
:root[data-theme="light"] {
color-scheme: light;
--background: #ffffff;
--foreground: #000000;
--card: #f4f4f5;
@ -2893,7 +2904,7 @@ input:checked + .slider::before {
max-width: 100vw;
background: var(--card);
border-left: 1px solid var(--border);
z-index: 2000;
z-index: 2050;
display: flex;
flex-direction: column;
transform: translateX(100%);
@ -3266,54 +3277,6 @@ img:not([src]), img[src=''] {
transition: flex 0.3s ease;
}
.fullscreen-lyrics-container {
position: absolute;
right: 0;
top: 0;
width: 0;
height: 100%;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(20px);
border-left: 1px solid rgba(255, 255, 255, 0.1);
overflow-y: auto;
overflow-x: hidden;
transition: width 0.3s ease;
padding: 0;
}
.fullscreen-lyrics-container.active {
width: 400px;
padding: 2rem;
}
.fullscreen-lyrics-container > * {
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
padding: 2rem;
box-sizing: border-box;
}
.fullscreen-lyrics-container::-webkit-scrollbar {
width: 8px;
}
.fullscreen-lyrics-container::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
}
.fullscreen-lyrics-container::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
}
.fullscreen-lyrics-container::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
.fullscreen-lyrics-toggle {
position: absolute;
top: 1rem;
@ -3345,22 +3308,6 @@ img:not([src]), img[src=''] {
flex-direction: column;
}
.fullscreen-lyrics-container {
top: auto;
bottom: 0;
left: 0;
width: 100%;
height: 0;
border-left: none;
border-top: 1px solid rgba(255, 255, 255, 0.1);
transition: height 0.3s ease;
}
.fullscreen-lyrics-container.active {
width: 50vh;
width: 100%;
}
.fullscreen-lyrics-toggle {
right: 3.5rem;
}