提升音乐解析超时时间,增加明显的缓冲中标识
This commit is contained in:
@@ -156,7 +156,7 @@ export async function GET(request: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
upstream = await fetch(upstreamUrl, {
|
upstream = await fetch(upstreamUrl, {
|
||||||
headers: upstreamHeaders,
|
headers: upstreamHeaders,
|
||||||
signal: AbortSignal.timeout(30000),
|
signal: AbortSignal.timeout(90000),
|
||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -165,7 +165,7 @@ export async function GET(request: NextRequest) {
|
|||||||
upstreamHeaders = buildUpstreamHeaders(request, upstreamUrl);
|
upstreamHeaders = buildUpstreamHeaders(request, upstreamUrl);
|
||||||
upstream = await fetch(upstreamUrl, {
|
upstream = await fetch(upstreamUrl, {
|
||||||
headers: upstreamHeaders,
|
headers: upstreamHeaders,
|
||||||
signal: AbortSignal.timeout(30000),
|
signal: AbortSignal.timeout(90000),
|
||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
});
|
});
|
||||||
console.warn('[music-v2/stream] upstream fetch failed once, retried with refreshed URL', {
|
console.warn('[music-v2/stream] upstream fetch failed once, retried with refreshed URL', {
|
||||||
|
|||||||
+155
-5
@@ -285,6 +285,7 @@ export default function MusicPage() {
|
|||||||
const [loadingMoreSearch, setLoadingMoreSearch] = useState(false);
|
const [loadingMoreSearch, setLoadingMoreSearch] = useState(false);
|
||||||
const [currentSong, setCurrentSong] = useState<Song | null>(null);
|
const [currentSong, setCurrentSong] = useState<Song | null>(null);
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
|
const [isBuffering, setIsBuffering] = useState(false);
|
||||||
const [currentTime, setCurrentTime] = useState(0);
|
const [currentTime, setCurrentTime] = useState(0);
|
||||||
const [duration, setDuration] = useState(0);
|
const [duration, setDuration] = useState(0);
|
||||||
const [volume, setVolume] = useState(100);
|
const [volume, setVolume] = useState(100);
|
||||||
@@ -660,6 +661,7 @@ export default function MusicPage() {
|
|||||||
setCurrentSongUrl(streamUrl);
|
setCurrentSongUrl(streamUrl);
|
||||||
|
|
||||||
if (audioRef.current) {
|
if (audioRef.current) {
|
||||||
|
setIsBuffering(true);
|
||||||
audioRef.current.src = streamUrl;
|
audioRef.current.src = streamUrl;
|
||||||
audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true });
|
audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true });
|
||||||
audioRef.current.load();
|
audioRef.current.load();
|
||||||
@@ -679,6 +681,7 @@ export default function MusicPage() {
|
|||||||
const data = await fetchPlayData(latestDbSong, platform, selectedQuality, true);
|
const data = await fetchPlayData(latestDbSong, platform, selectedQuality, true);
|
||||||
if (data.success && data.data?.play?.directUrl && audioRef.current) {
|
if (data.success && data.data?.play?.directUrl && audioRef.current) {
|
||||||
setCurrentSongUrl(data.data.play.directUrl);
|
setCurrentSongUrl(data.data.play.directUrl);
|
||||||
|
setIsBuffering(true);
|
||||||
audioRef.current.src = data.data.play.directUrl;
|
audioRef.current.src = data.data.play.directUrl;
|
||||||
audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true });
|
audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true });
|
||||||
audioRef.current.load();
|
audioRef.current.load();
|
||||||
@@ -1323,10 +1326,12 @@ export default function MusicPage() {
|
|||||||
setCurrentSongUrl(streamUrl);
|
setCurrentSongUrl(streamUrl);
|
||||||
|
|
||||||
if (audioRef.current) {
|
if (audioRef.current) {
|
||||||
|
setIsBuffering(true);
|
||||||
audioRef.current.src = streamUrl;
|
audioRef.current.src = streamUrl;
|
||||||
audioRef.current.load();
|
audioRef.current.load();
|
||||||
audioRef.current.play().catch(err => {
|
audioRef.current.play().catch(err => {
|
||||||
console.error('播放失败:', err);
|
console.error('播放失败:', err);
|
||||||
|
setIsBuffering(false);
|
||||||
});
|
});
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
}
|
}
|
||||||
@@ -1372,10 +1377,12 @@ export default function MusicPage() {
|
|||||||
setCurrentSongUrl(data.data.play.directUrl);
|
setCurrentSongUrl(data.data.play.directUrl);
|
||||||
|
|
||||||
if (audioRef.current) {
|
if (audioRef.current) {
|
||||||
|
setIsBuffering(true);
|
||||||
audioRef.current.src = data.data.play.directUrl;
|
audioRef.current.src = data.data.play.directUrl;
|
||||||
audioRef.current.load();
|
audioRef.current.load();
|
||||||
audioRef.current.play().catch(err => {
|
audioRef.current.play().catch(err => {
|
||||||
console.error('播放失败:', err);
|
console.error('播放失败:', err);
|
||||||
|
setIsBuffering(false);
|
||||||
});
|
});
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
}
|
}
|
||||||
@@ -1385,6 +1392,7 @@ export default function MusicPage() {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('播放失败:', error);
|
console.error('播放失败:', error);
|
||||||
|
setIsBuffering(false);
|
||||||
} finally {
|
} finally {
|
||||||
endResolving();
|
endResolving();
|
||||||
}
|
}
|
||||||
@@ -1457,8 +1465,10 @@ export default function MusicPage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
setIsBuffering(true);
|
||||||
audioRef.current.play().catch(err => {
|
audioRef.current.play().catch(err => {
|
||||||
console.error('播放失败:', err);
|
console.error('播放失败:', err);
|
||||||
|
setIsBuffering(false);
|
||||||
});
|
});
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
}
|
}
|
||||||
@@ -1612,6 +1622,7 @@ export default function MusicPage() {
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('切换音质后播放失败:', error);
|
console.error('切换音质后播放失败:', error);
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
|
setIsBuffering(false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
@@ -1619,12 +1630,14 @@ export default function MusicPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
audio.pause();
|
audio.pause();
|
||||||
|
setIsBuffering(true);
|
||||||
audio.src = nextSongUrl;
|
audio.src = nextSongUrl;
|
||||||
audio.addEventListener('loadedmetadata', resumeAfterMetadata, { once: true });
|
audio.addEventListener('loadedmetadata', resumeAfterMetadata, { once: true });
|
||||||
audio.load();
|
audio.load();
|
||||||
setIsPlaying(shouldResume);
|
setIsPlaying(shouldResume);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('切换音质失败:', error);
|
console.error('切换音质失败:', error);
|
||||||
|
setIsBuffering(false);
|
||||||
setToast({
|
setToast({
|
||||||
message: (error as Error).message || '切换音质失败',
|
message: (error as Error).message || '切换音质失败',
|
||||||
type: 'error',
|
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 = () => {
|
const handleDurationChange = () => {
|
||||||
setDuration(audio.duration);
|
setDuration(audio.duration);
|
||||||
|
|
||||||
@@ -1830,6 +1853,7 @@ export default function MusicPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleEnded = () => {
|
const handleEnded = () => {
|
||||||
|
setIsBuffering(false);
|
||||||
if (playMode === 'single') {
|
if (playMode === 'single') {
|
||||||
audio.currentTime = 0;
|
audio.currentTime = 0;
|
||||||
audio.play();
|
audio.play();
|
||||||
@@ -1849,12 +1873,28 @@ export default function MusicPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
audio.addEventListener('timeupdate', handleTimeUpdate);
|
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('loadedmetadata', handleLoadedMetadata);
|
||||||
audio.addEventListener('durationchange', handleDurationChange);
|
audio.addEventListener('durationchange', handleDurationChange);
|
||||||
audio.addEventListener('ended', handleEnded);
|
audio.addEventListener('ended', handleEnded);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
audio.removeEventListener('timeupdate', handleTimeUpdate);
|
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('loadedmetadata', handleLoadedMetadata);
|
||||||
audio.removeEventListener('durationchange', handleDurationChange);
|
audio.removeEventListener('durationchange', handleDurationChange);
|
||||||
audio.removeEventListener('ended', handleEnded);
|
audio.removeEventListener('ended', handleEnded);
|
||||||
@@ -1918,6 +1958,7 @@ export default function MusicPage() {
|
|||||||
// 进度条拖动
|
// 进度条拖动
|
||||||
const handleProgressChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleProgressChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const newTime = (parseFloat(e.target.value) / 100) * duration;
|
const newTime = (parseFloat(e.target.value) / 100) * duration;
|
||||||
|
setCurrentTime(newTime);
|
||||||
if (audioRef.current) {
|
if (audioRef.current) {
|
||||||
audioRef.current.currentTime = newTime;
|
audioRef.current.currentTime = newTime;
|
||||||
}
|
}
|
||||||
@@ -2002,6 +2043,7 @@ export default function MusicPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const progress = duration > 0 ? (currentTime / duration) * 100 : 0;
|
const progress = duration > 0 ? (currentTime / duration) * 100 : 0;
|
||||||
|
const showStreamBuffering = Boolean(currentSong && isBuffering);
|
||||||
|
|
||||||
const toggleSpectrum = () => {
|
const toggleSpectrum = () => {
|
||||||
setShowSpectrum(prev => !prev);
|
setShowSpectrum(prev => !prev);
|
||||||
@@ -2267,6 +2309,92 @@ export default function MusicPage() {
|
|||||||
opacity: 1;
|
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>
|
`}</style>
|
||||||
{resolvingCount > 0 && (
|
{resolvingCount > 0 && (
|
||||||
<div className="fixed top-4 right-4 z-[80] pointer-events-none">
|
<div className="fixed top-4 right-4 z-[80] pointer-events-none">
|
||||||
@@ -2729,9 +2857,12 @@ export default function MusicPage() {
|
|||||||
{/* Progress Bar */}
|
{/* Progress Bar */}
|
||||||
<div className="absolute left-0 right-0 top-0 h-1 bg-white/10 rounded-t-xl overflow-hidden">
|
<div className="absolute left-0 right-0 top-0 h-1 bg-white/10 rounded-t-xl overflow-hidden">
|
||||||
<div
|
<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}%` }}
|
style={{ width: `${progress}%` }}
|
||||||
/>
|
/>
|
||||||
|
{showStreamBuffering && (
|
||||||
|
<div className="music-buffer-track-global absolute inset-0 z-0" />
|
||||||
|
)}
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -2766,7 +2897,15 @@ export default function MusicPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<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 className="text-xs text-zinc-500 truncate">{currentSong.artist}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3169,9 +3308,17 @@ export default function MusicPage() {
|
|||||||
</div>
|
</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 && (
|
{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>
|
<span className="invisible">{formatTime(currentTime)}</span>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<AudioSpectrumCanvas bars={spectrumBars} />
|
<AudioSpectrumCanvas bars={spectrumBars} />
|
||||||
@@ -3183,9 +3330,12 @@ export default function MusicPage() {
|
|||||||
<span>{formatTime(currentTime)}</span>
|
<span>{formatTime(currentTime)}</span>
|
||||||
<div className="flex-1 h-1 bg-white/10 rounded-full overflow-hidden relative">
|
<div className="flex-1 h-1 bg-white/10 rounded-full overflow-hidden relative">
|
||||||
<div
|
<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}%` }}
|
style={{ width: `${progress}%` }}
|
||||||
/>
|
/>
|
||||||
|
{showStreamBuffering && (
|
||||||
|
<div className="music-buffer-track-global absolute inset-0 z-0" />
|
||||||
|
)}
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min="0"
|
min="0"
|
||||||
|
|||||||
+1
-1
@@ -215,7 +215,7 @@ async function lxFetch(path: string, init: RequestInit = {}, authMode: LxFetchAu
|
|||||||
const response = await fetch(`${baseUrl}${path}`, {
|
const response = await fetch(`${baseUrl}${path}`, {
|
||||||
...init,
|
...init,
|
||||||
headers,
|
headers,
|
||||||
signal: AbortSignal.timeout(15000),
|
signal: AbortSignal.timeout(45000),
|
||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user