🐞 fix: 修复歌词加载过慢仍旧展示上一首 #532

This commit is contained in:
imsyy
2025-11-04 12:01:27 +08:00
parent 242c6f2ca7
commit 0aae10e8a0
11 changed files with 119 additions and 50 deletions

3
components.d.ts vendored
View File

@@ -97,18 +97,15 @@ declare module 'vue' {
NP: typeof import('naive-ui')['NP'] NP: typeof import('naive-ui')['NP']
NPopconfirm: typeof import('naive-ui')['NPopconfirm'] NPopconfirm: typeof import('naive-ui')['NPopconfirm']
NPopover: typeof import('naive-ui')['NPopover'] NPopover: typeof import('naive-ui')['NPopover']
NProgress: typeof import('naive-ui')['NProgress']
NQrCode: typeof import('naive-ui')['NQrCode'] NQrCode: typeof import('naive-ui')['NQrCode']
NRadio: typeof import('naive-ui')['NRadio'] NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup'] NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NResult: typeof import('naive-ui')['NResult']
NScrollbar: typeof import('naive-ui')['NScrollbar'] NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect'] NSelect: typeof import('naive-ui')['NSelect']
NSkeleton: typeof import('naive-ui')['NSkeleton'] NSkeleton: typeof import('naive-ui')['NSkeleton']
NSlider: typeof import('naive-ui')['NSlider'] NSlider: typeof import('naive-ui')['NSlider']
NSpin: typeof import('naive-ui')['NSpin'] NSpin: typeof import('naive-ui')['NSpin']
NSwitch: typeof import('naive-ui')['NSwitch'] NSwitch: typeof import('naive-ui')['NSwitch']
NTab: typeof import('naive-ui')['NTab']
NTabPane: typeof import('naive-ui')['NTabPane'] NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs'] NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag'] NTag: typeof import('naive-ui')['NTag']

View File

