This commit is contained in:
mtvpls
2026-02-01 21:26:10 +08:00
parent e08e953047
commit 9b217b8eae
2 changed files with 78 additions and 12 deletions

View File

@@ -54,15 +54,26 @@ export async function GET(request: NextRequest) {
);
}
// 检查是否有 Range 请求头
const range = request.headers.get('range');
// 构建上游请求头
const upstreamHeaders: Record<string, string> = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'http://www.kuwo.cn/',
};
// 如果有 Range 请求,转发给上游
if (range) {
upstreamHeaders['Range'] = range;
}
// 发起请求获取音频流
const response = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'http://www.kuwo.cn/',
},
headers: upstreamHeaders,
});
if (!response.ok) {
if (!response.ok && response.status !== 206) {
return NextResponse.json(
{ error: '获取音频失败' },
{ status: response.status }
@@ -72,25 +83,27 @@ export async function GET(request: NextRequest) {
// 获取响应头
const contentType = response.headers.get('content-type') || 'audio/mpeg';
const contentLength = response.headers.get('content-length');
const contentRange = response.headers.get('content-range');
const acceptRanges = response.headers.get('accept-ranges');
// 创建响应头
const headers: Record<string, string> = {
'Content-Type': contentType,
'Cache-Control': 'public, max-age=3600',
'Access-Control-Allow-Origin': '*',
'Accept-Ranges': acceptRanges || 'bytes',
};
if (contentLength) {
headers['Content-Length'] = contentLength;
}
// 支持 Range 请求(用于音频拖动)
const range = request.headers.get('range');
if (range) {
headers['Accept-Ranges'] = 'bytes';
// 如果上游返回了 Content-Range转发给客户端
if (contentRange) {
headers['Content-Range'] = contentRange;
}
// 返回音频流
// 返回音频流保持原始状态码200 或 206
return new NextResponse(response.body, {
status: response.status,
headers,

View File

@@ -65,6 +65,7 @@ export default function MusicPage() {
const [showPlaylist, setShowPlaylist] = useState(false);
const [playlistIndex, setPlaylistIndex] = useState(-1); // 当前在播放列表中的索引
const [showQualityMenu, setShowQualityMenu] = useState(false); // 音质选择菜单
const [showVolumeSlider, setShowVolumeSlider] = useState(false); // 音量滑块显示状态
const audioRef = useRef<HTMLAudioElement>(null);
const lyricsContainerRef = useRef<HTMLDivElement>(null);
@@ -1047,8 +1048,19 @@ export default function MusicPage() {
{/* Lyrics Modal */}
{showLyrics && currentSong && (
<div className="fixed inset-0 bg-black/90 backdrop-blur-sm z-[100] flex items-center justify-center p-4">
<div className="w-full max-w-2xl h-[90vh] md:h-auto max-h-[90vh] bg-zinc-900/95 rounded-2xl overflow-hidden border border-white/10 shadow-2xl flex flex-col">
<div
className="fixed inset-0 bg-black/90 backdrop-blur-sm z-[100] flex items-center justify-center p-4"
onClick={(e) => {
// 点击背景关闭音量条
if (e.target === e.currentTarget) {
setShowVolumeSlider(false);
}
}}
>
<div
className="w-full max-w-2xl h-[90vh] md:h-auto max-h-[90vh] bg-zinc-900/95 rounded-2xl overflow-hidden border border-white/10 shadow-2xl flex flex-col"
onClick={() => setShowVolumeSlider(false)}
>
{/* Header */}
<div className="relative h-32 md:h-48 bg-gradient-to-b from-zinc-800 to-zinc-900 shrink-0">
{currentSong.pic && (
@@ -1203,6 +1215,47 @@ export default function MusicPage() {
</svg>
)}
</button>
{/* 音量控制 */}
<div className="relative group">
<button
onClick={(e) => {
e.stopPropagation();
setShowVolumeSlider(!showVolumeSlider);
}}
className="text-zinc-500 hover:text-white transition-colors"
title="音量"
>
<svg className="w-4 h-4 md:w-5 md:h-5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM14.657 2.929a1 1 0 011.414 0A9.972 9.972 0 0119 10a9.972 9.972 0 01-2.929 7.071 1 1 0 01-1.414-1.414A7.971 7.971 0 0017 10c0-2.21-.894-4.208-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 10a5.984 5.984 0 01-1.757 4.243 1 1 0 01-1.415-1.415A3.984 3.984 0 0013 10a3.983 3.983 0 00-1.172-2.828 1 1 0 010-1.415z" clipRule="evenodd" />
</svg>
</button>
{/* 垂直音量条 - 桌面悬浮/移动端点击 */}
<div
className={`absolute bottom-full left-1/2 -translate-x-1/2 pb-2 transition-opacity md:opacity-0 md:group-hover:opacity-100 md:pointer-events-auto ${showVolumeSlider ? 'opacity-100' : 'opacity-0 pointer-events-none'}`}
onClick={(e) => e.stopPropagation()}
>
<div className="bg-zinc-800/95 backdrop-blur-sm rounded-lg p-3 shadow-xl border border-white/10">
<div className="flex flex-col items-center gap-2">
<span className="text-xs text-zinc-400 font-mono">{volume}</span>
<div className="h-24 w-1 bg-white/10 rounded-full relative">
<div
className="absolute bottom-0 left-0 right-0 bg-green-500 rounded-full transition-all pointer-events-none"
style={{ height: `${volume}%` }}
/>
<input
type="range"
min="0"
max="100"
value={volume}
onChange={handleVolumeChange}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer [writing-mode:bt-lr] [-webkit-appearance:slider-vertical]"
orient="vertical"
/>
</div>
</div>
</div>
</div>
</div>
</div>
{/* 进度条 */}