添加歌词跳转进度功能

This commit is contained in:
mtvpls
2026-05-26 22:14:16 +08:00
parent 633ee0bc9b
commit bbf705b1d3
2 changed files with 39 additions and 6 deletions
+21 -3
View File
@@ -2114,6 +2114,21 @@ export default function MusicClient({ children: _children }: { children?: React.
} }
}; };
const seekToLyric = (line: LyricLine, index: number) => {
const audio = audioRef.current;
if (!audio || !Number.isFinite(line.time)) return;
const maxSeekTime =
Number.isFinite(audio.duration) && audio.duration > 0
? Math.max(0, audio.duration - 0.25)
: line.time;
const nextTime = Math.max(0, Math.min(line.time, maxSeekTime));
audio.currentTime = nextTime;
setCurrentTime(nextTime);
setCurrentLyricIndex(index);
};
// 音量调节 // 音量调节
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newVolume = parseInt(e.target.value); const newVolume = parseInt(e.target.value);
@@ -2884,10 +2899,12 @@ export default function MusicClient({ children: _children }: { children?: React.
{lyrics.length > 0 ? ( {lyrics.length > 0 ? (
<div className="space-y-4 md:space-y-5"> <div className="space-y-4 md:space-y-5">
{lyrics.map((line, index) => ( {lyrics.map((line, index) => (
<div <button
key={index} key={index}
type="button"
data-index={index} data-index={index}
className={`text-center transition-all duration-300 ${ onClick={() => seekToLyric(line, index)}
className={`block w-full appearance-none border-0 bg-transparent p-0 text-center transition-all duration-300 outline-none ring-0 focus:outline-none focus:ring-0 active:outline-none active:ring-0 ${
index === currentLyricIndex index === currentLyricIndex
? 'text-white text-lg md:text-xl lg:text-2xl font-bold scale-110' ? 'text-white text-lg md:text-xl lg:text-2xl font-bold scale-110'
: index === currentLyricIndex - 1 || index === currentLyricIndex + 1 : index === currentLyricIndex - 1 || index === currentLyricIndex + 1
@@ -2907,7 +2924,7 @@ export default function MusicClient({ children: _children }: { children?: React.
{line.translation} {line.translation}
</div> </div>
)} )}
</div> </button>
))} ))}
</div> </div>
) : ( ) : (
@@ -3615,6 +3632,7 @@ export default function MusicClient({ children: _children }: { children?: React.
setPipMinimized(minimized); setPipMinimized(minimized);
localStorage.setItem('lyricsPiPMinimized', minimized.toString()); localStorage.setItem('lyricsPiPMinimized', minimized.toString());
}} }}
onLyricSeek={seekToLyric}
onClose={() => setShowPiPLyrics(false)} onClose={() => setShowPiPLyrics(false)}
/> />
)} )}
+18 -3
View File
@@ -29,6 +29,7 @@ interface LyricsPiPWindowProps {
minimized: boolean; minimized: boolean;
onOpacityChange: (opacity: number) => void; onOpacityChange: (opacity: number) => void;
onMinimizedChange: (minimized: boolean) => void; onMinimizedChange: (minimized: boolean) => void;
onLyricSeek?: (line: LyricLine, index: number) => void;
onClose: () => void; onClose: () => void;
} }
@@ -40,6 +41,7 @@ interface PiPLyricsContentProps {
minimized: boolean; minimized: boolean;
onOpacityChange: (opacity: number) => void; onOpacityChange: (opacity: number) => void;
onMinimizedChange: (minimized: boolean) => void; onMinimizedChange: (minimized: boolean) => void;
onLyricSeek?: (line: LyricLine, index: number) => void;
onClose: () => void; onClose: () => void;
} }
@@ -52,6 +54,7 @@ const PiPLyricsContent = ({
minimized, minimized,
onOpacityChange, onOpacityChange,
onMinimizedChange, onMinimizedChange,
onLyricSeek,
onClose, onClose,
}: PiPLyricsContentProps) => { }: PiPLyricsContentProps) => {
const lyricsContainerRef = useRef<HTMLDivElement>(null); const lyricsContainerRef = useRef<HTMLDivElement>(null);
@@ -213,9 +216,13 @@ const PiPLyricsContent = ({
> >
{lyrics.length > 0 ? ( {lyrics.length > 0 ? (
lyrics.map((line, index) => ( lyrics.map((line, index) => (
<div <button
key={index} key={index}
type="button"
onClick={() => onLyricSeek?.(line, index)}
style={{ style={{
display: 'block',
width: '100%',
padding: '8px 0', padding: '8px 0',
textAlign: 'center', textAlign: 'center',
fontSize: index === currentLyricIndex ? '16px' : '14px', fontSize: index === currentLyricIndex ? '16px' : '14px',
@@ -223,6 +230,11 @@ const PiPLyricsContent = ({
color: index === currentLyricIndex ? '#22c55e' : 'white', color: index === currentLyricIndex ? '#22c55e' : 'white',
transition: 'all 0.3s ease', transition: 'all 0.3s ease',
fontWeight: index === currentLyricIndex ? 'bold' : 'normal', fontWeight: index === currentLyricIndex ? 'bold' : 'normal',
background: 'transparent',
border: 'none',
outline: 'none',
appearance: 'none',
cursor: onLyricSeek ? 'pointer' : 'default',
}} }}
> >
<div>{line.text}</div> <div>{line.text}</div>
@@ -238,7 +250,7 @@ const PiPLyricsContent = ({
{line.translation} {line.translation}
</div> </div>
)} )}
</div> </button>
)) ))
) : ( ) : (
<div <div
@@ -294,6 +306,7 @@ export default function LyricsPiPWindow({
minimized, minimized,
onOpacityChange, onOpacityChange,
onMinimizedChange, onMinimizedChange,
onLyricSeek,
onClose, onClose,
}: LyricsPiPWindowProps) { }: LyricsPiPWindowProps) {
const pipWindowRef = useRef<Window | null>(null); const pipWindowRef = useRef<Window | null>(null);
@@ -327,6 +340,7 @@ export default function LyricsPiPWindow({
onMinimizedChange={(newMinimized) => { onMinimizedChange={(newMinimized) => {
window.postMessage({ type: 'PIP_MINIMIZED_CHANGE', minimized: newMinimized }, '*'); window.postMessage({ type: 'PIP_MINIMIZED_CHANGE', minimized: newMinimized }, '*');
}} }}
onLyricSeek={onLyricSeek}
onClose={() => { onClose={() => {
window.postMessage({ type: 'PIP_CLOSE' }, '*'); window.postMessage({ type: 'PIP_CLOSE' }, '*');
}} }}
@@ -350,13 +364,14 @@ export default function LyricsPiPWindow({
onMinimizedChange={(newMinimized) => { onMinimizedChange={(newMinimized) => {
window.postMessage({ type: 'PIP_MINIMIZED_CHANGE', minimized: newMinimized }, '*'); window.postMessage({ type: 'PIP_MINIMIZED_CHANGE', minimized: newMinimized }, '*');
}} }}
onLyricSeek={onLyricSeek}
onClose={() => { onClose={() => {
window.postMessage({ type: 'PIP_CLOSE' }, '*'); window.postMessage({ type: 'PIP_CLOSE' }, '*');
}} }}
/> />
); );
} }
}, [currentSong, lyrics, currentLyricIndex, opacity, minimized]); }, [currentSong, lyrics, currentLyricIndex, opacity, minimized, onLyricSeek]);
// 打开 PiP 窗口 // 打开 PiP 窗口
useEffect(() => { useEffect(() => {