diff --git a/js/lyrics.js b/js/lyrics.js index 53ed59b..785429b 100644 --- a/js/lyrics.js +++ b/js/lyrics.js @@ -133,6 +133,40 @@ export class LyricsManager { this.geniusManager = new GeniusManager(); this.isGeniusMode = false; this.currentGeniusData = null; + this.timingOffset = 0; // Offset in milliseconds (positive = delay lyrics, negative = advance lyrics) + } + + // Get timing offset for current track + getTimingOffset(trackId) { + try { + const key = `lyrics-offset-${trackId}`; + const stored = localStorage.getItem(key); + return stored ? parseInt(stored, 10) : 0; + } catch { + return 0; + } + } + + // Set timing offset for current track + setTimingOffset(trackId, offsetMs) { + try { + const key = `lyrics-offset-${trackId}`; + localStorage.setItem(key, offsetMs.toString()); + } catch (e) { + console.warn('Failed to save lyrics timing offset:', e); + } + } + + // Reset timing offset for current track + resetTimingOffset(trackId) { + this.setTimingOffset(trackId, 0); + } + + // Get formatted offset display string + getOffsetDisplayString(offsetMs) { + const sign = offsetMs >= 0 ? '+' : ''; + const seconds = Math.abs(offsetMs) / 1000; + return `${sign}${seconds.toFixed(1)}s`; } // Load Kuroshiro from CDN (npm package uses Node.js path which doesn't work in browser) @@ -715,15 +749,38 @@ export function openLyricsPanel(track, audioPlayer, lyricsManager, forceOpen = f }); } + // Load saved timing offset for this track + manager.timingOffset = manager.getTimingOffset(track.id); + const renderControls = (container) => { const isRomajiMode = manager.getRomajiMode(); manager.isRomajiMode = isRomajiMode; const isGeniusMode = manager.isGeniusMode; + const offsetDisplay = manager.getOffsetDisplayString(manager.timingOffset); container.innerHTML = ` +
+ + ${offsetDisplay} + + +