feat: 优化列表播放方式 & 添加一些桌面歌词配置 #536

This commit is contained in:
imsyy
2025-11-09 00:39:55 +08:00
parent 5edbd66398
commit eb0094c189
16 changed files with 441 additions and 269 deletions

1
components.d.ts vendored
View File

@@ -101,7 +101,6 @@ declare module 'vue' {
NQrCode: typeof import('naive-ui')['NQrCode']
NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NResult: typeof import('naive-ui')['NResult']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSkeleton: typeof import('naive-ui')['NSkeleton']

View File

@@ -40,7 +40,7 @@
"@electron-toolkit/utils": "^4.0.0",
"@imsyy/color-utils": "^1.0.2",
"@material/material-color-utilities": "^0.3.0",
"@neteasecloudmusicapienhanced/api": "^4.29.12",
"@neteasecloudmusicapienhanced/api": "^4.29.15",
"@pixi/app": "^7.4.3",
"@pixi/core": "^7.4.3",
"@pixi/display": "^7.4.3",
@@ -49,7 +49,8 @@
"@pixi/filter-color-matrix": "^7.4.3",
"@pixi/sprite": "^7.4.3",
"@vueuse/core": "^13.9.0",
"axios": "^1.12.2",
"axios": "^1.13.2",
"axios-retry": "^4.5.0",
"change-case": "^5.4.4",
"dayjs": "^1.11.18",
"electron-dl": "^4.0.0",
@@ -91,7 +92,7 @@
"@vitejs/plugin-vue": "^6.0.1",
"ajv": "^8.17.1",
"crypto-js": "^4.2.0",
"electron": "^38.2.2",
"electron": "38.2.2",
"electron-builder": "^26.0.12",
"electron-log": "^5.4.3",
"electron-vite": "^4.0.1",
@@ -107,13 +108,13 @@
"typescript": "^5.9.3",
"unplugin-auto-import": "^20.2.0",
"unplugin-vue-components": "^29.1.0",
"vite": "^7.1.9",
"vite": "^7.2.2",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-vue-devtools": "^8.0.3",
"vite-plugin-wasm": "^3.5.0",
"vue": "^3.5.22",
"vue": "^3.5.24",
"vue-router": "^4.5.1",
"vue-tsc": "^3.1.1"
"vue-tsc": "^3.1.3"
},
"pnpm": {
"overrides": {

449
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,8 @@ const config: LyricConfig = {
fontFamily: "system-ui",
fontSize: 24,
fontIsBold: false,
showTran: true,
showYrc: true,
isDoubleLine: true,
position: "both",
limitBounds: false,

View File

@@ -60,7 +60,11 @@
:hiddenCover="hiddenCover"
:hiddenAlbum="hiddenAlbum"
:hiddenSize="hiddenSize"
@dblclick.stop="player.updatePlayList(listData, itemData, playListId)"
@dblclick.stop="
doubleClickAction === 'add'
? player.addNextSong(itemData, true)
: player.updatePlayList(listData, itemData, playListId)
"
@contextmenu.stop="
songListMenuRef?.openDropdown(
$event,
@@ -153,6 +157,8 @@ const props = withDefaults(
playListId?: number;
// 是否为每日推荐
isDailyRecommend?: boolean;
// 双击播放操作
doubleClickAction?: "all" | "add";
}>(),
{
type: "song",

View File

@@ -416,6 +416,30 @@
/>
</n-flex>
</n-card>
<n-card class="set-item">
<div class="label">
<n-text class="name">显示逐字歌词</n-text>
<n-text class="tip" :depth="3">是否显示桌面歌词逐字效果</n-text>
</div>
<n-switch
v-model:value="desktopLyricConfig.showYrc"
:round="false"
class="set"
@update:value="saveDesktopLyricConfig"
/>
</n-card>
<n-card class="set-item">
<div class="label">
<n-text class="name">显示翻译</n-text>
<n-text class="tip" :depth="3">是否显示桌面歌词翻译</n-text>
</div>
<n-switch
v-model:value="desktopLyricConfig.showTran"
:round="false"
class="set"
@update:value="saveDesktopLyricConfig"
/>
</n-card>
<n-card class="set-item">
<div class="label">
<n-text class="name">文字加粗</n-text>
@@ -549,13 +573,21 @@ const saveDesktopLyricConfig = () => {
const restoreDesktopLyricConfig = () => {
try {
if (!isElectron) return;
window.electron.ipcRenderer.send(
"update-desktop-lyric-option",
defaultDesktopLyricConfig,
true,
);
window.$message.success("桌面歌词配置已恢复默认");
console.log(defaultDesktopLyricConfig, desktopLyricConfig);
window.$dialog.warning({
title: "警告",
content: "此操作将恢复所有桌面歌词配置为默认值,是否继续?",
positiveText: "确定",
negativeText: "取消",
onPositiveClick: () => {
window.electron.ipcRenderer.send(
"update-desktop-lyric-option",
defaultDesktopLyricConfig,
true,
);
window.$message.success("桌面歌词配置已恢复默认");
console.log(defaultDesktopLyricConfig, desktopLyricConfig);
},
});
} catch (error) {
console.error("Failed to save options:", error);
window.$message.error("桌面歌词配置恢复默认失败");

View File

@@ -13,12 +13,12 @@
<n-card class="set-item">
<div class="label">
<n-text class="name">真实 IP 地址</n-text>
<n-text class="tip" :depth="3">可在此处输入国内 IP</n-text>
<n-text class="tip" :depth="3">可在此处输入国内 IP不填写则为随机</n-text>
</div>
<n-input
v-model:value="settingStore.realIP"
:disabled="!settingStore.useRealIP"
placeholder="请填写真实 IP 地址"
placeholder="127.0.0.1"
class="set"
>
<template #prefix>

View File

@@ -246,7 +246,7 @@ export const useSettingStore = defineStore("setting", {
proxyServe: "127.0.0.1",
proxyPort: 80,
useRealIP: false,
realIP: "116.25.146.177",
realIP: "",
}),
getters: {
/**

View File

@@ -37,6 +37,10 @@ export interface LyricConfig {
fontIsBold: boolean;
/** 是否双行 */
isDoubleLine: boolean;
/** 显示翻译 */
showTran: boolean;
/** 是否开启逐字歌词 */
showYrc: boolean;
/** 文本排版位置 */
position: "left" | "center" | "right" | "both";
/** 是否限制在屏幕边界内拖动 */

View File

@@ -587,6 +587,7 @@ class Player {
const statusStore = useStatusStore();
const settingStore = useSettingStore();
const sessionId = this.newSession();
try {
// 获取播放数据
const playSongData = getPlaySongData();
@@ -595,100 +596,91 @@ class Player {
return;
}
const { id, dj, path, type } = playSongData;
// 更改当前播放歌曲
musicStore.playSong = playSongData;
// 更改状态
statusStore.playLoading = true;
// 清理旧播放器与计时器
this.resetPlayerCore();
// 本地歌曲
if (path) {
if (this.isStale(sessionId)) return;
await this.createPlayer(`file://${path}`, autoPlay, seek);
// 获取歌曲元信息
await this.parseLocalMusicInfo(path);
try {
await this.createPlayer(`file://${path}`, autoPlay, seek);
await this.parseLocalMusicInfo(path);
} catch (err) {
console.error("播放器初始化错误(本地):", err);
}
}
// 在线歌曲
else if (id && dataStore.playList.length) {
const songId = type === "radio" ? dj?.id : id;
if (!songId) throw new Error("Get song id error");
// 优先使用预载的下一首 URL若命中缓存
const cached = this.nextPrefetch;
if (cached && cached.id === songId && cached.url) {
statusStore.playUblock = cached.ublock;
if (this.isStale(sessionId)) return;
await this.createPlayer(cached.url, autoPlay, seek);
} else {
// 官方地址失败或仅为试听时再尝试解锁Electron 且非电台且开启解灰)
const canUnlock = isElectron && type !== "radio" && settingStore.useSongUnlock;
const { url: officialUrl, isTrial } = await getOnlineUrl(songId);
if (officialUrl && !isTrial) {
// 官方可播放且非试听
statusStore.playUblock = false;
if (this.isStale(sessionId)) return;
await this.createPlayer(officialUrl, autoPlay, seek);
} else if (canUnlock) {
// 官方失败或为试听时尝试解锁
const unlockUrl = await getUnlockSongUrl(playSongData);
if (unlockUrl) {
statusStore.playUblock = true;
console.log("🎼 Song unlock successfully:", unlockUrl);
if (this.isStale(sessionId)) return;
await this.createPlayer(unlockUrl, autoPlay, seek);
} else if (officialUrl) {
// 解锁失败,若允许试听则播放试听
if (isTrial && settingStore.playSongDemo) {
window.$message.warning("当前歌曲仅可试听,请开通会员后重试");
statusStore.playUblock = false;
if (this.isStale(sessionId)) return;
await this.createPlayer(officialUrl, autoPlay, seek);
} else {
// 不允许试听
statusStore.playUblock = false;
if (statusStore.playIndex === dataStore.playList.length - 1) {
statusStore.$patch({ playStatus: false, playLoading: false });
window.$message.warning("当前列表歌曲无法播放,请更换歌曲");
} else {
window.$message.error("该歌曲暂无音源,跳至下一首");
// 防止切歌保护状态阻塞跳转
this.switching = false;
await this.nextOrPrev("next");
return;
}
}
} else {
// 无任何可用地址
statusStore.playUblock = false;
if (statusStore.playIndex === dataStore.playList.length - 1) {
statusStore.$patch({ playStatus: false, playLoading: false });
window.$message.warning("当前列表歌曲无法播放,请更换歌曲");
} else {
window.$message.error("该歌曲暂无音源,跳至下一首");
// 防止切歌保护状态阻塞跳转
this.switching = false;
await this.nextOrPrev("next");
return;
}
}
let playerUrl: string | null = null;
// 获取歌曲 URL 单独 try-catch
try {
const songId = type === "radio" ? dj?.id : id;
if (!songId) throw new Error("获取歌曲 ID 失败");
// 使用预载缓存
const cached = this.nextPrefetch;
if (cached && cached.id === songId && cached.url) {
playerUrl = cached.url;
statusStore.playUblock = cached.ublock;
} else {
if (dataStore.playList.length === 1) {
this.resetStatus();
window.$message.warning("当前播放列表已无可播放歌曲,请更换");
return;
const canUnlock = isElectron && type !== "radio" && settingStore.useSongUnlock;
const { url: officialUrl, isTrial } = await getOnlineUrl(songId);
if (officialUrl && !isTrial) {
playerUrl = officialUrl;
statusStore.playUblock = false;
} else if (canUnlock) {
const unlockUrl = await getUnlockSongUrl(playSongData);
if (unlockUrl) {
playerUrl = unlockUrl;
statusStore.playUblock = true;
console.log("🎼 Song unlock successfully:", unlockUrl);
} else if (officialUrl && isTrial && settingStore.playSongDemo) {
window.$message.warning("当前歌曲仅可试听,请开通会员后重试");
playerUrl = officialUrl;
statusStore.playUblock = false;
} else {
playerUrl = null;
}
} else {
window.$message.error("该歌曲无法播放,跳至下一首");
// 防止切歌保护状态阻塞跳转
this.switching = false;
await this.nextOrPrev();
return;
playerUrl = null;
}
}
if (!playerUrl) {
window.$message.error("该歌曲暂无音源,跳至下一首");
this.switching = false;
await this.nextOrPrev("next");
return;
}
} catch (err) {
console.error("❌ 获取歌曲地址出错:", err);
window.$message.error("获取歌曲地址失败,跳至下一首");
this.switching = false;
await this.nextOrPrev("next");
return;
}
// 有有效 URL 才创建播放器
if (playerUrl && !this.isStale(sessionId)) {
try {
await this.createPlayer(playerUrl, autoPlay, seek);
} catch (err) {
console.error("播放器初始化错误(在线):", err);
}
}
}
} catch (error) {
console.error("❌ 初始化音乐播放器出错:", error);
window.$message.error("播放遇到错误,尝试软件热重载");
// this.errorNext();
} catch (err) {
console.error("❌ 初始化音乐播放器出错:", err);
window.$message.error("播放遇到错误,尝试下一首");
this.switching = false;
await this.nextOrPrev("next");
} finally {
this.switching = false;
}

View File

@@ -3,6 +3,7 @@ import { isDev, isElectron } from "./env";
import { useSettingStore } from "@/stores";
import { getCookie } from "./cookie";
import { isLogin } from "./auth";
import axiosRetry from "axios-retry";
// 全局地址
const baseURL: string = String(isDev ? "/api/netease" : import.meta.env["VITE_API_URL"]);
@@ -16,6 +17,12 @@ const server: AxiosInstance = axios.create({
timeout: 15000,
});
// 请求重试
axiosRetry(server, {
// 重试次数
retries: 3,
});
// 请求拦截器
server.interceptors.request.use(
(request) => {
@@ -33,7 +40,11 @@ server.interceptors.request.use(
}
// 自定义 realIP
if (settingStore.useRealIP) {
request.params.realIP = settingStore.realIP || "116.25.146.177";
if (settingStore.realIP) {
request.params.realIP = settingStore.realIP;
} else {
request.params.randomCNIP = true;
}
}
// proxy
if (settingStore.proxyProtocol !== "off") {
@@ -90,8 +101,6 @@ server.interceptors.response.use(
// meta: "若持续发生,可尝试软件热重载",
// duration: 5000,
// });
// 控制台输出
window.$message.warning("请求出错,若持续发生,可尝试软件热重载");
// 返回错误
return Promise.reject(error);
},

View File

@@ -69,7 +69,9 @@
:ref="(el) => line.active && (currentLineRef = el as HTMLElement)"
>
<!-- 逐字歌词渲染 -->
<template v-if="lyricData?.yrcData?.length && line.line?.contents?.length">
<template
v-if="lyricConfig.showYrc && lyricData?.yrcData?.length && line.line?.contents?.length"
>
<span
class="scroll-content"
:style="getScrollStyle(line)"
@@ -210,7 +212,7 @@ const renderLyricLines = computed<RenderLine[]>(() => {
if (!current) return [];
const safeEnd = getSafeEndTime(lyrics, idx);
// 有翻译:保留第二行显示翻译,第一行显示原文(逐字由 contents 驱动)
if (current.tran && current.tran.trim().length > 0) {
if (lyricConfig.showTran && current.tran && current.tran.trim().length > 0) {
const lines: RenderLine[] = [
{ line: { ...current, endTime: safeEnd }, index: idx, key: `${idx}:orig`, active: true },
{
@@ -279,7 +281,7 @@ const currentContentRef = ref<HTMLElement | null>(null);
const scrollStartAtProgress = 0.5;
/**
* 逐字歌词滚动样式计算(基于毫秒游标插值)
* 歌词滚动样式计算
* - 容器 `currentLineRef` 与内容 `currentContentRef` 分别记录当前激活行与其文本内容
* - 当内容宽度超过容器宽度overflow > 0才会触发水平滚动
* - 进度采用毫秒锚点插值(`playSeekMs`),并以当前行的 `time` 与有效 `endTime` 计算区间
@@ -440,7 +442,11 @@ watchThrottled(
const next = { fontSize: size };
window.electron.ipcRenderer.send("update-desktop-lyric-option", next, true);
},
{ immediate: true, throttle: 100 },
{
leading: true,
immediate: true,
throttle: 100,
},
);
/**

View File

@@ -155,6 +155,7 @@
:data="albumDataShow"
:loading="loading"
:height="songListHeight"
:doubleClickAction="searchData?.length ? 'add' : 'all'"
hidden-album
@scroll="listScroll"
/>

View File

@@ -149,6 +149,7 @@
:loading="loading"
:height="songListHeight"
:playListId="playlistId"
:doubleClickAction="searchData?.length ? 'add' : 'all'"
@scroll="listScroll"
@removeSong="removeSong"
/>
@@ -418,7 +419,7 @@ onMounted(async () => {
return;
}
}
// 获取我喜欢的音乐歌单ID
const likedPlaylistId = dataStore.userLikeData.playlists?.[0]?.id;
if (likedPlaylistId) {

View File

@@ -194,6 +194,7 @@
:loading="loading"
:height="songListHeight"
:playListId="playlistId"
:doubleClickAction="searchData?.length ? 'add' : 'all'"
@scroll="listScroll"
@removeSong="removeSong"
/>

View File

@@ -156,6 +156,7 @@
:loading="loading"
:height="songListHeight"
:radioId="radioId"
:doubleClickAction="searchData?.length ? 'add' : 'all'"
type="radio"
@scroll="listScroll"
/>