代理kuwo
This commit is contained in:
@@ -0,0 +1,108 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
export const runtime = 'nodejs';
|
||||||
|
|
||||||
|
// 代理音频流
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const url = searchParams.get('url');
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '缺少 url 参数' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 安全检查:只允许代理音乐平台的音频和图片 CDN
|
||||||
|
const allowedDomains = [
|
||||||
|
'sycdn.kuwo.cn',
|
||||||
|
'kwcdn.kuwo.cn',
|
||||||
|
'img1.kwcdn.kuwo.cn',
|
||||||
|
'img2.kwcdn.kuwo.cn',
|
||||||
|
'img3.kwcdn.kuwo.cn',
|
||||||
|
'img4.kwcdn.kuwo.cn',
|
||||||
|
'music.163.com',
|
||||||
|
'y.qq.com',
|
||||||
|
'ws.stream.qqmusic.qq.com',
|
||||||
|
'isure.stream.qqmusic.qq.com',
|
||||||
|
'dl.stream.qqmusic.qq.com',
|
||||||
|
];
|
||||||
|
|
||||||
|
let urlObj: URL;
|
||||||
|
try {
|
||||||
|
urlObj = new URL(url);
|
||||||
|
} catch {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '无效的 URL' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAllowed = allowedDomains.some(domain =>
|
||||||
|
urlObj.hostname === domain || urlObj.hostname.endsWith(`.${domain}`)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isAllowed) {
|
||||||
|
console.warn(`拒绝代理音频请求: ${urlObj.hostname}`);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '不允许的目标域名' },
|
||||||
|
{ status: 403 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发起请求获取音频流
|
||||||
|
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/',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '获取音频失败' },
|
||||||
|
{ status: response.status }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取响应头
|
||||||
|
const contentType = response.headers.get('content-type') || 'audio/mpeg';
|
||||||
|
const contentLength = response.headers.get('content-length');
|
||||||
|
|
||||||
|
// 创建响应头
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': contentType,
|
||||||
|
'Cache-Control': 'public, max-age=3600',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (contentLength) {
|
||||||
|
headers['Content-Length'] = contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支持 Range 请求(用于音频拖动)
|
||||||
|
const range = request.headers.get('range');
|
||||||
|
if (range) {
|
||||||
|
headers['Accept-Ranges'] = 'bytes';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回音频流
|
||||||
|
return new NextResponse(response.body, {
|
||||||
|
status: response.status,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('代理音频失败:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: '代理请求失败',
|
||||||
|
details: (error as Error).message,
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+74
-10
@@ -79,17 +79,56 @@ async function executeMethod(
|
|||||||
let url = config.url;
|
let url = config.url;
|
||||||
const params: Record<string, string> = {};
|
const params: Record<string, string> = {};
|
||||||
|
|
||||||
|
// 先将 variables 中的值转换为可执行的变量
|
||||||
|
const evalContext: Record<string, any> = {};
|
||||||
|
for (const [key, value] of Object.entries(variables)) {
|
||||||
|
// 尝试将字符串转换为数字(如果可能)
|
||||||
|
const numValue = Number(value);
|
||||||
|
evalContext[key] = isNaN(numValue) ? value : numValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归处理对象中的模板变量
|
||||||
|
function processTemplateValue(value: any): any {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
// 处理包含模板变量的表达式
|
||||||
|
const expressionRegex = /\{\{(.+?)\}\}/g;
|
||||||
|
return value.replace(expressionRegex, (match, expression) => {
|
||||||
|
try {
|
||||||
|
// 创建一个函数来执行表达式,传入所有变量作为参数
|
||||||
|
// eslint-disable-next-line no-new-func
|
||||||
|
const func = new Function(...Object.keys(evalContext), `return ${expression}`);
|
||||||
|
const result = func(...Object.values(evalContext));
|
||||||
|
return String(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[executeMethod] 执行表达式失败: ${expression}`, err);
|
||||||
|
return '0'; // 默认值
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
return value.map(item => processTemplateValue(item));
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
const result: any = {};
|
||||||
|
for (const [k, v] of Object.entries(value)) {
|
||||||
|
result[k] = processTemplateValue(v);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 URL 参数
|
||||||
if (config.params) {
|
if (config.params) {
|
||||||
for (const [key, value] of Object.entries(config.params)) {
|
for (const [key, value] of Object.entries(config.params)) {
|
||||||
let processedValue = String(value);
|
params[key] = processTemplateValue(value);
|
||||||
// 替换所有模板变量
|
|
||||||
for (const [varName, varValue] of Object.entries(variables)) {
|
|
||||||
processedValue = processedValue.replace(`{{${varName}}}`, varValue);
|
|
||||||
}
|
|
||||||
params[key] = processedValue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理 POST body
|
||||||
|
let processedBody = config.body;
|
||||||
|
if (config.body) {
|
||||||
|
processedBody = processTemplateValue(config.body);
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 构建完整 URL
|
// 3. 构建完整 URL
|
||||||
if (config.method === 'GET' && Object.keys(params).length > 0) {
|
if (config.method === 'GET' && Object.keys(params).length > 0) {
|
||||||
const urlObj = new URL(url);
|
const urlObj = new URL(url);
|
||||||
@@ -105,8 +144,8 @@ async function executeMethod(
|
|||||||
headers: config.headers || {},
|
headers: config.headers || {},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (config.method === 'POST' && config.body) {
|
if (config.method === 'POST' && processedBody) {
|
||||||
requestOptions.body = JSON.stringify(config.body);
|
requestOptions.body = JSON.stringify(processedBody);
|
||||||
requestOptions.headers = {
|
requestOptions.headers = {
|
||||||
...requestOptions.headers,
|
...requestOptions.headers,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -123,10 +162,31 @@ async function executeMethod(
|
|||||||
const transformFn = eval(`(${config.transform})`);
|
const transformFn = eval(`(${config.transform})`);
|
||||||
data = transformFn(data);
|
data = transformFn(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Transform 函数执行失败:', err);
|
console.error('[executeMethod] Transform 函数执行失败:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6. 处理酷我音乐的图片 URL(转换为代理 URL)
|
||||||
|
if (platform === 'kuwo') {
|
||||||
|
const processKuwoImages = (obj: any): any => {
|
||||||
|
if (typeof obj === 'string' && obj.startsWith('http://') && obj.includes('kwcdn.kuwo.cn')) {
|
||||||
|
// 将 HTTP 图片 URL 转换为代理 URL
|
||||||
|
return `/api/music/proxy?url=${encodeURIComponent(obj)}`;
|
||||||
|
} else if (Array.isArray(obj)) {
|
||||||
|
return obj.map(item => processKuwoImages(item));
|
||||||
|
} else if (typeof obj === 'object' && obj !== null) {
|
||||||
|
const result: any = {};
|
||||||
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
|
result[key] = processKuwoImages(value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
data = processKuwoImages(data);
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +291,7 @@ export async function GET(request: NextRequest) {
|
|||||||
// 搜索歌曲
|
// 搜索歌曲
|
||||||
const platform = searchParams.get('platform');
|
const platform = searchParams.get('platform');
|
||||||
const keyword = searchParams.get('keyword');
|
const keyword = searchParams.get('keyword');
|
||||||
const page = searchParams.get('page') || '0';
|
const page = searchParams.get('page') || '1';
|
||||||
const pageSize = searchParams.get('pageSize') || '20';
|
const pageSize = searchParams.get('pageSize') || '20';
|
||||||
|
|
||||||
if (!platform || !keyword) {
|
if (!platform || !keyword) {
|
||||||
@@ -248,11 +308,15 @@ export async function GET(request: NextRequest) {
|
|||||||
return NextResponse.json(cached.data);
|
return NextResponse.json(cached.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 注意:不同平台可能使用不同的变量名
|
||||||
|
// 统一传递 keyword, page, pageSize, limit (limit = pageSize)
|
||||||
const data = await executeMethod(baseUrl, platform, 'search', {
|
const data = await executeMethod(baseUrl, platform, 'search', {
|
||||||
keyword,
|
keyword,
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
|
limit: pageSize, // 有些平台使用 limit 而不是 pageSize
|
||||||
});
|
});
|
||||||
|
|
||||||
serverCache.proxyRequests.set(cacheKey, { data, timestamp: Date.now() });
|
serverCache.proxyRequests.set(cacheKey, { data, timestamp: Date.now() });
|
||||||
|
|
||||||
return NextResponse.json(data);
|
return NextResponse.json(data);
|
||||||
|
|||||||
+102
-32
@@ -15,6 +15,7 @@ interface Song {
|
|||||||
artist: string;
|
artist: string;
|
||||||
album?: string;
|
album?: string;
|
||||||
pic?: string;
|
pic?: string;
|
||||||
|
platform?: 'netease' | 'qq' | 'kuwo'; // 添加平台信息
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PlayRecord {
|
interface PlayRecord {
|
||||||
@@ -70,6 +71,20 @@ export default function MusicPage() {
|
|||||||
const lastSaveTimeRef = useRef<number>(0);
|
const lastSaveTimeRef = useRef<number>(0);
|
||||||
const restoredTimeRef = useRef<number>(0);
|
const restoredTimeRef = useRef<number>(0);
|
||||||
|
|
||||||
|
// 工具函数:处理图片 URL(在 HTTPS 环境下代理 HTTP 图片)
|
||||||
|
const processImageUrl = (url: string | undefined, platform: string): string | undefined => {
|
||||||
|
if (!url) return url;
|
||||||
|
|
||||||
|
const isHttps = typeof window !== 'undefined' && window.location.protocol === 'https:';
|
||||||
|
|
||||||
|
// 只对酷我音乐的 HTTP 图片在 HTTPS 环境下进行代理
|
||||||
|
if (platform === 'kuwo' && isHttps && url.startsWith('http://')) {
|
||||||
|
return `/api/music/proxy?url=${encodeURIComponent(url)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
// 保存播放状态到 localStorage
|
// 保存播放状态到 localStorage
|
||||||
const savePlayState = () => {
|
const savePlayState = () => {
|
||||||
if (!currentSong) return;
|
if (!currentSong) return;
|
||||||
@@ -96,7 +111,7 @@ export default function MusicPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 从 localStorage 恢复播放状态
|
// 从 localStorage 恢复播放状态
|
||||||
const restorePlayState = () => {
|
const restorePlayState = async () => {
|
||||||
try {
|
try {
|
||||||
const saved = localStorage.getItem('musicPlayState');
|
const saved = localStorage.getItem('musicPlayState');
|
||||||
if (!saved) return;
|
if (!saved) return;
|
||||||
@@ -112,7 +127,6 @@ export default function MusicPage() {
|
|||||||
setQuality(playState.quality || '320k');
|
setQuality(playState.quality || '320k');
|
||||||
setPlayMode(playState.playMode || 'loop');
|
setPlayMode(playState.playMode || 'loop');
|
||||||
setVolume(playState.volume || 100);
|
setVolume(playState.volume || 100);
|
||||||
setCurrentSongUrl(playState.currentSongUrl || '');
|
|
||||||
setLyrics(playState.lyrics || []);
|
setLyrics(playState.lyrics || []);
|
||||||
setPlayRecords(playState.playRecords || []);
|
setPlayRecords(playState.playRecords || []);
|
||||||
setPlaylist(playState.playlist || []); // 恢复播放列表
|
setPlaylist(playState.playlist || []); // 恢复播放列表
|
||||||
@@ -121,29 +135,65 @@ export default function MusicPage() {
|
|||||||
// 保存需要恢复的时间点
|
// 保存需要恢复的时间点
|
||||||
restoredTimeRef.current = playState.currentTime || 0;
|
restoredTimeRef.current = playState.currentTime || 0;
|
||||||
|
|
||||||
if (playState.currentSong && playState.currentSongUrl) {
|
if (playState.currentSong) {
|
||||||
setShowPlayer(true);
|
setShowPlayer(true);
|
||||||
// 延迟设置音频源,等待 audio 元素加载
|
|
||||||
setTimeout(() => {
|
|
||||||
if (audioRef.current && playState.currentSongUrl) {
|
|
||||||
audioRef.current.src = playState.currentSongUrl;
|
|
||||||
|
|
||||||
// 监听多个事件以确保进度恢复
|
// 获取歌曲的平台信息
|
||||||
const restoreTime = () => {
|
const platform = playState.currentSong.platform || playState.currentSource || 'netease';
|
||||||
if (audioRef.current && restoredTimeRef.current > 0) {
|
|
||||||
audioRef.current.currentTime = restoredTimeRef.current;
|
// 重新解析歌曲获取新的播放链接
|
||||||
restoredTimeRef.current = 0;
|
try {
|
||||||
|
const response = await fetch('/api/music', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: 'parse',
|
||||||
|
platform: platform,
|
||||||
|
ids: playState.currentSong.id,
|
||||||
|
quality: playState.quality || '320k',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.code === 0 && data.data?.data && data.data.data.length > 0) {
|
||||||
|
const songData = data.data.data[0];
|
||||||
|
|
||||||
|
if (songData.url && songData.success) {
|
||||||
|
// 对于酷我音乐,使用代理
|
||||||
|
let playUrl = songData.url;
|
||||||
|
if (platform === 'kuwo') {
|
||||||
|
playUrl = `/api/music/proxy?url=${encodeURIComponent(songData.url)}`;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// 监听加载完成事件
|
setCurrentSongUrl(songData.url);
|
||||||
audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true });
|
|
||||||
audioRef.current.addEventListener('canplay', restoreTime, { once: true });
|
|
||||||
|
|
||||||
// 调用 load() 触发音频加载
|
// 延迟设置音频源,等待 audio 元素加载
|
||||||
audioRef.current.load();
|
setTimeout(() => {
|
||||||
|
if (audioRef.current) {
|
||||||
|
audioRef.current.src = playUrl;
|
||||||
|
|
||||||
|
// 监听多个事件以确保进度恢复
|
||||||
|
const restoreTime = () => {
|
||||||
|
if (audioRef.current && restoredTimeRef.current > 0) {
|
||||||
|
audioRef.current.currentTime = restoredTimeRef.current;
|
||||||
|
restoredTimeRef.current = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听加载完成事件
|
||||||
|
audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true });
|
||||||
|
audioRef.current.addEventListener('canplay', restoreTime, { once: true });
|
||||||
|
|
||||||
|
// 调用 load() 触发音频加载
|
||||||
|
audioRef.current.load();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, 100);
|
} catch (error) {
|
||||||
|
console.error('重新解析歌曲失败:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('恢复播放状态失败:', error);
|
console.error('恢复播放状态失败:', error);
|
||||||
@@ -179,6 +229,7 @@ export default function MusicPage() {
|
|||||||
artist: record.artist,
|
artist: record.artist,
|
||||||
album: record.album,
|
album: record.album,
|
||||||
pic: record.pic,
|
pic: record.pic,
|
||||||
|
platform: record.platform, // 添加平台信息
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -210,9 +261,11 @@ export default function MusicPage() {
|
|||||||
`/api/music?action=toplists&platform=${source}`
|
`/api/music?action=toplists&platform=${source}`
|
||||||
);
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setPlaylists(data || []);
|
// 确保返回的是数组
|
||||||
|
setPlaylists(Array.isArray(data) ? data : []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载排行榜失败:', error);
|
console.error('加载排行榜失败:', error);
|
||||||
|
setPlaylists([]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -226,11 +279,13 @@ export default function MusicPage() {
|
|||||||
`/api/music?action=toplist&platform=${currentSource}&id=${playlistId}`
|
`/api/music?action=toplist&platform=${currentSource}&id=${playlistId}`
|
||||||
);
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setSongs(data || []);
|
// 确保返回的是数组
|
||||||
|
setSongs(Array.isArray(data) ? data : []);
|
||||||
setCurrentPlaylistTitle(playlistName);
|
setCurrentPlaylistTitle(playlistName);
|
||||||
setCurrentView('songs');
|
setCurrentView('songs');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载歌单失败:', error);
|
console.error('加载歌单失败:', error);
|
||||||
|
setSongs([]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -246,11 +301,13 @@ export default function MusicPage() {
|
|||||||
`/api/music?action=search&platform=${currentSource}&keyword=${encodeURIComponent(searchKeyword)}&page=1&pageSize=20`
|
`/api/music?action=search&platform=${currentSource}&keyword=${encodeURIComponent(searchKeyword)}&page=1&pageSize=20`
|
||||||
);
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setSongs(data || []);
|
// 确保返回的是数组
|
||||||
|
setSongs(Array.isArray(data) ? data : []);
|
||||||
setCurrentPlaylistTitle(`搜索: ${searchKeyword}`);
|
setCurrentPlaylistTitle(`搜索: ${searchKeyword}`);
|
||||||
setCurrentView('songs');
|
setCurrentView('songs');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('搜索失败:', error);
|
console.error('搜索失败:', error);
|
||||||
|
setSongs([]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -259,6 +316,9 @@ export default function MusicPage() {
|
|||||||
// 播放歌曲
|
// 播放歌曲
|
||||||
const playSong = async (song: Song, index: number) => {
|
const playSong = async (song: Song, index: number) => {
|
||||||
try {
|
try {
|
||||||
|
// 使用歌曲自己的平台信息,如果没有则使用当前选择的平台
|
||||||
|
const platform = song.platform || currentSource;
|
||||||
|
|
||||||
// 先设置当前歌曲和显示播放器
|
// 先设置当前歌曲和显示播放器
|
||||||
setCurrentSong(song);
|
setCurrentSong(song);
|
||||||
setCurrentSongIndex(index);
|
setCurrentSongIndex(index);
|
||||||
@@ -267,7 +327,7 @@ export default function MusicPage() {
|
|||||||
|
|
||||||
// 添加到播放记录和播放列表
|
// 添加到播放记录和播放列表
|
||||||
const record: PlayRecord = {
|
const record: PlayRecord = {
|
||||||
platform: currentSource,
|
platform: platform,
|
||||||
id: song.id,
|
id: song.id,
|
||||||
playTime: 0, // 初始播放时间
|
playTime: 0, // 初始播放时间
|
||||||
duration: 0, // 将在音频加载后更新
|
duration: 0, // 将在音频加载后更新
|
||||||
@@ -294,11 +354,11 @@ export default function MusicPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setPlaylist(prev => {
|
setPlaylist(prev => {
|
||||||
const existingIndex = prev.findIndex(s => s.id === song.id);
|
const existingIndex = prev.findIndex(s => s.id === song.id && s.platform === platform);
|
||||||
if (existingIndex >= 0) {
|
if (existingIndex >= 0) {
|
||||||
return prev;
|
return prev;
|
||||||
} else {
|
} else {
|
||||||
return [...prev, song];
|
return [...prev, { ...song, platform }];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -308,39 +368,49 @@ export default function MusicPage() {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
action: 'parse',
|
action: 'parse',
|
||||||
platform: currentSource,
|
platform: platform, // 使用歌曲的平台
|
||||||
ids: song.id,
|
ids: song.id,
|
||||||
quality: quality,
|
quality: quality,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log('解析返回数据:', data);
|
|
||||||
|
|
||||||
// TuneHub 返回格式: { code: 0, data: { data: [...] } }
|
// TuneHub 返回格式: { code: 0, data: { data: [...] } }
|
||||||
if (data.code === 0 && data.data?.data && data.data.data.length > 0) {
|
if (data.code === 0 && data.data?.data && data.data.data.length > 0) {
|
||||||
const songData = data.data.data[0];
|
const songData = data.data.data[0];
|
||||||
|
|
||||||
if (songData.url && songData.success) {
|
if (songData.url && songData.success) {
|
||||||
|
// 处理封面图片(在 HTTPS 环境下代理 HTTP 图片)
|
||||||
|
const coverUrl = processImageUrl(songData.cover, platform);
|
||||||
|
|
||||||
// 更新歌曲信息,包括封面
|
// 更新歌曲信息,包括封面
|
||||||
if (songData.cover) {
|
if (coverUrl) {
|
||||||
setCurrentSong({
|
setCurrentSong({
|
||||||
...song,
|
...song,
|
||||||
pic: songData.cover,
|
pic: coverUrl,
|
||||||
|
platform,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析歌词 - 字段名是 lyrics
|
// 解析歌词
|
||||||
if (songData.lyrics) {
|
if (songData.lyrics) {
|
||||||
const parsedLyrics = parseLyric(songData.lyrics);
|
const parsedLyrics = parseLyric(songData.lyrics);
|
||||||
setLyrics(parsedLyrics);
|
setLyrics(parsedLyrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存歌曲URL用于下载
|
// 保存原始 URL 用于下载
|
||||||
setCurrentSongUrl(songData.url);
|
setCurrentSongUrl(songData.url);
|
||||||
|
|
||||||
|
// 对于酷我音乐,使用代理
|
||||||
|
let playUrl = songData.url;
|
||||||
|
if (platform === 'kuwo') {
|
||||||
|
playUrl = `/api/music/proxy?url=${encodeURIComponent(songData.url)}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (audioRef.current) {
|
if (audioRef.current) {
|
||||||
audioRef.current.src = songData.url;
|
audioRef.current.src = playUrl;
|
||||||
|
audioRef.current.load();
|
||||||
audioRef.current.play().catch(err => {
|
audioRef.current.play().catch(err => {
|
||||||
console.error('播放失败:', err);
|
console.error('播放失败:', err);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user