Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/api/cloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ export const matchCloudSong = (uid: number, sid: number, asid: number) => {
});
};

// 获取云盘歌曲歌词
export const cloudSongLyric = (sid: number, uid: number) => {
return request({
url: "/cloud/lyric/get",
params: {
sid,
uid,
Comment on lines +45 to +50
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cloudSongLyric 的参数顺序是 (sid, uid),但同文件的 matchCloudSong 是 (uid, sid, asid)。这种不一致很容易在后续调用时传参写反;建议统一为 (uid, sid) 或改为接收一个对象参数(如 { uid, sid })以提升可读性并减少误用风险。

Suggested change
export const cloudSongLyric = (sid: number, uid: number) => {
return request({
url: "/cloud/lyric/get",
params: {
sid,
uid,
export const cloudSongLyric = (uid: number, sid: number) => {
return request({
url: "/cloud/lyric/get",
params: {
uid,
sid,

Copilot uses AI. Check for mistakes.
timestamp: Date.now(),
},
});
};

// 上传歌曲到云盘
export const uploadCloudSong = (file: File) => {
const formData = new FormData();
Expand Down
80 changes: 62 additions & 18 deletions src/core/player/LyricManager.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { qqMusicMatch } from "@/api/qqmusic";
import { cloudSongLyric } from "@/api/cloud";
import { songLyric, songLyricTTML } from "@/api/song";
import { keywords as defaultKeywords, regexes as defaultRegexes } from "@/assets/data/exclude";
import { useCacheManager } from "@/core/resource/CacheManager";
import { useMusicStore, useSettingStore, useStatusStore, useStreamingStore } from "@/stores";
import {
useDataStore,
useMusicStore,
useSettingStore,
useStatusStore,
useStreamingStore,
} from "@/stores";
import type { LyricPriority, SongLyric } from "@/types/lyric";
import type { SongType } from "@/types/main";
import { isElectron } from "@/utils/env";
Expand Down Expand Up @@ -75,7 +82,10 @@ class LyricManager {
* @param type 缓存类型
* @returns 缓存数据
*/
private async getRawLyricCache(id: number, type: "lrc" | "ttml" | "qrc"): Promise<string | null> {
private async getRawLyricCache(
id: number | string,
type: "lrc" | "ttml" | "qrc",
): Promise<string | null> {
const settingStore = useSettingStore();
if (!isElectron || !settingStore.cacheEnabled) return null;
try {
Expand All @@ -99,7 +109,11 @@ class LyricManager {
* @param type 缓存类型
* @param data 数据
*/
private async saveRawLyricCache(id: number, type: "lrc" | "ttml" | "qrc", data: string) {
private async saveRawLyricCache(
id: number | string,
type: "lrc" | "ttml" | "qrc",
data: string,
) {
const settingStore = useSettingStore();
if (!isElectron || !settingStore.cacheEnabled) return;
try {
Expand Down Expand Up @@ -304,8 +318,11 @@ class LyricManager {
if (qqMusicAdopted && result.yrcData.length > 0) return;

if (typeof id !== "number") return;
const dataStore = useDataStore();
const userId = dataStore.userData.userId;
const lyricCacheKey = song.pc && userId ? `cloud_${userId}_${id}` : id;
let data: any = null;
const cached = await this.getRawLyricCache(id, "lrc");
const cached = await this.getRawLyricCache(lyricCacheKey, "lrc");
if (cached) {
try {
data = JSON.parse(cached);
Expand All @@ -314,33 +331,60 @@ class LyricManager {
}
}
if (!data) {
data = await songLyric(id);
if (song.pc && userId) {
data = await cloudSongLyric(id, userId);
}
if (!data) {
data = await songLyric(id);
}
if (data && data.code === 200) {
this.saveRawLyricCache(id, "lrc", JSON.stringify(data));
this.saveRawLyricCache(lyricCacheKey, "lrc", JSON.stringify(data));
}
}
Comment on lines 333 to 343
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The fallback logic for cloud song lyrics is incorrect. If cloudSongLyric returns a response object with a non-200 code (e.g., 404), the current implementation will skip the call to songLyric(id) because data is already truthy. This prevents the application from falling back to standard lyrics for cloud songs that might be matched in the library but don't have cloud-specific lyrics available in the cloud API.

Suggested change
if (!data) {
data = await songLyric(id);
if (song.pc && userId) {
data = await cloudSongLyric(id, userId);
}
if (!data) {
data = await songLyric(id);
}
if (data && data.code === 200) {
this.saveRawLyricCache(id, "lrc", JSON.stringify(data));
this.saveRawLyricCache(lyricCacheKey, "lrc", JSON.stringify(data));
}
}
if (!data) {
if (song.pc && userId) {
data = await cloudSongLyric(id, userId);
}
if (!data || data.code !== 200) {
data = await songLyric(id);
}
if (data && data.code === 200) {
this.saveRawLyricCache(lyricCacheKey, "lrc", JSON.stringify(data));
}
}

if (!data || data.code !== 200) return;
Comment on lines 333 to 344
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当 song.pc && userId 时会先请求 cloudSongLyric,但当前逻辑只有在 data 为 falsy 时才回退到 songLyric。若 cloudSongLyric 返回了一个非 200 的响应对象(例如 { code: 4xx }),这里会直接 return 导致整首歌没有歌词;建议在 cloudSongLyric 结果为空“或 code !== 200”时都回退到 songLyric,再统一做 code 校验与缓存写入。

Copilot uses AI. Check for mistakes.
let lrcLines: LyricLine[] = [];
let yrcLines: LyricLine[] = [];
const getLyricText = (lyric: unknown): string => {
if (typeof lyric === "string") return lyric;
if (
lyric &&
typeof lyric === "object" &&
"lyric" in lyric &&
typeof lyric.lyric === "string"
) {
return lyric.lyric;
}
return "";
};
const lrcContent = getLyricText(data?.lrc);
const tlyricContent = getLyricText(data?.tlyric);
const romalrcContent = getLyricText(data?.romalrc);
const yrcContent = getLyricText(data?.yrc);
const ytlrcContent = getLyricText(data?.ytlrc);
const yromalrcContent = getLyricText(data?.yromalrc);
// 普通歌词
if (data?.lrc?.lyric) {
lrcLines = parseLrc(data.lrc.lyric) || [];
if (lrcContent) {
lrcLines = parseLrc(lrcContent) || [];
// 普通歌词翻译
if (data?.tlyric?.lyric)
lrcLines = alignLyrics(lrcLines, parseLrc(data.tlyric.lyric), "translatedLyric");
if (tlyricContent) {
lrcLines = alignLyrics(lrcLines, parseLrc(tlyricContent), "translatedLyric");
}
// 普通歌词音译
if (data?.romalrc?.lyric)
lrcLines = alignLyrics(lrcLines, parseLrc(data.romalrc.lyric), "romanLyric");
if (romalrcContent) {
lrcLines = alignLyrics(lrcLines, parseLrc(romalrcContent), "romanLyric");
}
}
// 逐字歌词
if (data?.yrc?.lyric) {
yrcLines = parseYrc(data.yrc.lyric) || [];
if (yrcContent) {
yrcLines = parseYrc(yrcContent) || [];
// 逐字歌词翻译
if (data?.ytlrc?.lyric)
yrcLines = alignLyrics(yrcLines, parseLrc(data.ytlrc.lyric), "translatedLyric");
if (ytlrcContent) {
yrcLines = alignLyrics(yrcLines, parseLrc(ytlrcContent), "translatedLyric");
}
// 逐字歌词音译
if (data?.yromalrc?.lyric)
yrcLines = alignLyrics(yrcLines, parseLrc(data.yromalrc.lyric), "romanLyric");
if (yromalrcContent) {
yrcLines = alignLyrics(yrcLines, parseLrc(yromalrcContent), "romanLyric");
}
}
if (lrcLines.length) result.lrcData = lrcLines;
// 如果没有 TTML 且没有 QM YRC,则采用 网易云 YRC
Expand Down
Loading