Files
AlgerMusicPlayer/src/renderer/hooks/usePlayerHooks.ts
2025-12-13 11:31:49 +08:00

424 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { cloneDeep } from 'lodash';
import { createDiscreteApi } from 'naive-ui';
import i18n from '@/../i18n/renderer';
import { getBilibiliAudioUrl } from '@/api/bilibili';
import { getMusicLrc, getMusicUrl, getParsingMusicUrl } from '@/api/music';
import { playbackRequestManager } from '@/services/playbackRequestManager';
import type { ILyric, ILyricText, IWordData, SongResult } from '@/types/music';
import { getImgUrl } from '@/utils';
import { getImageLinearBackground } from '@/utils/linearColor';
import { parseLyrics as parseYrcLyrics } from '@/utils/yrcParser';
const { message } = createDiscreteApi(['message']);
/**
* 获取歌曲播放URL独立函数
*/
export const getSongUrl = async (
id: string | number,
songData: SongResult,
isDownloaded: boolean = false,
requestId?: string
) => {
const numericId = typeof id === 'string' ? parseInt(id, 10) : id;
// 动态导入 settingsStore
const { useSettingsStore } = await import('@/store/modules/settings');
const settingsStore = useSettingsStore();
try {
// 在开始处理前验证请求
if (requestId && !playbackRequestManager.isRequestValid(requestId)) {
console.log(`[getSongUrl] 请求已失效: ${requestId}`);
throw new Error('Request cancelled');
}
if (songData.playMusicUrl) {
return songData.playMusicUrl;
}
if (songData.source === 'bilibili' && songData.bilibiliData) {
console.log('加载B站音频URL');
if (!songData.playMusicUrl && songData.bilibiliData.bvid && songData.bilibiliData.cid) {
try {
songData.playMusicUrl = await getBilibiliAudioUrl(
songData.bilibiliData.bvid,
songData.bilibiliData.cid
);
// 验证请求
if (requestId && !playbackRequestManager.isRequestValid(requestId)) {
console.log(`[getSongUrl] 获取B站URL后请求已失效: ${requestId}`);
throw new Error('Request cancelled');
}
return songData.playMusicUrl;
} catch (error) {
console.error('重启后获取B站音频URL失败:', error);
return '';
}
}
return songData.playMusicUrl || '';
}
// ==================== 自定义API最优先 ====================
const globalSources = settingsStore.setData.enabledMusicSources || [];
const useCustomApiGlobally = globalSources.includes('custom');
const songId = String(id);
const savedSourceStr = localStorage.getItem(`song_source_${songId}`);
let useCustomApiForSong = false;
if (savedSourceStr) {
try {
const songSources = JSON.parse(savedSourceStr);
useCustomApiForSong = songSources.includes('custom');
} catch (e) {
console.error('解析歌曲音源设置失败:', e);
}
}
// 如果全局或歌曲专属设置中启用了自定义API则最优先尝试
if ((useCustomApiGlobally || useCustomApiForSong) && settingsStore.setData.customApiPlugin) {
console.log(`优先级 1: 尝试使用自定义API解析歌曲 ${id}...`);
try {
const { parseFromCustomApi } = await import('@/api/parseFromCustomApi');
const customResult = await parseFromCustomApi(
numericId,
cloneDeep(songData),
settingsStore.setData.musicQuality || 'higher'
);
// 验证请求
if (requestId && !playbackRequestManager.isRequestValid(requestId)) {
console.log(`[getSongUrl] 自定义API解析后请求已失效: ${requestId}`);
throw new Error('Request cancelled');
}
if (
customResult &&
customResult.data &&
customResult.data.data &&
customResult.data.data.url
) {
console.log('自定义API解析成功');
if (isDownloaded) return customResult.data.data as any;
return customResult.data.data.url;
} else {
console.log('自定义API解析失败将使用默认降级流程...');
message.warning(i18n.global.t('player.reparse.customApiFailed'));
}
} catch (error) {
console.error('调用自定义API时发生错误:', error);
if ((error as Error).message === 'Request cancelled') {
throw error;
}
message.error(i18n.global.t('player.reparse.customApiError'));
}
}
// 如果有自定义音源设置直接使用getParsingMusicUrl获取URL
if (savedSourceStr && songData.source !== 'bilibili') {
try {
console.log(`使用自定义音源解析歌曲 ID: ${songId}`);
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
console.log('res', res);
// 验证请求
if (requestId && !playbackRequestManager.isRequestValid(requestId)) {
console.log(`[getSongUrl] 自定义音源解析后请求已失效: ${requestId}`);
throw new Error('Request cancelled');
}
if (res && res.data && res.data.data && res.data.data.url) {
return res.data.data.url;
}
console.warn('自定义音源解析失败,使用默认音源');
} catch (error) {
console.error('error', error);
if ((error as Error).message === 'Request cancelled') {
throw error;
}
console.error('自定义音源解析出错:', error);
}
}
// 正常获取URL流程
const { data } = await getMusicUrl(numericId, isDownloaded);
// 验证请求
if (requestId && !playbackRequestManager.isRequestValid(requestId)) {
console.log(`[getSongUrl] 获取官方URL后请求已失效: ${requestId}`);
throw new Error('Request cancelled');
}
if (data && data.data && data.data[0]) {
const songDetail = data.data[0];
const hasNoUrl = !songDetail.url;
const isTrial = !!songDetail.freeTrialInfo;
if (hasNoUrl || isTrial) {
console.log(`官方URL无效 (无URL: ${hasNoUrl}, 试听: ${isTrial}),进入内置备用解析...`);
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
// 验证请求
if (requestId && !playbackRequestManager.isRequestValid(requestId)) {
console.log(`[getSongUrl] 备用解析后请求已失效: ${requestId}`);
throw new Error('Request cancelled');
}
if (isDownloaded) return res?.data?.data as any;
return res?.data?.data?.url || null;
}
console.log('官方API解析成功');
if (isDownloaded) return songDetail as any;
return songDetail.url;
}
console.log('官方API返回数据结构异常进入内置备用解析...');
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
// 验证请求
if (requestId && !playbackRequestManager.isRequestValid(requestId)) {
console.log(`[getSongUrl] 备用解析后请求已失效: ${requestId}`);
throw new Error('Request cancelled');
}
if (isDownloaded) return res?.data?.data as any;
return res?.data?.data?.url || null;
} catch (error) {
if ((error as Error).message === 'Request cancelled') {
throw error;
}
console.error('官方API请求失败进入内置备用解析流程:', error);
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
if (isDownloaded) return res?.data?.data as any;
return res?.data?.data?.url || null;
}
};
/**
* useSongUrl hook兼容旧代码
*/
export const useSongUrl = () => {
return { getSongUrl };
};
/**
* 使用新的yrcParser解析歌词独立函数
*/
const parseLyrics = (lyricsString: string): { lyrics: ILyricText[]; times: number[] } => {
if (!lyricsString || typeof lyricsString !== 'string') {
return { lyrics: [], times: [] };
}
try {
const parseResult = parseYrcLyrics(lyricsString);
if (!parseResult.success) {
console.error('歌词解析失败:', parseResult.error.message);
return { lyrics: [], times: [] };
}
const { lyrics: parsedLyrics } = parseResult.data;
const lyrics: ILyricText[] = [];
const times: number[] = [];
for (const line of parsedLyrics) {
// 检查是否有逐字歌词
const hasWords = line.words && line.words.length > 0;
lyrics.push({
text: line.fullText,
trText: '', // 翻译文本稍后处理
words: hasWords ? (line.words as IWordData[]) : undefined,
hasWordByWord: hasWords,
startTime: line.startTime,
duration: line.duration
});
// 时间数组使用秒为单位(与原有逻辑保持一致)
times.push(line.startTime / 1000);
}
return { lyrics, times };
} catch (error) {
console.error('解析歌词时发生错误:', error);
return { lyrics: [], times: [] };
}
};
/**
* 加载歌词(独立函数)
*/
export const loadLrc = async (id: string | number): Promise<ILyric> => {
if (typeof id === 'string' && id.includes('--')) {
console.log('B站音频无需加载歌词');
return {
lrcTimeArray: [],
lrcArray: [],
hasWordByWord: false
};
}
try {
const numericId = typeof id === 'string' ? parseInt(id, 10) : id;
const { data } = await getMusicLrc(numericId);
const { lyrics, times } = parseLyrics(data?.yrc?.lyric || data?.lrc?.lyric);
// 检查是否有逐字歌词
let hasWordByWord = false;
for (const lyric of lyrics) {
if (lyric.hasWordByWord) {
hasWordByWord = true;
break;
}
}
if (data.tlyric && data.tlyric.lyric) {
const { lyrics: tLyrics } = parseLyrics(data.tlyric.lyric);
// 按索引顺序一一对应翻译歌词
if (tLyrics.length === lyrics.length) {
// 数量相同,直接按索引对应
lyrics.forEach((item, index) => {
item.trText = item.text && tLyrics[index] ? tLyrics[index].text : '';
});
} else {
// 数量不同,构建时间戳映射并尝试匹配
const tLyricMap = new Map<number, string>();
tLyrics.forEach((lyric) => {
if (lyric.text && lyric.startTime !== undefined) {
const timeInSeconds = lyric.startTime / 1000;
tLyricMap.set(timeInSeconds, lyric.text);
}
});
// 为每句歌词查找最接近的翻译
lyrics.forEach((item, index) => {
if (!item.text) {
item.trText = '';
return;
}
const currentTime = times[index];
let closestTime = -1;
let minDiff = 2.0; // 最大允许差异2秒
// 查找最接近的时间戳
for (const [tTime] of tLyricMap.entries()) {
const diff = Math.abs(tTime - currentTime);
if (diff < minDiff) {
minDiff = diff;
closestTime = tTime;
}
}
item.trText = closestTime !== -1 ? tLyricMap.get(closestTime) || '' : '';
});
}
} else {
// 没有翻译歌词,清空 trText
lyrics.forEach((item) => {
item.trText = '';
});
}
return {
lrcTimeArray: times,
lrcArray: lyrics,
hasWordByWord
};
} catch (err) {
console.error('Error loading lyrics:', err);
return {
lrcTimeArray: [],
lrcArray: [],
hasWordByWord: false
};
}
};
/**
* useLyrics hook兼容旧代码
*/
export const useLyrics = () => {
return { loadLrc, parseLyrics };
};
/**
* 获取歌曲详情
*/
export const useSongDetail = () => {
const { getSongUrl } = useSongUrl();
const getSongDetail = async (playMusic: SongResult, requestId?: string) => {
// 验证请求
if (requestId && !playbackRequestManager.isRequestValid(requestId)) {
console.log(`[getSongDetail] 请求已失效: ${requestId}`);
throw new Error('Request cancelled');
}
if (playMusic.source === 'bilibili') {
try {
if (!playMusic.playMusicUrl && playMusic.bilibiliData) {
playMusic.playMusicUrl = await getBilibiliAudioUrl(
playMusic.bilibiliData.bvid,
playMusic.bilibiliData.cid
);
}
// 验证请求
if (requestId && !playbackRequestManager.isRequestValid(requestId)) {
console.log(`[getSongDetail] B站URL获取后请求已失效: ${requestId}`);
throw new Error('Request cancelled');
}
playMusic.playLoading = false;
return { ...playMusic } as SongResult;
} catch (error) {
console.error('获取B站音频详情失败:', error);
playMusic.playLoading = false;
throw error;
}
}
if (playMusic.expiredAt && playMusic.expiredAt < Date.now()) {
console.info(`歌曲已过期,重新获取: ${playMusic.name}`);
playMusic.playMusicUrl = undefined;
}
try {
const playMusicUrl =
playMusic.playMusicUrl || (await getSongUrl(playMusic.id, playMusic, false, requestId));
// 验证请求
if (requestId && !playbackRequestManager.isRequestValid(requestId)) {
console.log(`[getSongDetail] URL获取后请求已失效: ${requestId}`);
throw new Error('Request cancelled');
}
playMusic.createdAt = Date.now();
// 半小时后过期
playMusic.expiredAt = playMusic.createdAt + 1800000;
const { backgroundColor, primaryColor } =
playMusic.backgroundColor && playMusic.primaryColor
? playMusic
: await getImageLinearBackground(getImgUrl(playMusic?.picUrl, '30y30'));
// 验证请求
if (requestId && !playbackRequestManager.isRequestValid(requestId)) {
console.log(`[getSongDetail] 背景色获取后请求已失效: ${requestId}`);
throw new Error('Request cancelled');
}
playMusic.playLoading = false;
return { ...playMusic, playMusicUrl, backgroundColor, primaryColor } as SongResult;
} catch (error) {
if ((error as Error).message === 'Request cancelled') {
throw error;
}
console.error('获取音频URL失败:', error);
playMusic.playLoading = false;
throw error;
}
};
return { getSongDetail };
};