提升音乐解析超时时间,增加明显的缓冲中标识

This commit is contained in:
mtvpls
2026-05-25 23:33:17 +08:00
parent 33fac890bf
commit 6303d095d1
3 changed files with 158 additions and 8 deletions
+2 -2
View File
@@ -156,7 +156,7 @@ export async function GET(request: NextRequest) {
try {
upstream = await fetch(upstreamUrl, {
headers: upstreamHeaders,
signal: AbortSignal.timeout(30000),
signal: AbortSignal.timeout(90000),
cache: 'no-store',
});
} catch (error) {
@@ -165,7 +165,7 @@ export async function GET(request: NextRequest) {
upstreamHeaders = buildUpstreamHeaders(request, upstreamUrl);
upstream = await fetch(upstreamUrl, {
headers: upstreamHeaders,
signal: AbortSignal.timeout(30000),
signal: AbortSignal.timeout(90000),
cache: 'no-store',
});
console.warn('[music-v2/stream] upstream fetch failed once, retried with refreshed URL', {
+155 -5
View File
@@ -285,6 +285,7 @@ export default function MusicPage() {
const [loadingMoreSearch, setLoadingMoreSearch] = useState(false);
const [currentSong, setCurrentSong] = useState<Song | null>(null);
const [isPlaying, setIsPlaying] = useState(false);
const [isBuffering, setIsBuffering] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const [volume, setVolume] = useState(100);
@@ -660,6 +661,7 @@ export default function MusicPage() {
setCurrentSongUrl(streamUrl);
if (audioRef.current) {
setIsBuffering(true);
audioRef.current.src = streamUrl;
audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true });
audioRef.current.load();
@@ -679,6 +681,7 @@ export default function MusicPage() {
const data = await fetchPlayData(latestDbSong, platform, selectedQuality, true);
if (data.success && data.data?.play?.directUrl && audioRef.current) {
setCurrentSongUrl(data.data.play.directUrl);
setIsBuffering(true);
audioRef.current.src = data.data.play.directUrl;
audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true });
audioRef.current.load();
@@ -1323,10 +1326,12 @@ export default function MusicPage() {
setCurrentSongUrl(streamUrl);
if (audioRef.current) {
setIsBuffering(true);
audioRef.current.src = streamUrl;
audioRef.current.load();
audioRef.current.play().catch(err => {
console.error('播放失败:', err);
setIsBuffering(false);
});
setIsPlaying(true);
}
@@ -1372,10 +1377,12 @@ export default function MusicPage() {
setCurrentSongUrl(data.data.play.directUrl);
if (audioRef.current) {
setIsBuffering(true);
audioRef.current.src = data.data.play.directUrl;
audioRef.current.load();
audioRef.current.play().catch(err => {
console.error('播放失败:', err);
setIsBuffering(false);
});
setIsPlaying(true);
}
@@ -1385,6 +1392,7 @@ export default function MusicPage() {
}
} catch (error) {
console.error('播放失败:', error);
setIsBuffering(false);
} finally {
endResolving();
}
@@ -1457,8 +1465,10 @@ export default function MusicPage() {
});
}
} else {
setIsBuffering(true);
audioRef.current.play().catch(err => {
console.error('播放失败:', err);
setIsBuffering(false);
});
setIsPlaying(true);
}
@@ -1612,6 +1622,7 @@ export default function MusicPage() {
.catch((error) => {
console.error('切换音质后播放失败:', error);
setIsPlaying(false);
setIsBuffering(false);
});
} else {
setIsPlaying(false);
@@ -1619,12 +1630,14 @@ export default function MusicPage() {
};
audio.pause();
setIsBuffering(true);
audio.src = nextSongUrl;
audio.addEventListener('loadedmetadata', resumeAfterMetadata, { once: true });
audio.load();
setIsPlaying(shouldResume);
} catch (error) {
console.error('切换音质失败:', error);
setIsBuffering(false);
setToast({
message: (error as Error).message || '切换音质失败',
type: 'error',
@@ -1801,6 +1814,16 @@ export default function MusicPage() {
}
};
const handleBufferingStart = () => {
if (audio.src && !audio.ended) {
setIsBuffering(true);
}
};
const handleBufferingEnd = () => {
setIsBuffering(false);
};
const handleDurationChange = () => {
setDuration(audio.duration);
@@ -1830,6 +1853,7 @@ export default function MusicPage() {
}
};
const handleEnded = () => {
setIsBuffering(false);
if (playMode === 'single') {
audio.currentTime = 0;
audio.play();
@@ -1849,12 +1873,28 @@ export default function MusicPage() {
};
audio.addEventListener('timeupdate', handleTimeUpdate);
audio.addEventListener('loadstart', handleBufferingStart);
audio.addEventListener('waiting', handleBufferingStart);
audio.addEventListener('stalled', handleBufferingStart);
audio.addEventListener('canplay', handleBufferingEnd);
audio.addEventListener('canplaythrough', handleBufferingEnd);
audio.addEventListener('playing', handleBufferingEnd);
audio.addEventListener('pause', handleBufferingEnd);
audio.addEventListener('error', handleBufferingEnd);
audio.addEventListener('loadedmetadata', handleLoadedMetadata);
audio.addEventListener('durationchange', handleDurationChange);
audio.addEventListener('ended', handleEnded);
return () => {
audio.removeEventListener('timeupdate', handleTimeUpdate);
audio.removeEventListener('loadstart', handleBufferingStart);
audio.removeEventListener('waiting', handleBufferingStart);
audio.removeEventListener('stalled', handleBufferingStart);
audio.removeEventListener('canplay', handleBufferingEnd);
audio.removeEventListener('canplaythrough', handleBufferingEnd);
audio.removeEventListener('playing', handleBufferingEnd);
audio.removeEventListener('pause', handleBufferingEnd);
audio.removeEventListener('error', handleBufferingEnd);
audio.removeEventListener('loadedmetadata', handleLoadedMetadata);
audio.removeEventListener('durationchange', handleDurationChange);
audio.removeEventListener('ended', handleEnded);
@@ -1918,6 +1958,7 @@ export default function MusicPage() {
// 进度条拖动
const handleProgressChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newTime = (parseFloat(e.target.value) / 100) * duration;
setCurrentTime(newTime);
if (audioRef.current) {
audioRef.current.currentTime = newTime;
}
@@ -2002,6 +2043,7 @@ export default function MusicPage() {
};
const progress = duration > 0 ? (currentTime / duration) * 100 : 0;
const showStreamBuffering = Boolean(currentSong && isBuffering);
const toggleSpectrum = () => {
setShowSpectrum(prev => !prev);
@@ -2267,6 +2309,92 @@ export default function MusicPage() {
opacity: 1;
}
}
@keyframes music-buffer-scan {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(200%);
}
}
@keyframes music-buffer-stripes {
0% {
background-position: 0 0;
}
100% {
background-position: 24px 0;
}
}
.music-buffer-track {
background-image: repeating-linear-gradient(
115deg,
rgba(16, 185, 129, 0.08) 0,
rgba(16, 185, 129, 0.08) 6px,
rgba(110, 231, 183, 0.28) 6px,
rgba(110, 231, 183, 0.28) 12px
);
background-size: 24px 100%;
animation: music-buffer-stripes 0.65s linear infinite;
}
.music-buffer-track::before {
content: '';
position: absolute;
inset: 0;
width: 42%;
background: linear-gradient(
90deg,
transparent,
rgba(167, 243, 208, 0.95),
transparent
);
filter: drop-shadow(0 0 8px rgba(52, 211, 153, 0.7));
animation: music-buffer-scan 0.95s ease-in-out infinite;
}
`}</style>
<style jsx global>{`
@keyframes music-buffer-scan-global {
0% {
transform: translateX(-120%);
}
100% {
transform: translateX(260%);
}
}
@keyframes music-buffer-stripes-global {
0% {
background-position: 0 0;
}
100% {
background-position: 28px 0;
}
}
.music-buffer-track-global {
background-image: repeating-linear-gradient(
115deg,
rgba(16, 185, 129, 0.1) 0,
rgba(16, 185, 129, 0.1) 7px,
rgba(110, 231, 183, 0.34) 7px,
rgba(110, 231, 183, 0.34) 14px
);
background-size: 28px 100%;
animation: music-buffer-stripes-global 0.55s linear infinite;
}
.music-buffer-track-global::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 45%;
background: linear-gradient(
90deg,
transparent,
rgba(236, 253, 245, 0.95),
transparent
);
box-shadow: 0 0 12px rgba(52, 211, 153, 0.9);
animation: music-buffer-scan-global 0.85s ease-in-out infinite;
}
`}</style>
{resolvingCount > 0 && (
<div className="fixed top-4 right-4 z-[80] pointer-events-none">
@@ -2729,9 +2857,12 @@ export default function MusicPage() {
{/* Progress Bar */}
<div className="absolute left-0 right-0 top-0 h-1 bg-white/10 rounded-t-xl overflow-hidden">
<div
className="h-full bg-green-500 transition-all pointer-events-none"
className="relative z-10 h-full bg-green-500 transition-all pointer-events-none"
style={{ width: `${progress}%` }}
/>
{showStreamBuffering && (
<div className="music-buffer-track-global absolute inset-0 z-0" />
)}
<input
type="range"
min="0"
@@ -2766,7 +2897,15 @@ export default function MusicPage() {
)}
</div>
<div className="min-w-0">
<div className="text-sm font-bold text-white truncate">{currentSong.name}</div>
<div className="flex items-center gap-2">
<div className="text-sm font-bold text-white truncate">{currentSong.name}</div>
{showStreamBuffering && (
<span className="hidden sm:inline-flex items-center gap-1 rounded-full bg-green-500/15 px-2 py-0.5 text-[10px] font-medium text-green-300">
<span className="h-2 w-2 rounded-full bg-green-400 animate-pulse" />
</span>
)}
</div>
<div className="text-xs text-zinc-500 truncate">{currentSong.artist}</div>
</div>
</div>
@@ -3169,9 +3308,17 @@ export default function MusicPage() {
</div>
{/* 进度条 */}
<div>
<div className="relative">
{showStreamBuffering && (
<div className="pointer-events-none absolute left-0 top-0 z-10 -translate-y-full">
<span className="inline-flex items-center gap-1 rounded-full bg-green-500/15 px-2 py-0.5 text-[10px] font-medium text-green-300">
<span className="h-2 w-2 rounded-full bg-green-400 animate-pulse" />
</span>
</div>
)}
{showSpectrum && (
<div className="mb-3 flex items-center gap-2 text-xs">
<div className="relative mb-3 flex items-center gap-2 text-xs">
<span className="invisible">{formatTime(currentTime)}</span>
<div className="flex-1">
<AudioSpectrumCanvas bars={spectrumBars} />
@@ -3183,9 +3330,12 @@ export default function MusicPage() {
<span>{formatTime(currentTime)}</span>
<div className="flex-1 h-1 bg-white/10 rounded-full overflow-hidden relative">
<div
className="h-full bg-green-500 transition-all pointer-events-none"
className="relative z-10 h-full bg-green-500 transition-all pointer-events-none"
style={{ width: `${progress}%` }}
/>
{showStreamBuffering && (
<div className="music-buffer-track-global absolute inset-0 z-0" />
)}
<input
type="range"
min="0"
+1 -1
View File
@@ -215,7 +215,7 @@ async function lxFetch(path: string, init: RequestInit = {}, authMode: LxFetchAu
const response = await fetch(`${baseUrl}${path}`, {
...init,
headers,
signal: AbortSignal.timeout(15000),
signal: AbortSignal.timeout(45000),
cache: 'no-store',
});