{{ title }}
@@ -127,6 +127,10 @@ const songList = computed(() => sampleSize(props.data, 3));
display: flex;
flex-direction: column;
justify-content: space-evenly;
+ &.center {
+ align-items: center;
+ text-align: center;
+ }
.name {
font-size: 18px;
font-weight: bold;
diff --git a/src/views/Home/HomeOnline.vue b/src/views/Home/HomeOnline.vue
index e2a740322..27c84dbca 100644
--- a/src/views/Home/HomeOnline.vue
+++ b/src/views/Home/HomeOnline.vue
@@ -93,6 +93,7 @@ const settingStore = useSettingStore();
// 日推标题
const dailySongsTitle = computed(() => {
+ if (settingStore.hiddenCovers.home) return "每日推荐";
const day = new Date().getDate();
return h("div", { class: "date" }, [
h("div", { class: "date-icon" }, [
From 6054c1ad4357db44684d9ea9b33b8b715386e0f6 Mon Sep 17 00:00:00 2001
From: kazukokawagawa <2580099704@qq.com>
Date: Sun, 1 Feb 2026 21:01:24 +0800
Subject: [PATCH 05/10] =?UTF-8?q?=F0=9F=90=9E=20fix:=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E5=88=87=E6=AD=8C=E5=BB=B6=E8=BF=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/core/player/PlayerController.ts | 43 +++++++++++++++++------------
1 file changed, 26 insertions(+), 17 deletions(-)
diff --git a/src/core/player/PlayerController.ts b/src/core/player/PlayerController.ts
index ddf908b7a..75458ef13 100644
--- a/src/core/player/PlayerController.ts
+++ b/src/core/player/PlayerController.ts
@@ -148,45 +148,54 @@ class PlayerController {
}
try {
- // 停止当前播放
+ // 设置加载状态
+ statusStore.playLoading = true;
+
+ // 1. 预加载音频 (最耗时操作,期间保持旧歌播放)
+ const audioSource = await songManager.getAudioSource(playSongData);
+
+ // 检查请求是否过期
+ if (requestToken !== this.currentRequestToken) {
+ console.log(`🚫 [${playSongData.id}] 请求已过期,舍弃`);
+ return;
+ }
+ if (!audioSource.url) throw new Error("AUDIO_SOURCE_EMPTY");
+
+ // === 切换点 (Point of Switch) ===
+
+ // 2. 停止当前播放
audioManager.stop();
+
+ // 3. 更新核心状态
musicStore.playSong = playSongData;
statusStore.currentTime = options.seek ?? 0;
- const duration = this.getDuration() || statusStore.duration;
- if (duration > 0) {
- statusStore.progress = calculateProgress(statusStore.currentTime, duration);
- } else {
- statusStore.progress = 0;
- }
+ // 重置进度 (新歌时长未知,暂设计算)
+ statusStore.progress = 0;
+
statusStore.lyricIndex = -1;
+
// 重置重试计数
const sid = playSongData.type === "radio" ? playSongData.dj?.id : playSongData.id;
if (this.retryInfo.songId !== sid) {
this.retryInfo = { songId: sid || 0, count: 0 };
}
- // 设置加载状态
- statusStore.playLoading = true;
+
statusStore.lyricLoading = true;
// 重置 AB 循环
statusStore.abLoop.enable = false;
statusStore.abLoop.pointA = null;
statusStore.abLoop.pointB = null;
+
// 通知桌面歌词
if (isElectron) {
window.electron.ipcRenderer.send("update-desktop-lyric-data", {
lyricLoading: true,
});
}
- // 获取歌词
+ // 获取歌词 (在切换后获取,避免旧歌配新词)
lyricManager.handleLyric(playSongData);
- // 获取音频
- const audioSource = await songManager.getAudioSource(playSongData);
- if (requestToken !== this.currentRequestToken) {
- console.log(`🚫 [${playSongData.id}] 请求已过期,舍弃`);
- return;
- }
- if (!audioSource.url) throw new Error("AUDIO_SOURCE_EMPTY");
+
console.log(`🎧 [${playSongData.id}] 最终播放信息:`, audioSource);
// 更新音质和解锁状态
statusStore.songQuality = audioSource.quality;
From 683017f66f88ca06fb3e61ff6319ec69c755bd5a Mon Sep 17 00:00:00 2001
From: kazukokawagawa <2580099704@qq.com>
Date: Sun, 1 Feb 2026 21:03:31 +0800
Subject: [PATCH 06/10] =?UTF-8?q?=F0=9F=A6=84=20refactor:=20=E9=BB=98?=
=?UTF-8?q?=E8=AE=A4=E6=98=BE=E7=A4=BA=E9=9F=B3=E6=BA=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/Player/PlayerMeta/PlayerData.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/Player/PlayerMeta/PlayerData.vue b/src/components/Player/PlayerMeta/PlayerData.vue
index f441cee10..5ef24585b 100644
--- a/src/components/Player/PlayerMeta/PlayerData.vue
+++ b/src/components/Player/PlayerMeta/PlayerData.vue
@@ -234,7 +234,7 @@ const audioSourceText = computed(() => {
if (statusStore.audioSource) {
return sourceMap[statusStore.audioSource] || statusStore.audioSource.toUpperCase();
}
- return "ONLINE";
+ return "Netease";
});
const jumpPage = debounce(
From 308c48b1addbdf129cfc605c44cffd4e126a74df Mon Sep 17 00:00:00 2001
From: kazukokawagawa <2580099704@qq.com>
Date: Sun, 1 Feb 2026 21:12:07 +0800
Subject: [PATCH 07/10] =?UTF-8?q?=F0=9F=90=9E=20fix:=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=AE=A1=E6=9F=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/Modal/ThemeConfig.vue | 2 +-
src/core/player/SongManager.ts | 15 ++++++++++++++-
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/src/components/Modal/ThemeConfig.vue b/src/components/Modal/ThemeConfig.vue
index 0966ae8db..01aec17e4 100644
--- a/src/components/Modal/ThemeConfig.vue
+++ b/src/components/Modal/ThemeConfig.vue
@@ -317,7 +317,7 @@ const randomizeTheme = () => {
// 3. 随机变体
const randomVariant = variantOptions[Math.floor(Math.random() * variantOptions.length)];
- settingStore.themeVariant = randomVariant.value as any;
+ settingStore.themeVariant = randomVariant.value as typeof settingStore.themeVariant;
// 4. 随机颜色 (生成随机 Hex 并应用到自定义)
const randomHex = `#${Math.floor(Math.random() * 16777215)
diff --git a/src/core/player/SongManager.ts b/src/core/player/SongManager.ts
index 7f689aa09..f4e662d97 100644
--- a/src/core/player/SongManager.ts
+++ b/src/core/player/SongManager.ts
@@ -407,8 +407,21 @@ class SongManager {
if (unlockUrl && (unlockUrl.includes(".flac") || unlockUrl.includes(".wav"))) {
quality = QualityType.SQ;
}
+
+ // 检查本地缓存
+ const cachedUrl = await this.checkLocalCache(songId, quality);
+ if (cachedUrl) {
+ return {
+ id: songId,
+ url: cachedUrl,
+ isUnlocked: true,
+ quality,
+ source: source,
+ };
+ }
+
// 触发缓存下载
- this.triggerCacheDownload(songId, unlockUrl);
+ this.triggerCacheDownload(songId, unlockUrl, quality);
return {
id: songId,
From 50f0a6ff7cdb84b4379eb04974f4f9c009a2aa62 Mon Sep 17 00:00:00 2001
From: kazukokawagawa <2580099704@qq.com>
Date: Sun, 1 Feb 2026 21:23:51 +0800
Subject: [PATCH 08/10] =?UTF-8?q?=E2=9C=A8=20feat:=20=E4=B8=8B=E8=BD=BD?=
=?UTF-8?q?=E6=AD=8C=E8=AF=8D=E5=8F=A6=E5=AD=98=E4=B8=BAass?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/Setting/config/local.ts | 11 ++++
src/core/resource/DownloadManager.ts | 70 ++++++++++++++++++++--
src/stores/migrations/settingMigrations.ts | 7 ++-
src/stores/setting.ts | 3 +
src/utils/assGenerator.ts | 68 +++++++++++++++++++++
5 files changed, 154 insertions(+), 5 deletions(-)
create mode 100644 src/utils/assGenerator.ts
diff --git a/src/components/Setting/config/local.ts b/src/components/Setting/config/local.ts
index 7c2aeefb2..a4c5efdc2 100644
--- a/src/components/Setting/config/local.ts
+++ b/src/components/Setting/config/local.ts
@@ -442,6 +442,17 @@ export const useLocalSettings = (): SettingConfig => {
set: (v) => (settingStore.downloadMakeYrc = v),
}),
},
+ {
+ key: "downloadSaveAsAss",
+ label: "下载时另存为 ASS 文件",
+ type: "switch",
+ description: "生成 ASS 字幕文件以支持第三方播放器识别(源文件仍内嵌 LRC)",
+ disabled: computed(() => !settingStore.downloadMeta || !settingStore.downloadLyric),
+ value: computed({
+ get: () => settingStore.downloadSaveAsAss,
+ set: (v) => (settingStore.downloadSaveAsAss = v),
+ }),
+ },
{
key: "downloadLyricToTraditional",
label: "下载歌词转繁体",
diff --git a/src/core/resource/DownloadManager.ts b/src/core/resource/DownloadManager.ts
index 0b1a5bccc..434689cd1 100644
--- a/src/core/resource/DownloadManager.ts
+++ b/src/core/resource/DownloadManager.ts
@@ -9,7 +9,9 @@ import { qqMusicMatch } from "@/api/qqmusic";
import { songLevelData } from "@/utils/meta";
import { getPlayerInfoObj } from "@/utils/format";
import { getConverter, type ConverterMode } from "@/utils/opencc";
-import { lyricLinesToTTML, parseQRCLyric } from "@/utils/lyricParser";
+import { lyricLinesToTTML, parseQRCLyric, parseSmartLrc } from "@/utils/lyricParser";
+import { generateASS } from "@/utils/assGenerator";
+import { parseTTML, parseYrc } from "@applemusic-like-lyrics/lyric";
interface DownloadTask {
song: SongType;
@@ -374,8 +376,14 @@ class DownloadManager {
}
if (isElectron) {
- const { downloadMeta, downloadCover, downloadLyric, saveMetaFile, downloadMakeYrc } =
- settingStore;
+ const {
+ downloadMeta,
+ downloadCover,
+ downloadLyric,
+ saveMetaFile,
+ downloadMakeYrc,
+ downloadSaveAsAss,
+ } = settingStore;
let lyric = "";
let yrcLyric = "";
let ttmlLyric = "";
@@ -385,7 +393,7 @@ class DownloadManager {
lyric = await this.processLyric(lyricResult);
// 获取逐字歌词内容用于另存
- if (downloadMakeYrc) {
+ if (downloadMakeYrc || downloadSaveAsAss) {
console.log(`[Download] Fetching verbatim lyrics for ${song.name} (${song.id})...`);
try {
const ttmlRes = await songLyricTTML(song.id);
@@ -495,6 +503,60 @@ class DownloadManager {
}
}
+ if (result.status !== "cancelled" && result.status !== "error" && downloadSaveAsAss) {
+ try {
+ let lines: any[] = [];
+ // 1. Try TTML (highest precision)
+ if (ttmlLyric) {
+ const parsed = parseTTML(ttmlLyric);
+ if (parsed?.lines) lines = parsed.lines;
+ }
+ // 2. Try YRC (QRC)
+ else if (yrcLyric) {
+ // yrcLyric might be QRC XML
+ if (yrcLyric.trim().startsWith("<") || yrcLyric.includes("
")) {
+ lines = parseQRCLyric(yrcLyric);
+ } else {
+ lines = parseYrc(yrcLyric) || [];
+ }
+ }
+ // 3. Fallback to LRC (embedded lyric)
+ else if (lyric) {
+ const parsed = parseSmartLrc(lyric);
+ if (parsed?.lines) lines = parsed.lines;
+ }
+
+ if (lines.length > 0) {
+ let assContent = generateASS(lines, {
+ title: song.name,
+ artist: rawArtist,
+ });
+
+ // 繁体转换
+ assContent = await this._convertToTraditionalIfNeeded(assContent);
+
+ const fileName = `${safeFileName}.ass`;
+ const encoding = settingStore.downloadLyricEncoding || "utf-8";
+
+ console.log(`[Download] Saving ASS file: ${fileName}`);
+ const saveRes = await window.electron.ipcRenderer.invoke("save-file-content", {
+ path: targetPath,
+ fileName,
+ content: assContent,
+ encoding,
+ });
+
+ if (saveRes.success) {
+ console.log(`[Download] Saved ASS file successfully: ${fileName}`);
+ } else {
+ console.error(`[Download] Failed to save ASS file: ${saveRes.message}`);
+ }
+ }
+ } catch (e) {
+ console.error("[Download] Failed to save ASS file exception", e);
+ }
+ }
+
if (result.status === "skipped") {
return { success: true, skipped: true, message: result.message };
}
diff --git a/src/stores/migrations/settingMigrations.ts b/src/stores/migrations/settingMigrations.ts
index 36bcf67cc..d7745efab 100644
--- a/src/stores/migrations/settingMigrations.ts
+++ b/src/stores/migrations/settingMigrations.ts
@@ -6,7 +6,7 @@ import type { SettingState } from "../setting";
/**
* 当前设置 Schema 版本号
*/
-export const CURRENT_SETTING_SCHEMA_VERSION = 9;
+export const CURRENT_SETTING_SCHEMA_VERSION = 10;
/**
* 迁移函数类型
@@ -203,4 +203,9 @@ export const settingMigrations: Record = {
},
};
},
+ 10: () => {
+ return {
+ downloadSaveAsAss: false,
+ };
+ },
};
diff --git a/src/stores/setting.ts b/src/stores/setting.ts
index 4d9879d1f..a37606a79 100644
--- a/src/stores/setting.ts
+++ b/src/stores/setting.ts
@@ -109,6 +109,8 @@ export interface SettingState {
useUnlockForDownload: boolean;
/** 内嵌暂逐字歌词 (beta) */
downloadMakeYrc: boolean;
+ /** 下载后另存为 ASS 格式 */
+ downloadSaveAsAss: boolean;
/** 下载歌词转繁体 */
downloadLyricToTraditional: boolean;
/** 下载歌词文件编码 */
@@ -571,6 +573,7 @@ export const useSettingStore = defineStore("setting", {
usePlaybackForDownload: false,
useUnlockForDownload: false,
downloadMakeYrc: false,
+ downloadSaveAsAss: false,
downloadLyricToTraditional: false,
downloadLyricEncoding: "utf-8",
saveMetaFile: false,
diff --git a/src/utils/assGenerator.ts b/src/utils/assGenerator.ts
new file mode 100644
index 000000000..2d866a603
--- /dev/null
+++ b/src/utils/assGenerator.ts
@@ -0,0 +1,68 @@
+import { type LyricLine } from "@applemusic-like-lyrics/lyric";
+
+/**
+ * 将毫秒转换为 ASS 时间格式 (H:MM:SS.cc)
+ */
+const formatTime = (ms: number): string => {
+ const totalSeconds = Math.floor(ms / 1000);
+ const centiseconds = Math.floor((ms % 1000) / 10); // ASS uses centiseconds (0-99)
+
+ const hours = Math.floor(totalSeconds / 3600);
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
+ const seconds = totalSeconds % 60;
+
+ return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}.${centiseconds.toString().padStart(2, "0")}`;
+};
+
+/**
+ * 生成 ASS 字幕内容
+ * @param lines 歌词行数组
+ * @param metadata 元数据(标题、艺术家等)
+ */
+export const generateASS = (
+ lines: LyricLine[],
+ metadata: { title?: string; artist?: string } = {}
+): string => {
+ const { title = "Unknown Title", artist = "Unknown Artist" } = metadata;
+
+ const header = `[Script Info]
+Title: ${title} - ${artist}
+ScriptType: v4.00+
+WrapStyle: 0
+ScaledBorderAndShadow: yes
+YCbCr Matrix: TV.601
+PlayResX: 1920
+PlayResY: 1080
+
+[V4+ Styles]
+Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
+Style: Default,Arial,60,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
+
+[Events]
+Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+`;
+
+ const events = lines
+ .map((line) => {
+ // 忽略空行
+ const text = line.words.map((w) => w.word).join("").trim();
+ if (!text) return null;
+
+ const startTime = formatTime(line.startTime);
+ const endTime = formatTime(line.endTime);
+
+ // 处理翻译
+ let dialogueText = text;
+ if (line.translatedLyric) {
+ dialogueText += `\\N${line.translatedLyric}`;
+ }
+
+ // 如果有罗马音,也可以加上,但 ASS 通常不宜过分拥挤,暂只加翻译
+
+ return `Dialogue: 0,${startTime},${endTime},Default,,0,0,0,,${dialogueText}`;
+ })
+ .filter(Boolean)
+ .join("\n");
+
+ return header + events;
+};
From f1f8f71afbedd04f61890c92715a1bf4897b0c8d Mon Sep 17 00:00:00 2001
From: imsyy
Date: Sun, 1 Feb 2026 22:17:25 +0800
Subject: [PATCH 09/10] =?UTF-8?q?=F0=9F=90=9E=20fix:=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E9=94=99=E8=AF=AF=E7=9A=84=E5=81=8F=E5=A5=BD=E5=AD=98=E5=82=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
auto-eslint.mjs | 628 +++++++++----------
native/external-media-integration/index.d.ts | 97 +--
src/components/Modal/ThemeConfig.vue | 12 +-
src/core/player/PlayerController.ts | 41 +-
src/core/player/SongManager.ts | 43 +-
src/core/resource/DownloadManager.ts | 10 +-
src/stores/data.ts | 51 ++
src/stores/migrations/settingMigrations.ts | 43 +-
8 files changed, 439 insertions(+), 486 deletions(-)
diff --git a/auto-eslint.mjs b/auto-eslint.mjs
index 5716b26d5..2dd6c614e 100644
--- a/auto-eslint.mjs
+++ b/auto-eslint.mjs
@@ -1,315 +1,315 @@
export default {
- globals: {
- Component: true,
- ComponentPublicInstance: true,
- ComputedRef: true,
- DirectiveBinding: true,
- EffectScope: true,
- ExtractDefaultPropTypes: true,
- ExtractPropTypes: true,
- ExtractPublicPropTypes: true,
- InjectionKey: true,
- MaybeRef: true,
- MaybeRefOrGetter: true,
- PropType: true,
- Ref: true,
- ShallowRef: true,
- Slot: true,
- Slots: true,
- VNode: true,
- WritableComputedRef: true,
- asyncComputed: true,
- autoResetRef: true,
- computed: true,
- computedAsync: true,
- computedEager: true,
- computedInject: true,
- computedWithControl: true,
- controlledComputed: true,
- controlledRef: true,
- createApp: true,
- createEventHook: true,
- createGlobalState: true,
- createInjectionState: true,
- createReactiveFn: true,
- createRef: true,
- createReusableTemplate: true,
- createSharedComposable: true,
- createTemplatePromise: true,
- createUnrefFn: true,
- customRef: true,
- debouncedRef: true,
- debouncedWatch: true,
- defineAsyncComponent: true,
- defineComponent: true,
- eagerComputed: true,
- effectScope: true,
- extendRef: true,
- getCurrentInstance: true,
- getCurrentScope: true,
- getCurrentWatcher: true,
- h: true,
- ignorableWatch: true,
- inject: true,
- injectLocal: true,
- isDefined: true,
- isProxy: true,
- isReactive: true,
- isReadonly: true,
- isRef: true,
- isShallow: true,
- makeDestructurable: true,
- markRaw: true,
- nextTick: true,
- onActivated: true,
- onBeforeMount: true,
- onBeforeRouteLeave: true,
- onBeforeRouteUpdate: true,
- onBeforeUnmount: true,
- onBeforeUpdate: true,
- onClickOutside: true,
- onDeactivated: true,
- onElementRemoval: true,
- onErrorCaptured: true,
- onKeyStroke: true,
- onLongPress: true,
- onMounted: true,
- onRenderTracked: true,
- onRenderTriggered: true,
- onScopeDispose: true,
- onServerPrefetch: true,
- onStartTyping: true,
- onUnmounted: true,
- onUpdated: true,
- onWatcherCleanup: true,
- pausableWatch: true,
- provide: true,
- provideLocal: true,
- reactify: true,
- reactifyObject: true,
- reactive: true,
- reactiveComputed: true,
- reactiveOmit: true,
- reactivePick: true,
- readonly: true,
- ref: true,
- refAutoReset: true,
- refDebounced: true,
- refDefault: true,
- refManualReset: true,
- refThrottled: true,
- refWithControl: true,
- resolveComponent: true,
- resolveRef: true,
- shallowReactive: true,
- shallowReadonly: true,
- shallowRef: true,
- syncRef: true,
- syncRefs: true,
- templateRef: true,
- throttledRef: true,
- throttledWatch: true,
- toRaw: true,
- toReactive: true,
- toRef: true,
- toRefs: true,
- toValue: true,
- triggerRef: true,
- tryOnBeforeMount: true,
- tryOnBeforeUnmount: true,
- tryOnMounted: true,
- tryOnScopeDispose: true,
- tryOnUnmounted: true,
- unref: true,
- unrefElement: true,
- until: true,
- useActiveElement: true,
- useAnimate: true,
- useArrayDifference: true,
- useArrayEvery: true,
- useArrayFilter: true,
- useArrayFind: true,
- useArrayFindIndex: true,
- useArrayFindLast: true,
- useArrayIncludes: true,
- useArrayJoin: true,
- useArrayMap: true,
- useArrayReduce: true,
- useArraySome: true,
- useArrayUnique: true,
- useAsyncQueue: true,
- useAsyncState: true,
- useAttrs: true,
- useBase64: true,
- useBattery: true,
- useBluetooth: true,
- useBreakpoints: true,
- useBroadcastChannel: true,
- useBrowserLocation: true,
- useCached: true,
- useClipboard: true,
- useClipboardItems: true,
- useCloned: true,
- useColorMode: true,
- useConfirmDialog: true,
- useCountdown: true,
- useCounter: true,
- useCssModule: true,
- useCssVar: true,
- useCssVars: true,
- useCurrentElement: true,
- useCycleList: true,
- useDark: true,
- useDateFormat: true,
- useDebounce: true,
- useDebounceFn: true,
- useDebouncedRefHistory: true,
- useDeviceMotion: true,
- useDeviceOrientation: true,
- useDevicePixelRatio: true,
- useDevicesList: true,
- useDialog: true,
- useDisplayMedia: true,
- useDocumentVisibility: true,
- useDraggable: true,
- useDropZone: true,
- useElementBounding: true,
- useElementByPoint: true,
- useElementHover: true,
- useElementSize: true,
- useElementVisibility: true,
- useEventBus: true,
- useEventListener: true,
- useEventSource: true,
- useEyeDropper: true,
- useFavicon: true,
- useFetch: true,
- useFileDialog: true,
- useFileSystemAccess: true,
- useFocus: true,
- useFocusWithin: true,
- useFps: true,
- useFullscreen: true,
- useGamepad: true,
- useGeolocation: true,
- useId: true,
- useIdle: true,
- useImage: true,
- useInfiniteScroll: true,
- useIntersectionObserver: true,
- useInterval: true,
- useIntervalFn: true,
- useKeyModifier: true,
- useLastChanged: true,
- useLink: true,
- useLoadingBar: true,
- useLocalStorage: true,
- useMagicKeys: true,
- useManualRefHistory: true,
- useMediaControls: true,
- useMediaQuery: true,
- useMemoize: true,
- useMemory: true,
- useMessage: true,
- useModel: true,
- useMounted: true,
- useMouse: true,
- useMouseInElement: true,
- useMousePressed: true,
- useMutationObserver: true,
- useNavigatorLanguage: true,
- useNetwork: true,
- useNotification: true,
- useNow: true,
- useObjectUrl: true,
- useOffsetPagination: true,
- useOnline: true,
- usePageLeave: true,
- useParallax: true,
- useParentElement: true,
- usePerformanceObserver: true,
- usePermission: true,
- usePointer: true,
- usePointerLock: true,
- usePointerSwipe: true,
- usePreferredColorScheme: true,
- usePreferredContrast: true,
- usePreferredDark: true,
- usePreferredLanguages: true,
- usePreferredReducedMotion: true,
- usePreferredReducedTransparency: true,
- usePrevious: true,
- useRafFn: true,
- useRefHistory: true,
- useResizeObserver: true,
- useRoute: true,
- useRouter: true,
- useSSRWidth: true,
- useScreenOrientation: true,
- useScreenSafeArea: true,
- useScriptTag: true,
- useScroll: true,
- useScrollLock: true,
- useSessionStorage: true,
- useShare: true,
- useSlots: true,
- useSorted: true,
- useSpeechRecognition: true,
- useSpeechSynthesis: true,
- useStepper: true,
- useStorage: true,
- useStorageAsync: true,
- useStyleTag: true,
- useSupported: true,
- useSwipe: true,
- useTemplateRef: true,
- useTemplateRefsList: true,
- useTextDirection: true,
- useTextSelection: true,
- useTextareaAutosize: true,
- useThrottle: true,
- useThrottleFn: true,
- useThrottledRefHistory: true,
- useTimeAgo: true,
- useTimeAgoIntl: true,
- useTimeout: true,
- useTimeoutFn: true,
- useTimeoutPoll: true,
- useTimestamp: true,
- useTitle: true,
- useToNumber: true,
- useToString: true,
- useToggle: true,
- useTransition: true,
- useUrlSearchParams: true,
- useUserMedia: true,
- useVModel: true,
- useVModels: true,
- useVibrate: true,
- useVirtualList: true,
- useWakeLock: true,
- useWebNotification: true,
- useWebSocket: true,
- useWebWorker: true,
- useWebWorkerFn: true,
- useWindowFocus: true,
- useWindowScroll: true,
- useWindowSize: true,
- watch: true,
- watchArray: true,
- watchAtMost: true,
- watchDebounced: true,
- watchDeep: true,
- watchEffect: true,
- watchIgnorable: true,
- watchImmediate: true,
- watchOnce: true,
- watchPausable: true,
- watchPostEffect: true,
- watchSyncEffect: true,
- watchThrottled: true,
- watchTriggerable: true,
- watchWithFilter: true,
- whenever: true,
- },
-};
+ "globals": {
+ "Component": true,
+ "ComponentPublicInstance": true,
+ "ComputedRef": true,
+ "DirectiveBinding": true,
+ "EffectScope": true,
+ "ExtractDefaultPropTypes": true,
+ "ExtractPropTypes": true,
+ "ExtractPublicPropTypes": true,
+ "InjectionKey": true,
+ "MaybeRef": true,
+ "MaybeRefOrGetter": true,
+ "PropType": true,
+ "Ref": true,
+ "ShallowRef": true,
+ "Slot": true,
+ "Slots": true,
+ "VNode": true,
+ "WritableComputedRef": true,
+ "asyncComputed": true,
+ "autoResetRef": true,
+ "computed": true,
+ "computedAsync": true,
+ "computedEager": true,
+ "computedInject": true,
+ "computedWithControl": true,
+ "controlledComputed": true,
+ "controlledRef": true,
+ "createApp": true,
+ "createEventHook": true,
+ "createGlobalState": true,
+ "createInjectionState": true,
+ "createReactiveFn": true,
+ "createRef": true,
+ "createReusableTemplate": true,
+ "createSharedComposable": true,
+ "createTemplatePromise": true,
+ "createUnrefFn": true,
+ "customRef": true,
+ "debouncedRef": true,
+ "debouncedWatch": true,
+ "defineAsyncComponent": true,
+ "defineComponent": true,
+ "eagerComputed": true,
+ "effectScope": true,
+ "extendRef": true,
+ "getCurrentInstance": true,
+ "getCurrentScope": true,
+ "getCurrentWatcher": true,
+ "h": true,
+ "ignorableWatch": true,
+ "inject": true,
+ "injectLocal": true,
+ "isDefined": true,
+ "isProxy": true,
+ "isReactive": true,
+ "isReadonly": true,
+ "isRef": true,
+ "isShallow": true,
+ "makeDestructurable": true,
+ "markRaw": true,
+ "nextTick": true,
+ "onActivated": true,
+ "onBeforeMount": true,
+ "onBeforeRouteLeave": true,
+ "onBeforeRouteUpdate": true,
+ "onBeforeUnmount": true,
+ "onBeforeUpdate": true,
+ "onClickOutside": true,
+ "onDeactivated": true,
+ "onElementRemoval": true,
+ "onErrorCaptured": true,
+ "onKeyStroke": true,
+ "onLongPress": true,
+ "onMounted": true,
+ "onRenderTracked": true,
+ "onRenderTriggered": true,
+ "onScopeDispose": true,
+ "onServerPrefetch": true,
+ "onStartTyping": true,
+ "onUnmounted": true,
+ "onUpdated": true,
+ "onWatcherCleanup": true,
+ "pausableWatch": true,
+ "provide": true,
+ "provideLocal": true,
+ "reactify": true,
+ "reactifyObject": true,
+ "reactive": true,
+ "reactiveComputed": true,
+ "reactiveOmit": true,
+ "reactivePick": true,
+ "readonly": true,
+ "ref": true,
+ "refAutoReset": true,
+ "refDebounced": true,
+ "refDefault": true,
+ "refManualReset": true,
+ "refThrottled": true,
+ "refWithControl": true,
+ "resolveComponent": true,
+ "resolveRef": true,
+ "shallowReactive": true,
+ "shallowReadonly": true,
+ "shallowRef": true,
+ "syncRef": true,
+ "syncRefs": true,
+ "templateRef": true,
+ "throttledRef": true,
+ "throttledWatch": true,
+ "toRaw": true,
+ "toReactive": true,
+ "toRef": true,
+ "toRefs": true,
+ "toValue": true,
+ "triggerRef": true,
+ "tryOnBeforeMount": true,
+ "tryOnBeforeUnmount": true,
+ "tryOnMounted": true,
+ "tryOnScopeDispose": true,
+ "tryOnUnmounted": true,
+ "unref": true,
+ "unrefElement": true,
+ "until": true,
+ "useActiveElement": true,
+ "useAnimate": true,
+ "useArrayDifference": true,
+ "useArrayEvery": true,
+ "useArrayFilter": true,
+ "useArrayFind": true,
+ "useArrayFindIndex": true,
+ "useArrayFindLast": true,
+ "useArrayIncludes": true,
+ "useArrayJoin": true,
+ "useArrayMap": true,
+ "useArrayReduce": true,
+ "useArraySome": true,
+ "useArrayUnique": true,
+ "useAsyncQueue": true,
+ "useAsyncState": true,
+ "useAttrs": true,
+ "useBase64": true,
+ "useBattery": true,
+ "useBluetooth": true,
+ "useBreakpoints": true,
+ "useBroadcastChannel": true,
+ "useBrowserLocation": true,
+ "useCached": true,
+ "useClipboard": true,
+ "useClipboardItems": true,
+ "useCloned": true,
+ "useColorMode": true,
+ "useConfirmDialog": true,
+ "useCountdown": true,
+ "useCounter": true,
+ "useCssModule": true,
+ "useCssVar": true,
+ "useCssVars": true,
+ "useCurrentElement": true,
+ "useCycleList": true,
+ "useDark": true,
+ "useDateFormat": true,
+ "useDebounce": true,
+ "useDebounceFn": true,
+ "useDebouncedRefHistory": true,
+ "useDeviceMotion": true,
+ "useDeviceOrientation": true,
+ "useDevicePixelRatio": true,
+ "useDevicesList": true,
+ "useDialog": true,
+ "useDisplayMedia": true,
+ "useDocumentVisibility": true,
+ "useDraggable": true,
+ "useDropZone": true,
+ "useElementBounding": true,
+ "useElementByPoint": true,
+ "useElementHover": true,
+ "useElementSize": true,
+ "useElementVisibility": true,
+ "useEventBus": true,
+ "useEventListener": true,
+ "useEventSource": true,
+ "useEyeDropper": true,
+ "useFavicon": true,
+ "useFetch": true,
+ "useFileDialog": true,
+ "useFileSystemAccess": true,
+ "useFocus": true,
+ "useFocusWithin": true,
+ "useFps": true,
+ "useFullscreen": true,
+ "useGamepad": true,
+ "useGeolocation": true,
+ "useId": true,
+ "useIdle": true,
+ "useImage": true,
+ "useInfiniteScroll": true,
+ "useIntersectionObserver": true,
+ "useInterval": true,
+ "useIntervalFn": true,
+ "useKeyModifier": true,
+ "useLastChanged": true,
+ "useLink": true,
+ "useLoadingBar": true,
+ "useLocalStorage": true,
+ "useMagicKeys": true,
+ "useManualRefHistory": true,
+ "useMediaControls": true,
+ "useMediaQuery": true,
+ "useMemoize": true,
+ "useMemory": true,
+ "useMessage": true,
+ "useModel": true,
+ "useMounted": true,
+ "useMouse": true,
+ "useMouseInElement": true,
+ "useMousePressed": true,
+ "useMutationObserver": true,
+ "useNavigatorLanguage": true,
+ "useNetwork": true,
+ "useNotification": true,
+ "useNow": true,
+ "useObjectUrl": true,
+ "useOffsetPagination": true,
+ "useOnline": true,
+ "usePageLeave": true,
+ "useParallax": true,
+ "useParentElement": true,
+ "usePerformanceObserver": true,
+ "usePermission": true,
+ "usePointer": true,
+ "usePointerLock": true,
+ "usePointerSwipe": true,
+ "usePreferredColorScheme": true,
+ "usePreferredContrast": true,
+ "usePreferredDark": true,
+ "usePreferredLanguages": true,
+ "usePreferredReducedMotion": true,
+ "usePreferredReducedTransparency": true,
+ "usePrevious": true,
+ "useRafFn": true,
+ "useRefHistory": true,
+ "useResizeObserver": true,
+ "useRoute": true,
+ "useRouter": true,
+ "useSSRWidth": true,
+ "useScreenOrientation": true,
+ "useScreenSafeArea": true,
+ "useScriptTag": true,
+ "useScroll": true,
+ "useScrollLock": true,
+ "useSessionStorage": true,
+ "useShare": true,
+ "useSlots": true,
+ "useSorted": true,
+ "useSpeechRecognition": true,
+ "useSpeechSynthesis": true,
+ "useStepper": true,
+ "useStorage": true,
+ "useStorageAsync": true,
+ "useStyleTag": true,
+ "useSupported": true,
+ "useSwipe": true,
+ "useTemplateRef": true,
+ "useTemplateRefsList": true,
+ "useTextDirection": true,
+ "useTextSelection": true,
+ "useTextareaAutosize": true,
+ "useThrottle": true,
+ "useThrottleFn": true,
+ "useThrottledRefHistory": true,
+ "useTimeAgo": true,
+ "useTimeAgoIntl": true,
+ "useTimeout": true,
+ "useTimeoutFn": true,
+ "useTimeoutPoll": true,
+ "useTimestamp": true,
+ "useTitle": true,
+ "useToNumber": true,
+ "useToString": true,
+ "useToggle": true,
+ "useTransition": true,
+ "useUrlSearchParams": true,
+ "useUserMedia": true,
+ "useVModel": true,
+ "useVModels": true,
+ "useVibrate": true,
+ "useVirtualList": true,
+ "useWakeLock": true,
+ "useWebNotification": true,
+ "useWebSocket": true,
+ "useWebWorker": true,
+ "useWebWorkerFn": true,
+ "useWindowFocus": true,
+ "useWindowScroll": true,
+ "useWindowSize": true,
+ "watch": true,
+ "watchArray": true,
+ "watchAtMost": true,
+ "watchDebounced": true,
+ "watchDeep": true,
+ "watchEffect": true,
+ "watchIgnorable": true,
+ "watchImmediate": true,
+ "watchOnce": true,
+ "watchPausable": true,
+ "watchPostEffect": true,
+ "watchSyncEffect": true,
+ "watchThrottled": true,
+ "watchTriggerable": true,
+ "watchWithFilter": true,
+ "whenever": true
+ }
+}
diff --git a/native/external-media-integration/index.d.ts b/native/external-media-integration/index.d.ts
index 80658212a..91c1b96ab 100644
--- a/native/external-media-integration/index.d.ts
+++ b/native/external-media-integration/index.d.ts
@@ -1,7 +1,7 @@
/* auto-generated by NAPI-RS */
-/* eslint-disable */
+
/** 关闭 Discord RPC */
-export declare function disableDiscordRpc(): void;
+export declare function disableDiscordRpc(): void
/**
* 禁用媒体控件
@@ -10,7 +10,7 @@ export declare function disableDiscordRpc(): void;
*
* 会在调用 API 失败时抛出错误
*/
-export declare function disableSystemMedia(): void;
+export declare function disableSystemMedia(): void
/** Discord 配置参数 */
export interface DiscordConfigPayload {
@@ -19,9 +19,9 @@ export interface DiscordConfigPayload {
*
* 注意暂停时进度会固定为 0
*/
- showWhenPaused: boolean;
+ showWhenPaused: boolean
/** 显示模式,参考 [`DiscordDisplayMode`] */
- displayMode?: DiscordDisplayMode;
+ displayMode?: DiscordDisplayMode
}
/**
@@ -29,13 +29,12 @@ export interface DiscordConfigPayload {
*
* 不打开详细信息面板时,在用户名下方显示的小字
*/
-export type DiscordDisplayMode =
- /** Listening to SPlayer */
- | "Name"
- /** Listening to Rick Astley */
- | "State"
- /** Listening to Never Gonna Give You Up */
- | "Details";
+export type DiscordDisplayMode = /** Listening to SPlayer */
+'Name'|
+/** Listening to Rick Astley */
+'State'|
+/** Listening to Never Gonna Give You Up */
+'Details';
/**
* 启用 Discord RPC
@@ -44,7 +43,7 @@ export type DiscordDisplayMode =
*
* 启用后会立刻尝试连接,如果 Discord 未启动,或因为其他未知原因连接失败,会每 5 秒尝试连接一次
*/
-export declare function enableDiscordRpc(): void;
+export declare function enableDiscordRpc(): void
/**
* 启用媒体控件
@@ -53,7 +52,7 @@ export declare function enableDiscordRpc(): void;
*
* 会在调用 API 失败时抛出错误
*/
-export declare function enableSystemMedia(): void;
+export declare function enableSystemMedia(): void
/**
* 初始化插件
@@ -70,20 +69,20 @@ export declare function enableSystemMedia(): void;
*
* 如果其他 API 调用失败,则只会打印日志并静默失败
*/
-export declare function initialize(logDir: string): void;
+export declare function initialize(logDir: string): void
export interface MetadataParam {
- songName: string;
- authorName: string;
- albumName: string;
+ songName: string
+ authorName: string
+ albumName: string
/** 封面的原始字节数据,适用于除 Discord RPC 之外的其他平台 */
- coverData?: Buffer;
+ coverData?: Buffer
/**
* 封面的 HTTP URL,更新 Discord RPC 时必传,其他平台可不传
*
* Linux 平台在没有提供 `cover_data` 时会使用它
*/
- originalCoverUrl?: string;
+ originalCoverUrl?: string
/**
* 网易云音乐中对应的曲目 ID
*
@@ -92,25 +91,26 @@ export interface MetadataParam {
* - 生成 Discord RPC 的按钮链接
* - MacOS 和 Linux 会使用此值来填充唯一的曲目 ID
*/
- ncmId?: number;
+ ncmId?: number
/**
* 当前歌曲时长,单位是毫秒
*
* 用于 Linux、MacOS、Discord RPC 的元数据更新。Windows 使用 [`TimelinePayload`] 的
* `total_time` 字段。
*/
- duration?: number;
+ duration?: number
}
-export type PlaybackStatus = "Playing" | "Paused";
+export type PlaybackStatus = 'Playing'|
+'Paused';
export interface PlayModePayload {
- isShuffling: boolean;
- repeatMode: RepeatMode;
+ isShuffling: boolean
+ repeatMode: RepeatMode
}
export interface PlayStatePayload {
- status: PlaybackStatus;
+ status: PlaybackStatus
}
/**
@@ -124,34 +124,35 @@ export interface PlayStatePayload {
*
* 如果 N-API 创建线程安全函数失败,会抛出错误。通常不应该发生,除非 JS 环境已经销毁了
*/
-export declare function registerEventHandler(callback: (arg: SystemMediaEvent) => void): void;
+export declare function registerEventHandler(callback: (arg: SystemMediaEvent) => void): void
-export type RepeatMode = "None" | "Track" | "List";
+export type RepeatMode = 'None'|
+'Track'|
+'List';
/** 关闭插件,清理资源 */
-export declare function shutdown(): void;
+export declare function shutdown(): void
export interface SystemMediaEvent {
- type: SystemMediaEventType;
- positionMs?: number;
+ type: SystemMediaEventType
+ positionMs?: number
}
-export type SystemMediaEventType =
- | "Play"
- | "Pause"
- | "Stop"
- | "NextSong"
- | "PreviousSong"
- | "ToggleShuffle"
- | "ToggleRepeat"
- /** 绝对位置,毫秒 */
- | "Seek";
+export type SystemMediaEventType = 'Play'|
+'Pause'|
+'Stop'|
+'NextSong'|
+'PreviousSong'|
+'ToggleShuffle'|
+'ToggleRepeat'|
+/** 绝对位置,毫秒 */
+'Seek';
export interface TimelinePayload {
/** 单位是毫秒 */
- currentTime: number;
+ currentTime: number
/** 单位是毫秒 */
- totalTime: number;
+ totalTime: number
}
/**
@@ -162,7 +163,7 @@ export interface TimelinePayload {
* * `payload` - 配置信息,可以配置是否在暂停后也显示 Discord Activity 和 状态显示风格。详情请查看
* [`DiscordConfigPayload`]
*/
-export declare function updateDiscordConfig(payload: DiscordConfigPayload): void;
+export declare function updateDiscordConfig(payload: DiscordConfigPayload): void
/**
* 更新歌曲元数据
@@ -173,7 +174,7 @@ export declare function updateDiscordConfig(payload: DiscordConfigPayload): void
*
* 更新 Discord RPC 的元数据时,必须提供 `original_cover_url`
*/
-export declare function updateMetadata(payload: MetadataParam): void;
+export declare function updateMetadata(payload: MetadataParam): void
/**
* 更新播放模式
@@ -182,14 +183,14 @@ export declare function updateMetadata(payload: MetadataParam): void;
*
* 只会更新媒体控件的信息,不会更新 Discord RPC 上的信息
*/
-export declare function updatePlayMode(payload: PlayModePayload): void;
+export declare function updatePlayMode(payload: PlayModePayload): void
/**
* 更新播放状态 (播放/暂停)
*
* 同时也会更新 Discord 的播放状态 (如果启用了 Discord RPC)
*/
-export declare function updatePlayState(payload: PlayStatePayload): void;
+export declare function updatePlayState(payload: PlayStatePayload): void
/**
* 更新进度信息
@@ -200,4 +201,4 @@ export declare function updatePlayState(payload: PlayStatePayload): void;
*
* Discord RPC 实现的进度更新有节流,调用此函数无需担心 Discord RPC 的速率限制
*/
-export declare function updateTimeline(payload: TimelinePayload): void;
+export declare function updateTimeline(payload: TimelinePayload): void
diff --git a/src/components/Modal/ThemeConfig.vue b/src/components/Modal/ThemeConfig.vue
index 01aec17e4..75f618584 100644
--- a/src/components/Modal/ThemeConfig.vue
+++ b/src/components/Modal/ThemeConfig.vue
@@ -309,24 +309,18 @@ const selectColor = (key: ThemeColorType) => {
// 随机主题
const randomizeTheme = () => {
- // 1. 关闭动态取色
settingStore.themeFollowCover = false;
-
- // 2. 随机全局着色 (50% 概率)
+ // 随机全局着色 (50% 概率)
settingStore.themeGlobalColor = Math.random() > 0.5;
-
- // 3. 随机变体
+ // 随机变体
const randomVariant = variantOptions[Math.floor(Math.random() * variantOptions.length)];
settingStore.themeVariant = randomVariant.value as typeof settingStore.themeVariant;
-
- // 4. 随机颜色 (生成随机 Hex 并应用到自定义)
+ // 随机颜色 (生成随机 Hex 并应用到自定义)
const randomHex = `#${Math.floor(Math.random() * 16777215)
.toString(16)
.padStart(6, "0")}`;
settingStore.themeCustomColor = randomHex;
settingStore.themeColorType = "custom";
-
- // 5. 触发更新
themeGlobalColorChange(true);
};
diff --git a/src/core/player/PlayerController.ts b/src/core/player/PlayerController.ts
index 75458ef13..33599b36d 100644
--- a/src/core/player/PlayerController.ts
+++ b/src/core/player/PlayerController.ts
@@ -99,14 +99,17 @@ class PlayerController {
// 简单的防削波保护 (如果有峰值信息)
// 目标: gain * peak <= 1.0
- const peak = settingStore.replayGainMode === "album" ? (albumPeak ?? trackPeak) : (trackPeak ?? albumPeak);
+ const peak =
+ settingStore.replayGainMode === "album" ? (albumPeak ?? trackPeak) : (trackPeak ?? albumPeak);
if (peak && peak > 0) {
if (targetGain * peak > 1.0) {
targetGain = 1.0 / peak;
}
}
- console.log(`🔊 [ReplayGain] Applied: ${targetGain.toFixed(4)} (Mode: ${settingStore.replayGainMode})`);
+ console.log(
+ `🔊 [ReplayGain] Applied: ${targetGain.toFixed(4)} (Mode: ${settingStore.replayGainMode})`,
+ );
audioManager.setReplayGain(targetGain);
}
@@ -148,45 +151,30 @@ class PlayerController {
}
try {
- // 设置加载状态
statusStore.playLoading = true;
-
- // 1. 预加载音频 (最耗时操作,期间保持旧歌播放)
const audioSource = await songManager.getAudioSource(playSongData);
-
// 检查请求是否过期
if (requestToken !== this.currentRequestToken) {
console.log(`🚫 [${playSongData.id}] 请求已过期,舍弃`);
return;
}
if (!audioSource.url) throw new Error("AUDIO_SOURCE_EMPTY");
-
- // === 切换点 (Point of Switch) ===
-
- // 2. 停止当前播放
audioManager.stop();
-
- // 3. 更新核心状态
musicStore.playSong = playSongData;
-
statusStore.currentTime = options.seek ?? 0;
- // 重置进度 (新歌时长未知,暂设计算)
+ // 重置进度
statusStore.progress = 0;
-
statusStore.lyricIndex = -1;
-
// 重置重试计数
const sid = playSongData.type === "radio" ? playSongData.dj?.id : playSongData.id;
if (this.retryInfo.songId !== sid) {
this.retryInfo = { songId: sid || 0, count: 0 };
}
-
statusStore.lyricLoading = true;
// 重置 AB 循环
statusStore.abLoop.enable = false;
statusStore.abLoop.pointA = null;
statusStore.abLoop.pointB = null;
-
// 通知桌面歌词
if (isElectron) {
window.electron.ipcRenderer.send("update-desktop-lyric-data", {
@@ -195,7 +183,6 @@ class PlayerController {
}
// 获取歌词 (在切换后获取,避免旧歌配新词)
lyricManager.handleLyric(playSongData);
-
console.log(`🎧 [${playSongData.id}] 最终播放信息:`, audioSource);
// 更新音质和解锁状态
statusStore.songQuality = audioSource.quality;
@@ -262,25 +249,21 @@ class PlayerController {
* @param source 音频源标识
*/
public async switchAudioSource(source: string) {
+ const dataStore = useDataStore();
const statusStore = useStatusStore();
const songManager = useSongManager();
const musicStore = useMusicStore();
const audioManager = useAudioManager();
-
const playSongData = musicStore.playSong;
if (!playSongData || playSongData.path) return;
-
try {
statusStore.playLoading = true;
-
// 保存偏好
- await songManager.saveAudioSourcePreference(playSongData.id, source);
+ await dataStore.setAudioSourcePreference(playSongData.id, source);
statusStore.preferredAudioSource = source;
-
// 清除预取缓存
songManager.clearPrefetch();
-
- // 获取新音频源 (直接请求指定源,避免并发请求所有源导致卡顿)
+ // 获取新音频源
const audioSource = await songManager.getAudioSourceFromSpecificServer(playSongData, source);
if (!audioSource.url) {
@@ -296,13 +279,11 @@ class PlayerController {
statusStore.playUblock = audioSource.isUnlocked ?? false;
statusStore.audioSource = audioSource.source;
- // 停止当前播放
- audioManager.stop();
-
// 保持当前进度和播放状态
const seek = statusStore.currentTime;
const shouldAutoPlay = statusStore.playStatus;
-
+ // 停止当前播放
+ audioManager.stop();
await this.loadAndPlay(audioSource.url, shouldAutoPlay, seek);
} catch (error) {
console.error("❌ 切换音频源失败:", error);
diff --git a/src/core/player/SongManager.ts b/src/core/player/SongManager.ts
index f4e662d97..45d8524ca 100644
--- a/src/core/player/SongManager.ts
+++ b/src/core/player/SongManager.ts
@@ -1,6 +1,5 @@
import { personalFm, personalFmToTrash } from "@/api/rec";
import { songUrl, unlockSongUrl } from "@/api/song";
-import { useCacheManager } from "@/core/resource/CacheManager";
import {
useDataStore,
useMusicStore,
@@ -179,46 +178,13 @@ class SongManager {
return { id, url: finalUrl, isTrial, quality };
};
- /**
- * 获取音频源偏好
- */
- private async getAudioSourcePreference(id: number | string): Promise {
- const settingStore = useSettingStore();
- if (!isElectron || !settingStore.cacheEnabled) return null;
- try {
- const cacheManager = useCacheManager();
- const result = await cacheManager.get("list-data", `audio_pref_${id}`);
- if (result.success && result.data) {
- const decoder = new TextDecoder();
- return decoder.decode(result.data);
- }
- return null;
- } catch {
- return null;
- }
- }
-
- /**
- * 保存音频源偏好
- */
- public async saveAudioSourcePreference(id: number | string, source: string) {
- const settingStore = useSettingStore();
- if (!isElectron || !settingStore.cacheEnabled) return;
- try {
- const cacheManager = useCacheManager();
- await cacheManager.set("list-data", `audio_pref_${id}`, source);
- } catch (error) {
- console.error("写入音频源偏好失败:", error);
- }
- }
-
/**
* 获取所有可用解锁源
*/
public getAvailableUnlockSources = async (song: SongType): Promise => {
const settingStore = useSettingStore();
const songId = song.id;
-
+
const artist = Array.isArray(song.artists) ? song.artists[0].name : song.artists;
const keyWord = song.name + "-" + artist;
if (!songId || !keyWord) {
@@ -422,7 +388,7 @@ class SongManager {
// 触发缓存下载
this.triggerCacheDownload(songId, unlockUrl, quality);
-
+
return {
id: songId,
url: unlockUrl,
@@ -481,7 +447,8 @@ class SongManager {
if (!songId) return { id: 0, url: undefined, quality: undefined, isUnlocked: false };
// 获取偏好
- const pref = await this.getAudioSourcePreference(songId);
+ const dataStore = useDataStore();
+ const pref = await dataStore.getAudioSourcePreference(songId);
statusStore.preferredAudioSource = pref;
// 检查缓存并返回 (如果偏好匹配)
@@ -528,7 +495,7 @@ class SongManager {
// 选择源
let selected: AudioSource | undefined;
-
+
// 1. 尝试使用偏好源
if (pref) {
selected = candidates.find((s) => s.source === pref);
diff --git a/src/core/resource/DownloadManager.ts b/src/core/resource/DownloadManager.ts
index 434689cd1..de591af60 100644
--- a/src/core/resource/DownloadManager.ts
+++ b/src/core/resource/DownloadManager.ts
@@ -11,7 +11,7 @@ import { getPlayerInfoObj } from "@/utils/format";
import { getConverter, type ConverterMode } from "@/utils/opencc";
import { lyricLinesToTTML, parseQRCLyric, parseSmartLrc } from "@/utils/lyricParser";
import { generateASS } from "@/utils/assGenerator";
-import { parseTTML, parseYrc } from "@applemusic-like-lyrics/lyric";
+import { parseTTML, parseYrc, type LyricLine } from "@applemusic-like-lyrics/lyric";
interface DownloadTask {
song: SongType;
@@ -505,13 +505,13 @@ class DownloadManager {
if (result.status !== "cancelled" && result.status !== "error" && downloadSaveAsAss) {
try {
- let lines: any[] = [];
- // 1. Try TTML (highest precision)
+ let lines: LyricLine[] = [];
+ // Try TTML
if (ttmlLyric) {
const parsed = parseTTML(ttmlLyric);
if (parsed?.lines) lines = parsed.lines;
}
- // 2. Try YRC (QRC)
+ // Try YRC (QRC)
else if (yrcLyric) {
// yrcLyric might be QRC XML
if (yrcLyric.trim().startsWith("<") || yrcLyric.includes("")) {
@@ -520,7 +520,7 @@ class DownloadManager {
lines = parseYrc(yrcLyric) || [];
}
}
- // 3. Fallback to LRC (embedded lyric)
+ // Fallback to LRC (embedded lyric)
else if (lyric) {
const parsed = parseSmartLrc(lyric);
if (parsed?.lines) lines = parsed.lines;
diff --git a/src/stores/data.ts b/src/stores/data.ts
index acae92b15..1fe0f55b5 100644
--- a/src/stores/data.ts
+++ b/src/stores/data.ts
@@ -51,6 +51,8 @@ interface ListState {
/** 总大小 */
totalSize: string;
}>;
+ /** 音频源偏好 memory cache */
+ audioSourcePreference: Record;
}
type UserDataKeys = keyof ListState["userLikeData"];
@@ -76,6 +78,13 @@ const backgroundDB = localforage.createInstance({
storeName: "background",
});
+// audioPrefDB
+const audioPrefDB = localforage.createInstance({
+ name: "music-data",
+ description: "Audio source preferences",
+ storeName: "audio_preferences",
+});
+
export const useDataStore = defineStore("data", {
state: (): ListState => ({
// 播放列表
@@ -130,6 +139,8 @@ export const useDataStore = defineStore("data", {
},
// 正在下载的歌曲列表
downloadingSongs: [],
+ // 音频源偏好
+ audioSourcePreference: {},
}),
getters: {
// 是否为喜欢歌曲
@@ -530,6 +541,46 @@ export const useDataStore = defineStore("data", {
throw error;
}
},
+ /**
+ * 获取音频源偏好
+ * @param id 歌曲ID
+ * @returns 音频源标识
+ */
+ async getAudioSourcePreference(id: number | string): Promise {
+ // 优先从内存读取
+ const key = String(id);
+ if (this.audioSourcePreference[key]) {
+ return this.audioSourcePreference[key];
+ }
+ // 从 DB 读取并缓存到内存
+ try {
+ const val = await audioPrefDB.getItem(key);
+ if (val) {
+ this.audioSourcePreference[key] = val;
+ return val;
+ }
+ return null;
+ } catch (error) {
+ console.error(`Error getting audio preference for ${id}:`, error);
+ return null;
+ }
+ },
+ /**
+ * 保存音频源偏好
+ * @param id 歌曲ID
+ * @param source 音频源标识
+ */
+ async setAudioSourcePreference(id: number | string, source: string): Promise {
+ const key = String(id);
+ // 更新内存
+ this.audioSourcePreference[key] = source;
+ // 更新 DB
+ try {
+ await audioPrefDB.setItem(key, source);
+ } catch (error) {
+ console.error(`Error setting audio preference for ${id}:`, error);
+ }
+ },
},
// 持久化
persist: {
diff --git a/src/stores/migrations/settingMigrations.ts b/src/stores/migrations/settingMigrations.ts
index d7745efab..bfc0d1117 100644
--- a/src/stores/migrations/settingMigrations.ts
+++ b/src/stores/migrations/settingMigrations.ts
@@ -6,7 +6,7 @@ import type { SettingState } from "../setting";
/**
* 当前设置 Schema 版本号
*/
-export const CURRENT_SETTING_SCHEMA_VERSION = 10;
+export const CURRENT_SETTING_SCHEMA_VERSION = 8;
/**
* 迁移函数类型
@@ -167,45 +167,4 @@ export const settingMigrations: Record = {
excludeLyricsUserRegexes: oldState.excludeUserRegexes,
};
},
- 9: () => {
- return {
- showSongAlbum: true,
- showSongDuration: true,
- showSongOperations: true,
- showSongArtist: true,
- fullscreenPlayerElements: {
- like: true,
- addToPlaylist: true,
- download: true,
- comments: true,
- desktopLyric: true,
- moreSettings: true,
- copyLyric: true,
- lyricOffset: true,
- lyricSettings: true,
- },
- contextMenuOptions: {
- play: true,
- playNext: true,
- addToPlaylist: true,
- mv: true,
- dislike: true,
- more: true,
- cloudImport: true,
- deleteFromPlaylist: true,
- deleteFromCloud: true,
- deleteFromLocal: true,
- openFolder: true,
- cloudMatch: true,
- wiki: true,
- search: true,
- download: true,
- },
- };
- },
- 10: () => {
- return {
- downloadSaveAsAss: false,
- };
- },
};
From a4d1402506c96b58b53dee8fb6f9c61397b21178 Mon Sep 17 00:00:00 2001
From: imsyy
Date: Sun, 1 Feb 2026 22:20:27 +0800
Subject: [PATCH 10/10] =?UTF-8?q?=F0=9F=90=9E=20fix:=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=20lint?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
components.d.ts | 22 --------
electron/main/ipc/ipc-store.ts | 6 +-
native/external-media-integration/index.d.ts | 2 +-
src/components/Setting/config/general.ts | 29 +++++-----
src/components/Setting/config/network.ts | 2 +-
src/core/player/LyricManager.ts | 59 +++++++++++---------
6 files changed, 52 insertions(+), 68 deletions(-)
diff --git a/components.d.ts b/components.d.ts
index 1db15c675..eeb6b7f2d 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -19,16 +19,11 @@ declare module 'vue' {
AutoClose: typeof import('./src/components/Modal/AutoClose.vue')['default']
BackgroundRender: typeof import('./src/components/AMLL/BackgroundRender.vue')['default']
BatchList: typeof import('./src/components/Modal/BatchList.vue')['default']
- CacheDirectory: typeof import('./src/components/Setting/components/CacheDirectory.vue')['default']
- CacheLimitConfig: typeof import('./src/components/Setting/components/CacheLimitConfig.vue')['default']
CacheSizeLimit: typeof import('./src/components/Setting/components/CacheSizeLimit.vue')['default']
ChangeRate: typeof import('./src/components/Modal/ChangeRate.vue')['default']
CloudMatch: typeof import('./src/components/Modal/CloudMatch.vue')['default']
- CommentFilter: typeof import('./src/components/Modal/CommentFilter.vue')['default']
CommentList: typeof import('./src/components/List/CommentList.vue')['default']
- ConfigurableInputNumber: typeof import('./src/components/Setting/items/ConfigurableInputNumber.vue')['default']
ContextMenuManager: typeof import('./src/components/Modal/Setting/ContextMenuManager.vue')['default']
- copy: typeof import('./src/components/Global/Provider copy.vue')['default']
CopyLyrics: typeof import('./src/components/Modal/CopyLyrics.vue')['default']
CoverList: typeof import('./src/components/List/CoverList.vue')['default']
CoverManager: typeof import('./src/components/Modal/Setting/CoverManager.vue')['default']
@@ -36,10 +31,7 @@ declare module 'vue' {
CreatePlaylist: typeof import('./src/components/Modal/CreatePlaylist.vue')['default']
CustomCode: typeof import('./src/components/Modal/Setting/CustomCode.vue')['default']
DefaultLyric: typeof import('./src/components/Player/PlayerLyric/DefaultLyric.vue')['default']
- DiscordRpcConfig: typeof import('./src/components/Setting/components/DiscordRpcConfig.vue')['default']
- DownloadDirectory: typeof import('./src/components/Setting/components/DownloadDirectory.vue')['default']
DownloadModal: typeof import('./src/components/Modal/DownloadModal.vue')['default']
- DownloadPathButtons: typeof import('./src/components/Setting/components/DownloadPathButtons.vue')['default']
Equalizer: typeof import('./src/components/Modal/Equalizer.vue')['default']
ExcludeComment: typeof import('./src/components/Modal/Setting/ExcludeComment.vue')['default']
ExcludeLyrics: typeof import('./src/components/Modal/Setting/ExcludeLyrics.vue')['default']
@@ -47,19 +39,12 @@ declare module 'vue' {
FullPlayer: typeof import('./src/components/Player/FullPlayer.vue')['default']
FullPlayerMobile: typeof import('./src/components/Player/FullPlayerMobile.vue')['default']
FullscreenPlayerManager: typeof import('./src/components/Modal/Setting/FullscreenPlayerManager.vue')['default']
- GeneralSetting: typeof import('./src/components/Setting/old/GeneralSetting.vue')['default']
HomePageSectionManager: typeof import('./src/components/Modal/Setting/HomePageSectionManager.vue')['default']
JumpArtist: typeof import('./src/components/Modal/JumpArtist.vue')['default']
- KeyboardSetting: typeof import('./src/components/Setting/old/KeyboardSetting.vue')['default']
- LastfmConfig: typeof import('./src/components/Setting/components/LastfmConfig.vue')['default']
ListComment: typeof import('./src/components/List/ListComment.vue')['default']
ListDetail: typeof import('./src/components/List/ListDetail.vue')['default']
LocalLyricDirectories: typeof import('./src/components/Setting/components/LocalLyricDirectories.vue')['default']
- LocalMusicDirectories: typeof import('./src/components/Setting/components/LocalMusicDirectories.vue')['default']
LocalMusicDirectory: typeof import('./src/components/Modal/Setting/LocalMusicDirectory.vue')['default']
- LocalMusicDirectoryModal: typeof import('./src/components/Modal/Setting/LocalMusicDirectoryModal.vue')['default']
- LocalPathConfig: typeof import('./src/components/Setting/components/LocalPathConfig.vue')['default']
- LocalSetting: typeof import('./src/components/Setting/old/LocalSetting.vue')['default']
Login: typeof import('./src/components/Modal/Login/Login.vue')['default']
LoginCookie: typeof import('./src/components/Modal/Login/LoginCookie.vue')['default']
LoginPhone: typeof import('./src/components/Modal/Login/LoginPhone.vue')['default']
@@ -68,7 +53,6 @@ declare module 'vue' {
Logo: typeof import('./src/components/Layout/Logo.vue')['default']
LyricPlayer: typeof import('./src/components/AMLL/LyricPlayer.vue')['default']
LyricPreview: typeof import('./src/components/Setting/components/LyricPreview.vue')['default']
- LyricsSetting: typeof import('./src/components/Setting/old/LyricsSetting.vue')['default']
MainPlayer: typeof import('./src/components/Player/MainPlayer.vue')['default']
MainSetting: typeof import('./src/components/Setting/MainSetting.vue')['default']
Menu: typeof import('./src/components/Layout/Menu.vue')['default']
@@ -151,7 +135,6 @@ declare module 'vue' {
NText: typeof import('naive-ui')['NText']
NThing: typeof import('naive-ui')['NThing']
NTree: typeof import('naive-ui')['NTree']
- OtherSetting: typeof import('./src/components/Setting/old/OtherSetting.vue')['default']
PersonalFM: typeof import('./src/components/Player/PlayerComponents/PersonalFM.vue')['default']
PlayerBackground: typeof import('./src/components/Player/PlayerMeta/PlayerBackground.vue')['default']
PlayerComment: typeof import('./src/components/Player/PlayerComponents/PlayerComment.vue')['default']
@@ -166,9 +149,7 @@ declare module 'vue' {
PlayerSpectrum: typeof import('./src/components/Player/PlayerComponents/PlayerSpectrum.vue')['default']
PlaylistAdd: typeof import('./src/components/Modal/PlaylistAdd.vue')['default']
PlaylistPageManager: typeof import('./src/components/Modal/Setting/PlaylistPageManager.vue')['default']
- PlaySetting: typeof import('./src/components/Setting/old/PlaySetting.vue')['default']
Provider: typeof import('./src/components/Global/Provider.vue')['default']
- ProxyConfig: typeof import('./src/components/Setting/components/ProxyConfig.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ScalingModal: typeof import('./src/components/Modal/ScalingModal.vue')['default']
@@ -193,17 +174,14 @@ declare module 'vue' {
SongUnlockManager: typeof import('./src/components/Modal/Setting/SongUnlockManager.vue')['default']
StreamingServerConfig: typeof import('./src/components/Modal/Setting/StreamingServerConfig.vue')['default']
StreamingServerList: typeof import('./src/components/Setting/components/StreamingServerList.vue')['default']
- StreamingSetting: typeof import('./src/components/Setting/old/StreamingSetting.vue')['default']
SvgIcon: typeof import('./src/components/Global/SvgIcon.vue')['default']
TextContainer: typeof import('./src/components/Global/TextContainer.vue')['default']
ThemeConfig: typeof import('./src/components/Modal/ThemeConfig.vue')['default']
- ThirdSetting: typeof import('./src/components/Setting/old/ThirdSetting.vue')['default']
UniversalSetting: typeof import('./src/components/Setting/UniversalSetting.vue')['default']
UpdateApp: typeof import('./src/components/Modal/UpdateApp.vue')['default']
UpdatePlaylist: typeof import('./src/components/Modal/UpdatePlaylist.vue')['default']
User: typeof import('./src/components/Layout/User.vue')['default']
UserAgreement: typeof import('./src/components/Modal/UserAgreement.vue')['default']
VirtualScroll: typeof import('./src/components/UI/VirtualScroll.vue')['default']
- WebsocketConfig: typeof import('./src/components/Setting/components/WebsocketConfig.vue')['default']
}
}
diff --git a/electron/main/ipc/ipc-store.ts b/electron/main/ipc/ipc-store.ts
index 04e35bfdc..57cd8464f 100644
--- a/electron/main/ipc/ipc-store.ts
+++ b/electron/main/ipc/ipc-store.ts
@@ -95,17 +95,17 @@ const initStoreIpc = (): void => {
if (filePaths && filePaths.length > 0) {
console.log("[IPC] Importing from:", filePaths[0]);
const fileContent = await readFile(filePaths[0], "utf-8");
-
+
let settings;
try {
settings = JSON.parse(fileContent);
- } catch (e) {
+ } catch {
return { success: false, error: "invalid_json" };
}
// 基础结构验证
if (!settings || typeof settings !== "object") {
- return { success: false, error: "invalid_format" };
+ return { success: false, error: "invalid_format" };
}
// 恢复 Electron Store 配置
diff --git a/native/external-media-integration/index.d.ts b/native/external-media-integration/index.d.ts
index 91c1b96ab..be6065e15 100644
--- a/native/external-media-integration/index.d.ts
+++ b/native/external-media-integration/index.d.ts
@@ -1,5 +1,5 @@
/* auto-generated by NAPI-RS */
-
+/* eslint-disable */
/** 关闭 Discord RPC */
export declare function disableDiscordRpc(): void
diff --git a/src/components/Setting/config/general.ts b/src/components/Setting/config/general.ts
index fe0920abb..4e6050108 100644
--- a/src/components/Setting/config/general.ts
+++ b/src/components/Setting/config/general.ts
@@ -1,9 +1,7 @@
import { useDataStore, useMusicStore, useSettingStore } from "@/stores";
import { usePlayerController } from "@/core/player/PlayerController";
import { isElectron } from "@/utils/env";
-import {
- openExcludeComment,
-} from "@/utils/modal";
+import { openExcludeComment } from "@/utils/modal";
import { sendRegisterProtocol } from "@/utils/protocol";
import { SettingConfig } from "@/types/settings";
import { ref, computed, h } from "vue";
@@ -95,7 +93,7 @@ export const useGeneralSettings = (): SettingConfig => {
window.$message.error(errorMsg);
}
}
- } catch (error) {
+ } catch {
window.$message.error("设置导出出错");
}
};
@@ -108,7 +106,10 @@ export const useGeneralSettings = (): SettingConfig => {
h(
NAlert,
{ type: "warning", showIcon: true, style: { marginBottom: "12px" } },
- { default: () => "导入设置将覆盖当前所有配置(包括主题、快捷键、音效设置等)并重启软件。" },
+ {
+ default: () =>
+ "导入设置将覆盖当前所有配置(包括主题、快捷键、音效设置等)并重启软件。",
+ },
),
h("div", null, "是否继续?"),
]),
@@ -127,26 +128,26 @@ export const useGeneralSettings = (): SettingConfig => {
// "status-store",
// "music-store",
];
-
- storesToRestore.forEach(key => {
+
+ storesToRestore.forEach((key) => {
if (data.renderer[key]) {
localStorage.setItem(key, data.renderer[key]);
restoredCount++;
}
});
}
-
+
if (restoredCount > 0 || data.electron) {
- window.$message.success("设置导入成功,即将重启");
- setTimeout(() => {
- window.location.reload();
- }, 1000);
+ window.$message.success("设置导入成功,即将重启");
+ setTimeout(() => {
+ window.location.reload();
+ }, 1000);
} else {
- window.$message.warning("未找到可恢复的设置数据");
+ window.$message.warning("未找到可恢复的设置数据");
}
} else {
if (result?.error !== "cancelled") {
- window.$message.error("设置导入失败: " + (result?.error || "未知错误"));
+ window.$message.error("设置导入失败: " + (result?.error || "未知错误"));
}
}
} catch (error) {
diff --git a/src/components/Setting/config/network.ts b/src/components/Setting/config/network.ts
index 75908946b..d7dc60f33 100644
--- a/src/components/Setting/config/network.ts
+++ b/src/components/Setting/config/network.ts
@@ -170,7 +170,7 @@ export const useNetworkSettings = (): SettingConfig => {
window.$message.success(`已成功连接到 Last.fm 账号: ${sessionResponse.session.name}`);
lastfmAuthLoading.value = false;
}
- } catch (error) {
+ } catch {
// 用户还未授权,继续等待
}
}, 2000);
diff --git a/src/core/player/LyricManager.ts b/src/core/player/LyricManager.ts
index 3dd1d295e..f5e863ed4 100644
--- a/src/core/player/LyricManager.ts
+++ b/src/core/player/LyricManager.ts
@@ -70,9 +70,9 @@ class LyricManager {
public async switchLyricSource(source: string) {
const statusStore = useStatusStore();
const musicStore = useMusicStore();
-
+
if (statusStore.preferredLyricSource === source) return;
-
+
statusStore.preferredLyricSource = source;
// 保存并强制重新加载歌词
if (musicStore.playSong) {
@@ -415,7 +415,11 @@ class LyricManager {
if (neteaseData?.lrc?.lyric) {
lrcLines = parseLrc(neteaseData.lrc.lyric) || [];
if (neteaseData?.tlyric?.lyric)
- lrcLines = this.alignLyrics(lrcLines, parseLrc(neteaseData.tlyric.lyric), "translatedLyric");
+ lrcLines = this.alignLyrics(
+ lrcLines,
+ parseLrc(neteaseData.tlyric.lyric),
+ "translatedLyric",
+ );
if (neteaseData?.romalrc?.lyric)
lrcLines = this.alignLyrics(lrcLines, parseLrc(neteaseData.romalrc.lyric), "romanLyric");
}
@@ -424,7 +428,11 @@ class LyricManager {
if (neteaseData?.yrc?.lyric) {
yrcLines = parseYrc(neteaseData.yrc.lyric) || [];
if (neteaseData?.ytlrc?.lyric)
- yrcLines = this.alignLyrics(yrcLines, parseLrc(neteaseData.ytlrc.lyric), "translatedLyric");
+ yrcLines = this.alignLyrics(
+ yrcLines,
+ parseLrc(neteaseData.ytlrc.lyric),
+ "translatedLyric",
+ );
if (neteaseData?.yromalrc?.lyric)
yrcLines = this.alignLyrics(yrcLines, parseLrc(neteaseData.yromalrc.lyric), "romanLyric");
}
@@ -444,7 +452,7 @@ class LyricManager {
// 3. 选择源
let selected = statusStore.preferredLyricSource;
-
+
// 如果没有偏好或偏好不可用,使用默认优先级
if (!selected || !candidates[selected]) {
// 默认优先级: TTML > QM > YRC > LRC
@@ -456,14 +464,14 @@ class LyricManager {
// 4. 应用结果
const finalData = (selected && candidates[selected]) || { lrcData: [], yrcData: [] };
-
+
statusStore.usingTTMLLyric = selected === "TTML";
statusStore.usingQRCLyric = selected === "QM";
// 排除过滤 & 简繁转换
let processedData = this.handleLyricExclude(finalData);
processedData = await this.applyChineseVariant(processedData);
-
+
// 设置最终歌词
this.setFinalLyric(processedData, req);
@@ -885,23 +893,21 @@ class LyricManager {
if (!str) return str;
// If the entire string is enclosed in brackets (e.g. "(Chorus)"), remove them if not in enclosure mode
- if (!isEnclosure && /^\s*[\((][^()()]*[\))]\s*$/.test(str)) {
+ if (!isEnclosure && /^\s*[((][^()()]*[))]\s*$/.test(str)) {
return str
- .replace(/^\s*[\((]/, "")
- .replace(/[\))]\s*$/, "")
+ .replace(/^\s*[((]/, "")
+ .replace(/[))]\s*$/, "")
.trim();
}
- let res = str.replace(/[\((]/g, startStr);
+ let res = str.replace(/[((]/g, startStr);
if (isEnclosure) {
- res = res.replace(/[\))]/g, endStr);
+ res = res.replace(/[))]/g, endStr);
} else {
// Separator mode:
// 1. Remove ) if it's at the end of the string (effectively just a closing marker)
// 2. Otherwise replace ) with endStr (usually space)
- res = res
- .replace(/[\))](?=\s*$)/g, "")
- .replace(/[\))]/g, endStr);
+ res = res.replace(/[))](?=\s*$)/g, "").replace(/[))]/g, endStr);
// Cleanup double dashes if the separator contains a dash
if (startStr.includes("-")) {
@@ -917,15 +923,15 @@ class LyricManager {
// If the whole line is in brackets and we are NOT in enclosure mode (e.g. dash mode),
// we likely want to strip the brackets entirely instead of replacing them with dashes.
const fullText = line.words.map((w) => w.word).join("");
- const isFullBracket = /^\s*[\((][^()()]*[\))]\s*$/.test(fullText);
+ const isFullBracket = /^\s*[((][^()()]*[))]\s*$/.test(fullText);
if (isFullBracket && !isEnclosure) {
// Remove the first opening bracket found in the words
let foundStart = false;
for (const word of line.words) {
if (foundStart) break;
- if (/[\((]/.test(word.word)) {
- word.word = word.word.replace(/[\((]/, "");
+ if (/[((]/.test(word.word)) {
+ word.word = word.word.replace(/[((]/, "");
foundStart = true;
}
}
@@ -934,12 +940,11 @@ class LyricManager {
for (let i = line.words.length - 1; i >= 0; i--) {
if (foundEnd) break;
const word = line.words[i];
- if (/[\))]/.test(word.word)) {
+ if (/[))]/.test(word.word)) {
// Find the last occurrence of ) or )
const lastIndex = Math.max(word.word.lastIndexOf(")"), word.word.lastIndexOf(")"));
if (lastIndex !== -1) {
- word.word =
- word.word.substring(0, lastIndex) + word.word.substring(lastIndex + 1);
+ word.word = word.word.substring(0, lastIndex) + word.word.substring(lastIndex + 1);
foundEnd = true;
}
}
@@ -948,14 +953,14 @@ class LyricManager {
// Normal replacement logic
line.words.forEach((word, index) => {
// Replace opening brackets
- word.word = word.word.replace(/[\((]/g, startStr);
+ word.word = word.word.replace(/[((]/g, startStr);
if (isEnclosure) {
// Enclosure mode: simply replace closing brackets with endStr
- word.word = word.word.replace(/[\))]/g, endStr);
+ word.word = word.word.replace(/[))]/g, endStr);
} else {
// Separator mode: logic to handle closing brackets nicely
- word.word = word.word.replace(/[\))]/g, (_, offset, string) => {
+ word.word = word.word.replace(/[))]/g, (_, offset, string) => {
const isAtEnd = offset === string.length - 1;
// If ) is at the end of the word...
if (isAtEnd) {
@@ -1159,15 +1164,15 @@ class LyricManager {
public async handleLyric(song: SongType) {
const settingStore = useSettingStore();
const statusStore = useStatusStore();
-
+
// 标记当前歌词请求(避免旧请求覆盖新请求)
const req = ++this.lyricReqSeq;
this.activeLyricReq = req;
-
+
// 加载歌词源偏好
const pref = await this.getLyricPreference(song.id);
statusStore.preferredLyricSource = pref;
-
+
const isStreaming = song?.type === "streaming";
try {
let lyricData: SongLyric = { lrcData: [], yrcData: [] };