mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-25 03:14:57 +08:00
🐞 fix: 修复歌词加载过慢仍旧展示上一首 #532
This commit is contained in:
3
components.d.ts
vendored
3
components.d.ts
vendored
@@ -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']
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user