@@ -6,7 +6,7 @@ import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite"; import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite"; import Components from "unplugin-vue-components/vite";
import viteCompression from "vite-plugin-compression"; import viteCompression from "vite-plugin-compression";
import VueDevTools from "vite-plugin-vue-devtools"; // import VueDevTools from "vite-plugin-vue-devtools";
import wasm from "vite-plugin-wasm"; import wasm from "vite-plugin-wasm";
export default defineConfig(({ command, mode }) => { export default defineConfig(({ command, mode }) => {
@@ -49,7 +49,7 @@ export default defineConfig(({ command, mode }) => {
root: ".", root: ".",
plugins: [ plugins: [
vue(), vue(),
mode === "development" && VueDevTools(), // mode === "development" && VueDevTools(),
AutoImport({ AutoImport({
imports: [ imports: [
"vue", "vue",

View File

@@ -16,11 +16,16 @@ const initLyricIpc = (): void => {
// 切换桌面歌词 // 切换桌面歌词
ipcMain.on("toggle-desktop-lyric", (_event, val: boolean) => { ipcMain.on("toggle-desktop-lyric", (_event, val: boolean) => {
if (val) { if (val) {
lyricWin = lyricWindow.create(); if (!lyricWin) {
lyricWin = lyricWindow.create();
} else {
lyricWin?.show();
}
lyricWin?.setAlwaysOnTop(true, "screen-saver"); lyricWin?.setAlwaysOnTop(true, "screen-saver");
} else { } else {
lyricWin?.destroy(); // 关闭:不销毁窗口,直接隐藏,保留位置与状态
lyricWin = null; if (!lyricWin) return;
lyricWin.hide();
} }
}); });

View File

@@ -73,7 +73,7 @@ export const songLyric = (id: number) => {
*/ */
export const songLyricTTML = async (id: number) => { export const songLyricTTML = async (id: number) => {
if (isElectron) { if (isElectron) {
return request({ url: "/lyric/ttml", params: { id } }); return request({ url: "/lyric/ttml", params: { id, noCookie: true } });
} else { } else {
const url = `https://amll-ttml-db.stevexmh.net/ncm/${id}`; const url = `https://amll-ttml-db.stevexmh.net/ncm/${id}`;
try { try {

View File

@@ -55,7 +55,7 @@
<!-- 评论 --> <!-- 评论 -->
<PlayerComment v-if="isShowComment && !statusStore.pureLyricMode" /> <PlayerComment v-if="isShowComment && !statusStore.pureLyricMode" />
<!-- 歌词 --> <!-- 歌词 -->
<div v-else-if="musicStore.isHasLrc" class="content-right"> <div class="content-right">
<!-- 数据 --> <!-- 数据 -->
<PlayerData <PlayerData
v-if="statusStore.pureLyricMode && musicStore.isHasLrc" v-if="statusStore.pureLyricMode && musicStore.isHasLrc"
@@ -218,6 +218,7 @@ onBeforeUnmount(() => {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
will-change: width, opacity, transform;
transition: transition:
width 0.3s, width 0.3s,
opacity 0.5s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.5s cubic-bezier(0.34, 1.56, 0.64, 1),
@@ -232,6 +233,7 @@ onBeforeUnmount(() => {
max-width: 50%; max-width: 50%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
transition: opacity 0.3s;
.player-data { .player-data {
margin-top: 0; margin-top: 0;
margin-bottom: 26px; margin-bottom: 26px;
@@ -272,6 +274,9 @@ onBeforeUnmount(() => {
.content-left { .content-left {
width: 100%; width: 100%;
} }
.content-right {
opacity: 0;
}
} }
} }
} }

View File

@@ -4,7 +4,9 @@
:key="amLyricsData?.[0]?.words?.length" :key="amLyricsData?.[0]?.words?.length"
:class="['lyric-am', { pure: statusStore.pureLyricMode }]" :class="['lyric-am', { pure: statusStore.pureLyricMode }]"
> >
<div v-if="statusStore.lyricLoading" class="lyric-loading">歌词正在加载中...</div>
<LyricPlayer <LyricPlayer
v-else
ref="lyricPlayerRef" ref="lyricPlayerRef"
:lyricLines="amLyricsData" :lyricLines="amLyricsData"
:currentTime="playSeek" :currentTime="playSeek"
@@ -37,7 +39,6 @@ import { useMusicStore, useSettingStore, useStatusStore } from "@/stores";
import { msToS } from "@/utils/time"; import { msToS } from "@/utils/time";
import { getLyricLanguage } from "@/utils/lyric"; import { getLyricLanguage } from "@/utils/lyric";
import player from "@/utils/player"; import player from "@/utils/player";
import { watch } from "vue";
import LyricMenu from "./LyricMenu.vue"; import LyricMenu from "./LyricMenu.vue";
const musicStore = useMusicStore(); const musicStore = useMusicStore();
@@ -47,11 +48,15 @@ const settingStore = useSettingStore();
const lyricPlayerRef = ref<any | null>(null); const lyricPlayerRef = ref<any | null>(null);
// 实时播放进度 // 实时播放进度
const playSeek = ref<number>(player.getSeek()); const playSeek = ref<number>(
Math.floor((player.getSeek() + statusStore.getSongOffset(musicStore.playSong?.id)) * 1000),
);
// 实时更新播放进度 // 实时更新播放进度
const { pause: pauseSeek, resume: resumeSeek } = useRafFn(() => { const { pause: pauseSeek, resume: resumeSeek } = useRafFn(() => {
const seekInSeconds = player.getSeek(); const songId = musicStore.playSong?.id;
const offsetSeconds = statusStore.getSongOffset(songId);
const seekInSeconds = player.getSeek() + offsetSeconds;
playSeek.value = Math.floor(seekInSeconds * 1000); playSeek.value = Math.floor(seekInSeconds * 1000);
}); });
@@ -164,4 +169,14 @@ onBeforeUnmount(() => {
font-family: var(--ja-font-family); font-family: var(--ja-font-family);
} }
} }
.lyric-loading {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: var(--amll-lyric-view-color, #efefef);
font-size: 22px;
}
</style> </style>

View File

@@ -26,7 +26,8 @@
@after-enter="lyricsScroll(statusStore.lyricIndex)" @after-enter="lyricsScroll(statusStore.lyricIndex)"
@after-leave="lyricsScroll(statusStore.lyricIndex)" @after-leave="lyricsScroll(statusStore.lyricIndex)"
> >
<n-scrollbar ref="lyricScroll" class="lyric-scroll" tabindex="-1"> <div v-if="statusStore.lyricLoading" class="lyric-loading">歌词正在加载中...</div>
<n-scrollbar v-else ref="lyricScroll" class="lyric-scroll" tabindex="-1">
<!-- 逐字歌词 --> <!-- 逐字歌词 -->
<template v-if="settingStore.showYrc && musicStore.isHasYrc"> <template v-if="settingStore.showYrc && musicStore.isHasYrc">
<div id="lrc-placeholder" class="placeholder"> <div id="lrc-placeholder" class="placeholder">
@@ -597,3 +598,14 @@ onBeforeUnmount(() => {
} }
} }
</style> </style>
<style scoped>
.lyric-loading {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
}
</style>

View File

@@ -52,6 +52,8 @@ interface StatusState {
playIndex: number; playIndex: number;
/** 歌词播放索引 */ /** 歌词播放索引 */
lyricIndex: number; lyricIndex: number;
/** 歌词加载状态 */
lyricLoading: boolean;
/** 当前播放时间 */ /** 当前播放时间 */
currentTime: number; currentTime: number;
/** 歌曲总时长 */ /** 歌曲总时长 */
@@ -116,6 +118,7 @@ export const useStatusStore = defineStore("status", {
spectrumsData: [], spectrumsData: [],
playIndex: -1, playIndex: -1,
lyricIndex: -1, lyricIndex: -1,
lyricLoading: false,
playRate: 1, playRate: 1,
playVolume: 0.7, playVolume: 0.7,
playVolumeMute: 0, playVolumeMute: 0,

View File

@@ -44,6 +44,8 @@ export const resetSongLyric = () => {
// 重置歌词数据 // 重置歌词数据
musicStore.setSongLyric({}, true); musicStore.setSongLyric({}, true);
statusStore.usingTTMLLyric = false; statusStore.usingTTMLLyric = false;
// 标记为加载中(切歌时防止显示上一首歌词)
statusStore.lyricLoading = true;
// 重置歌词索引 // 重置歌词索引
statusStore.lyricIndex = -1; statusStore.lyricIndex = -1;
}; };
@@ -109,6 +111,8 @@ export const parsedLyricsData = (lyricData: any, skipExclude: boolean = false):
); );
// 重置歌词索引 // 重置歌词索引
statusStore.lyricIndex = -1; statusStore.lyricIndex = -1;
// 歌词已加载完成
statusStore.lyricLoading = false;
}; };
/** /**

View File

@@ -13,50 +13,69 @@ export const getLyricData = async (id: number) => {
const musicStore = useMusicStore(); const musicStore = useMusicStore();
const settingStore = useSettingStore(); const settingStore = useSettingStore();
const statusStore = useStatusStore(); const statusStore = useStatusStore();
// 切歌或重新获取时,先标记为加载中
statusStore.lyricLoading = true;
if (!id) { if (!id) {
statusStore.usingTTMLLyric = false; statusStore.usingTTMLLyric = false;
resetSongLyric(); resetSongLyric();
statusStore.lyricLoading = false;
return; return;
} }
try { try {
// 检测本地歌词覆盖 // 检测本地歌词覆盖
const getLyric = getLyricFun(settingStore.localLyricPath, id); const getLyric = getLyricFun(settingStore.localLyricPath, id);
const [{ lyric: lyricRes, isLocal: lyricLocal }, { lyric: ttmlContent, isLocal: ttmlLocal }] = // 先加载 LRC不阻塞到 TTML 完成
await Promise.all([ const lrcPromise = getLyric("lrc", songLyric);
getLyric("lrc", songLyric), const ttmlPromise = settingStore.enableTTMLLyric ? getLyric("ttml", songLyricTTML) : null;
settingStore.enableTTMLLyric ? getLyric("ttml", songLyricTTML) : getLyric("ttml"),
]); const { lyric: lyricRes, isLocal: lyricLocal } = await lrcPromise;
parsedLyricsData(lyricRes, lyricLocal && !settingStore.enableExcludeLocalLyrics); parsedLyricsData(lyricRes, lyricLocal && !settingStore.enableExcludeLocalLyrics);
if (ttmlContent) { // LRC 到达后即可认为加载完成
const parsedResult = parseTTML(ttmlContent); statusStore.lyricLoading = false;
if (!parsedResult?.lines?.length) {
statusStore.usingTTMLLyric = false; // TTML 并行加载,完成后增量更新,不阻塞整体流程
return; if (ttmlPromise) {
} statusStore.usingTTMLLyric = false;
const skipExcludeLocal = ttmlLocal && !settingStore.enableExcludeLocalLyrics; void ttmlPromise
const skipExcludeTTML = !settingStore.enableExcludeTTML; .then(({ lyric: ttmlContent, isLocal: ttmlLocal }) => {
const skipExclude = skipExcludeLocal || skipExcludeTTML; if (!ttmlContent) {
const ttmlLyric = parseTTMLToAMLL(parsedResult, skipExclude); statusStore.usingTTMLLyric = false;
const ttmlYrcLyric = parseTTMLToYrc(parsedResult, skipExclude); return;
console.log("TTML lyrics:", ttmlLyric, ttmlYrcLyric); }
// 合并数据 const parsedResult = parseTTML(ttmlContent);
const updates: Partial<{ yrcAMData: LyricLine[]; yrcData: LyricType[] }> = {}; if (!parsedResult?.lines?.length) {
if (ttmlLyric?.length) { statusStore.usingTTMLLyric = false;
updates.yrcAMData = ttmlLyric; return;
console.log("✅ TTML AMLL lyrics success"); }
} const skipExcludeLocal = ttmlLocal && !settingStore.enableExcludeLocalLyrics;
if (ttmlYrcLyric?.length) { const skipExcludeTTML = !settingStore.enableExcludeTTML;
updates.yrcData = ttmlYrcLyric; const skipExclude = skipExcludeLocal || skipExcludeTTML;
console.log("✅ TTML Yrc lyrics success"); const ttmlLyric = parseTTMLToAMLL(parsedResult, skipExclude);
} const ttmlYrcLyric = parseTTMLToYrc(parsedResult, skipExclude);
if (Object.keys(updates).length) { console.log("TTML lyrics:", ttmlLyric, ttmlYrcLyric);
musicStore.setSongLyric(updates); // 合并数据
statusStore.usingTTMLLyric = true; const updates: Partial<{ yrcAMData: LyricLine[]; yrcData: LyricType[] }> = {};
} else { if (ttmlLyric?.length) {
statusStore.usingTTMLLyric = false; updates.yrcAMData = ttmlLyric;
} console.log("✅ TTML AMLL lyrics success");
}
if (ttmlYrcLyric?.length) {
updates.yrcData = ttmlYrcLyric;
console.log("✅ TTML Yrc lyrics success");
}
if (Object.keys(updates).length) {
musicStore.setSongLyric(updates);
statusStore.usingTTMLLyric = true;
} else {
statusStore.usingTTMLLyric = false;
}
})
.catch((err) => {
console.error("❌ Error loading TTML lyrics:", err);
statusStore.usingTTMLLyric = false;
});
} else { } else {
statusStore.usingTTMLLyric = false; statusStore.usingTTMLLyric = false;
} }
@@ -66,6 +85,7 @@ export const getLyricData = async (id: number) => {
console.error("❌ Error loading lyrics:", error); console.error("❌ Error loading lyrics:", error);
statusStore.usingTTMLLyric = false; statusStore.usingTTMLLyric = false;
resetSongLyric(); resetSongLyric();
statusStore.lyricLoading = false;
} }
}; };

View File

@@ -647,7 +647,10 @@ class Player {
window.$message.warning("当前列表歌曲无法播放,请更换歌曲"); window.$message.warning("当前列表歌曲无法播放,请更换歌曲");
} else { } else {
window.$message.error("该歌曲暂无音源,跳至下一首"); window.$message.error("该歌曲暂无音源,跳至下一首");
this.nextOrPrev("next"); // 防止切歌保护状态阻塞跳转
this.switching = false;
await this.nextOrPrev("next");
return;
} }
} }
} else { } else {
@@ -658,7 +661,10 @@ class Player {
window.$message.warning("当前列表歌曲无法播放,请更换歌曲"); window.$message.warning("当前列表歌曲无法播放,请更换歌曲");
} else { } else {
window.$message.error("该歌曲暂无音源,跳至下一首"); window.$message.error("该歌曲暂无音源,跳至下一首");
this.nextOrPrev("next"); // 防止切歌保护状态阻塞跳转
this.switching = false;
await this.nextOrPrev("next");
return;
} }
} }
} else { } else {
@@ -668,7 +674,9 @@ class Player {
return; return;
} else { } else {
window.$message.error("该歌曲无法播放,跳至下一首"); window.$message.error("该歌曲无法播放,跳至下一首");
this.nextOrPrev(); // 防止切歌保护状态阻塞跳转
this.switching = false;
await this.nextOrPrev();
return; return;
} }
} }