添加歌词跳转进度功能
This commit is contained in:
@@ -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)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user