feat: optimize lyric index updates and improve auto-scrolling functionality in FullScreenPlayer

This commit is contained in:
2025-06-19 21:02:26 +00:00
committed by GitHub
parent 6ccb14f362
commit 8404f5e499

View File

@@ -75,80 +75,101 @@ export const FullScreenPlayer: React.FC<FullScreenPlayerProps> = ({ isOpen, onCl
loadLyrics(); loadLyrics();
}, [currentTrack]); }, [currentTrack]);
// Update current lyric index based on time // Update current lyric index based on time (with optimization to prevent unnecessary updates)
useEffect(() => { useEffect(() => {
const newIndex = lrcLibClient.getCurrentLyricIndex(lyrics, currentTime); const newIndex = lrcLibClient.getCurrentLyricIndex(lyrics, currentTime);
setCurrentLyricIndex(newIndex); if (newIndex !== currentLyricIndex) {
}, [lyrics, currentTime]); setCurrentLyricIndex(newIndex);
}
}, [lyrics, currentTime, currentLyricIndex]);
// Auto-scroll lyrics to center current line without cutting off text // Auto-scroll lyrics using lyricsRef
useEffect(() => { useEffect(() => {
if (currentLyricIndex >= 0 && lyrics.length > 0 && showLyrics) { if (currentLyricIndex >= 0 && lyrics.length > 0 && showLyrics && lyricsRef.current) {
// Use a small delay to ensure the DOM is updated
const scrollTimeout = setTimeout(() => { const scrollTimeout = setTimeout(() => {
const lyricsScrollArea = document.querySelector('[data-radix-scroll-area-viewport]'); // Find the ScrollArea viewport
if (lyricsScrollArea) { const scrollViewport = lyricsRef.current?.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement;
const currentLyricElement = lyricsScrollArea.querySelector(`[data-lyric-index="${currentLyricIndex}"]`) as HTMLElement; const currentLyricElement = lyricsRef.current?.querySelector(`[data-lyric-index="${currentLyricIndex}"]`) as HTMLElement;
if (currentLyricElement) {
const containerHeight = lyricsScrollArea.clientHeight; if (scrollViewport && currentLyricElement) {
const elementHeight = currentLyricElement.offsetHeight; const containerHeight = scrollViewport.clientHeight;
const elementOffsetTop = currentLyricElement.offsetTop; const elementTop = currentLyricElement.offsetTop;
const elementHeight = currentLyricElement.offsetHeight;
// Calculate scroll position to center the current lyric
const targetScrollTop = elementOffsetTop - (containerHeight / 2) + (elementHeight / 2); // Calculate scroll position to center the current lyric
const targetScrollTop = elementTop - (containerHeight / 2) + (elementHeight / 2);
lyricsScrollArea.scrollTo({
top: Math.max(0, targetScrollTop), scrollViewport.scrollTo({
behavior: 'smooth' top: Math.max(0, targetScrollTop),
}); behavior: 'smooth'
} });
} }
}, 50); }, 100);
return () => clearTimeout(scrollTimeout); return () => clearTimeout(scrollTimeout);
} }
}, [currentLyricIndex, lyrics.length, showLyrics]); }, [currentLyricIndex, showLyrics]);
// Reset lyrics to top when song ends or changes // Reset lyrics to top when song changes
useEffect(() => { useEffect(() => {
if (currentTrack && showLyrics) { if (currentTrack && showLyrics && lyricsRef.current) {
const lyricsScrollArea = document.querySelector('[data-radix-scroll-area-viewport]'); // Reset scroll position using lyricsRef
if (lyricsScrollArea) { const resetScroll = () => {
lyricsScrollArea.scrollTo({ const scrollViewport = lyricsRef.current?.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement;
top: 0,
behavior: 'smooth' if (scrollViewport) {
}); scrollViewport.scrollTo({
} top: 0,
setCurrentLyricIndex(-1); behavior: 'instant' // Use instant for track changes
});
}
};
// Small delay to ensure DOM is ready
const resetTimeout = setTimeout(() => {
resetScroll();
setCurrentLyricIndex(-1);
}, 50);
return () => clearTimeout(resetTimeout);
} }
}, [currentTrack, showLyrics]); }, [currentTrack?.id, showLyrics]); // Only reset when track ID changes
// Sync with main audio player // Sync with main audio player (throttled to prevent infinite loops)
useEffect(() => { useEffect(() => {
let lastUpdate = 0;
const throttleMs = 200; // Update at most every 200ms
const syncWithMainPlayer = () => { const syncWithMainPlayer = () => {
const now = Date.now();
if (now - lastUpdate < throttleMs) return;
lastUpdate = now;
const mainAudio = document.querySelector('audio') as HTMLAudioElement; const mainAudio = document.querySelector('audio') as HTMLAudioElement;
if (mainAudio && currentTrack) { if (mainAudio && currentTrack) {
const newCurrentTime = mainAudio.currentTime; const newCurrentTime = mainAudio.currentTime;
const newDuration = mainAudio.duration || 0; const newDuration = mainAudio.duration || 0;
const newIsPlaying = !mainAudio.paused; const newIsPlaying = !mainAudio.paused;
// Check if song ended (reset lyrics to top) // Only update state if values have changed significantly
if (newCurrentTime === 0 && !newIsPlaying && currentTime > 0) { if (Math.abs(newCurrentTime - currentTime) > 0.5) {
const lyricsScrollArea = document.querySelector('[data-radix-scroll-area-viewport]'); setCurrentTime(newCurrentTime);
if (lyricsScrollArea) { }
lyricsScrollArea.scrollTo({ if (Math.abs(newDuration - duration) > 0.1) {
top: 0, setDuration(newDuration);
behavior: 'smooth' }
}); if (newDuration > 0) {
} const newProgress = (newCurrentTime / newDuration) * 100;
setCurrentLyricIndex(-1); if (Math.abs(newProgress - progress) > 0.1) {
setProgress(newProgress);
}
}
if (newIsPlaying !== isPlaying) {
setIsPlaying(newIsPlaying);
}
if (Math.abs(mainAudio.volume - volume) > 0.01) {
setVolume(mainAudio.volume);
} }
setCurrentTime(newCurrentTime);
setDuration(newDuration);
setProgress(newDuration ? (newCurrentTime / newDuration) * 100 : 0);
setIsPlaying(newIsPlaying);
setVolume(mainAudio.volume);
} }
}; };
@@ -160,7 +181,7 @@ export const FullScreenPlayer: React.FC<FullScreenPlayerProps> = ({ isOpen, onCl
const interval = setInterval(syncWithMainPlayer, 100); const interval = setInterval(syncWithMainPlayer, 100);
return () => clearInterval(interval); return () => clearInterval(interval);
} }
}, [isOpen, currentTrack, currentTime]); }, [isOpen, currentTrack]); // Removed currentTime from dependencies to prevent loop
// Extract dominant color from cover art // Extract dominant color from cover art
useEffect(() => { useEffect(() => {
@@ -372,7 +393,7 @@ export const FullScreenPlayer: React.FC<FullScreenPlayerProps> = ({ isOpen, onCl
{/* Right Side - Lyrics */} {/* Right Side - Lyrics */}
{showLyrics && lyrics.length > 0 && ( {showLyrics && lyrics.length > 0 && (
<div className="flex-1 lg:max-w-md min-h-0"> <div className="flex-1 lg:max-w-md min-h-0" ref={lyricsRef}>
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<ScrollArea className="flex-1 min-h-0"> <ScrollArea className="flex-1 min-h-0">
<div className="space-y-4 pr-4 px-2"> <div className="space-y-4 pr-4 px-2">