feat: optimize lyric index updates and improve auto-scrolling functionality in FullScreenPlayer
This commit is contained in:
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user