音乐openlist代理
This commit is contained in:
135
src/app/api/music/audio-proxy/route.ts
Normal file
135
src/app/api/music/audio-proxy/route.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { OpenListClient } from '@/lib/openlist.client';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
// 获取 OpenList 客户端
|
||||
async function getOpenListClient(): Promise<OpenListClient | null> {
|
||||
const config = await getConfig();
|
||||
const musicConfig = config?.MusicConfig;
|
||||
|
||||
if (!musicConfig?.OpenListCacheEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const url = musicConfig.OpenListCacheURL;
|
||||
const username = musicConfig.OpenListCacheUsername;
|
||||
const password = musicConfig.OpenListCachePassword;
|
||||
|
||||
if (!url || !username || !password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new OpenListClient(url, username, password);
|
||||
}
|
||||
|
||||
// 代理OpenList缓存的音频文件
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const platform = searchParams.get('platform');
|
||||
const id = searchParams.get('id');
|
||||
const quality = searchParams.get('quality');
|
||||
|
||||
if (!platform || !id || !quality) {
|
||||
return NextResponse.json(
|
||||
{ error: '缺少必要参数: platform, id, quality' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 获取OpenList客户端
|
||||
const openListClient = await getOpenListClient();
|
||||
if (!openListClient) {
|
||||
return NextResponse.json(
|
||||
{ error: 'OpenList未配置或未启用' },
|
||||
{ status: 503 }
|
||||
);
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
const config = await getConfig();
|
||||
const cachePath = config?.MusicConfig?.OpenListCachePath || '/music-cache';
|
||||
|
||||
// 构建音频文件路径
|
||||
const audioPath = `${cachePath}/${platform}/audio/${id}-${quality}.mp3`;
|
||||
|
||||
// 获取文件信息
|
||||
const fileResponse = await openListClient.getFile(audioPath);
|
||||
|
||||
if (fileResponse.code !== 200 || !fileResponse.data?.raw_url) {
|
||||
return NextResponse.json(
|
||||
{ error: '音频文件未找到' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// 检查是否有 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',
|
||||
};
|
||||
|
||||
// 如果有 Range 请求,转发给上游
|
||||
if (range) {
|
||||
upstreamHeaders['Range'] = range;
|
||||
}
|
||||
|
||||
// 从OpenList获取音频流
|
||||
const response = await fetch(fileResponse.data.raw_url, {
|
||||
headers: upstreamHeaders,
|
||||
});
|
||||
|
||||
if (!response.ok && response.status !== 206) {
|
||||
return NextResponse.json(
|
||||
{ error: '获取音频失败' },
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
// 获取响应头
|
||||
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=31536000, immutable', // 永久缓存(1年)
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Accept-Ranges': acceptRanges || 'bytes',
|
||||
'X-Cache-Source': 'openlist-audio-proxy',
|
||||
};
|
||||
|
||||
if (contentLength) {
|
||||
headers['Content-Length'] = contentLength;
|
||||
}
|
||||
|
||||
// 如果上游返回了 Content-Range,转发给客户端
|
||||
if (contentRange) {
|
||||
headers['Content-Range'] = contentRange;
|
||||
}
|
||||
|
||||
// 返回音频流,保持原始状态码(200 或 206)
|
||||
return new NextResponse(response.body, {
|
||||
status: response.status,
|
||||
headers,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('代理OpenList音频失败:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: '代理请求失败',
|
||||
details: (error as Error).message,
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,10 @@ async function replaceAudioUrlsWithOpenList(
|
||||
return data;
|
||||
}
|
||||
|
||||
// 获取配置,检查是否启用缓存代理
|
||||
const config = await getConfig();
|
||||
const cacheProxyEnabled = config?.MusicConfig?.OpenListCacheProxyEnabled ?? true;
|
||||
|
||||
// TuneHub 返回的数据结构是 { code: 0, data: { data: [...], total: 1 } }
|
||||
// 需要提取内层的 data 数组
|
||||
const songsData = data.data.data || data.data;
|
||||
@@ -143,7 +147,14 @@ async function replaceAudioUrlsWithOpenList(
|
||||
const fileResponse = await openListClient.getFile(audioPath);
|
||||
|
||||
if (fileResponse.code === 200 && fileResponse.data?.raw_url) {
|
||||
song.url = fileResponse.data.raw_url;
|
||||
// 如果启用缓存代理,返回代理URL;否则返回直接URL
|
||||
if (cacheProxyEnabled) {
|
||||
// 使用代理URL,通过我们的服务器代理OpenList的音频
|
||||
song.url = `/api/music/audio-proxy?platform=${platform}&id=${song.id}&quality=${quality}`;
|
||||
} else {
|
||||
// 直接使用OpenList的raw_url
|
||||
song.url = fileResponse.data.raw_url;
|
||||
}
|
||||
song.cached = true;
|
||||
} else {
|
||||
song.cached = false;
|
||||
|
||||
Reference in New Issue
Block a user