Skip to content

Commit fddaaed

Browse files
authored
Merge pull request #659 from imsyy/dev-perf
✨ feat: 增加播放时音质切换
2 parents bd2977e + aa1b7ed commit fddaaed

12 files changed

Lines changed: 478 additions & 47 deletions

File tree

components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ declare module 'vue' {
107107
NP: typeof import('naive-ui')['NP']
108108
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
109109
NPopover: typeof import('naive-ui')['NPopover']
110+
NPopselect: typeof import('naive-ui')['NPopselect']
110111
NProgress: typeof import('naive-ui')['NProgress']
111112
NQrCode: typeof import('naive-ui')['NQrCode']
112113
NRadio: typeof import('naive-ui')['NRadio']

electron/main/services/MusicCacheService.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { existsSync, createWriteStream } from "fs";
2-
import { unlink, rename } from "fs/promises";
2+
import { unlink, rename, stat } from "fs/promises";
33
import { pipeline } from "stream/promises";
44
import { CacheService } from "./CacheService";
55
import { useStore } from "../store";
@@ -105,6 +105,13 @@ export class MusicCacheService {
105105

106106
await pipeline(downloadStream, fileStream);
107107

108+
// 检查文件大小,避免空文件(stat 在文件不存在时会抛出错误)
109+
const stats = await stat(tempPath);
110+
if (stats.size === 0) {
111+
await unlink(tempPath).catch(() => {});
112+
throw new Error("下载的文件为空");
113+
}
114+
108115
// 下载成功后,将临时文件重命名为正式缓存文件
109116
await rename(tempPath, filePath);
110117

src/components/Layout/Nav.vue

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,28 @@
3333
<!-- 客户端控制 -->
3434
<n-flex v-if="isElectron" align="center" class="client-control">
3535
<n-divider class="divider" vertical />
36-
<n-button :focusable="false" title="最小化" tertiary circle @click="min">
37-
<template #icon>
38-
<SvgIcon name="WindowMinimize" />
39-
</template>
40-
</n-button>
41-
<n-button
42-
:focusable="false"
43-
:title="isMax ? '还原' : '最大化'"
44-
tertiary
45-
circle
46-
@click="maxOrRes"
47-
>
48-
<template #icon>
49-
<SvgIcon :name="isMax ? 'WindowRestore' : 'WindowMaximize'" />
50-
</template>
51-
</n-button>
36+
<div class="min-button-wrapper" @click="min" title="最小化">
37+
<n-button :focusable="false" title="最小化" tertiary circle @click.stop="min">
38+
<template #icon>
39+
<SvgIcon name="WindowMinimize" />
40+
</template>
41+
</n-button>
42+
<div class="min-expanded-area"></div>
43+
</div>
44+
<div class="max-button-wrapper" @click="maxOrRes" :title="isMax ? '还原' : '最大化'">
45+
<n-button
46+
:focusable="false"
47+
:title="isMax ? '还原' : '最大化'"
48+
tertiary
49+
circle
50+
@click.stop="maxOrRes"
51+
>
52+
<template #icon>
53+
<SvgIcon :name="isMax ? 'WindowRestore' : 'WindowMaximize'" />
54+
</template>
55+
</n-button>
56+
<div class="max-expanded-area"></div>
57+
</div>
5258
<div class="close-button-wrapper" @click="tryClose" title="关闭">
5359
<n-button :focusable="false" title="关闭" tertiary circle @click.stop="tryClose">
5460
<template #icon>
@@ -239,20 +245,32 @@ onMounted(() => {
239245
.divider {
240246
margin: 0 0 0 12px;
241247
}
248+
.min-button-wrapper,
249+
.max-button-wrapper,
242250
.close-button-wrapper {
243251
position: relative;
244252
cursor: pointer;
245-
.close-expanded-area {
246-
position: fixed;
247-
top: 0;
248-
right: 0;
249-
width: 60px;
250-
height: 70px;
251-
background-color: transparent;
252-
cursor: pointer;
253-
-webkit-app-region: no-drag;
254-
z-index: 1000;
255-
}
253+
}
254+
.min-expanded-area,
255+
.max-expanded-area,
256+
.close-expanded-area {
257+
position: fixed;
258+
top: 0;
259+
width: 50px;
260+
height: 70px;
261+
background-color: transparent;
262+
cursor: pointer;
263+
-webkit-app-region: no-drag;
264+
z-index: 1000;
265+
}
266+
.close-expanded-area {
267+
right: 0;
268+
}
269+
.max-expanded-area {
270+
right: 50px;
271+
}
272+
.min-expanded-area {
273+
right: 100px;
256274
}
257275
}
258276
}

src/components/Player/LyricMenu.vue

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,41 @@
44
<SvgIcon name="Copy" />
55
</div>
66
<div class="divider" />
7-
<div class="menu-icon" @click="changeOffset(-500)">
7+
<div class="menu-icon" @click="changeOffset(-settingStore.lyricOffsetStep)">
88
<SvgIcon name="Replay5" />
99
</div>
10-
<span class="time" @click="resetOffset()">
11-
{{ currentTimeOffsetValue }}
12-
</span>
13-
<div class="menu-icon" @click="changeOffset(500)">
10+
<n-popover class="player" trigger="click" placement="left" style="padding: 8px">
11+
<template #trigger>
12+
<span class="time">
13+
{{ currentTimeOffsetValue }}
14+
</span>
15+
</template>
16+
<n-flex class="offset-menu" :size="4" vertical>
17+
<span class="title"> 歌词偏移 </span>
18+
<span class="tip"> 正值为歌词提前,单位毫秒 </span>
19+
<n-input-number
20+
v-model:value="offsetMilliseconds"
21+
class="offset-input"
22+
:precision="0"
23+
:step="100"
24+
placeholder="0"
25+
size="small"
26+
>
27+
<template #suffix>ms</template>
28+
</n-input-number>
29+
<n-button
30+
class="player"
31+
size="small"
32+
secondary
33+
strong
34+
@click="resetOffset"
35+
:disabled="offsetMilliseconds == 0"
36+
>
37+
清零
38+
</n-button>
39+
</n-flex>
40+
</n-popover>
41+
<div class="menu-icon" @click="changeOffset(settingStore.lyricOffsetStep)">
1442
<SvgIcon name="Forward5" />
1543
</div>
1644
<div class="divider" />
@@ -21,10 +49,11 @@
2149
</template>
2250

2351
<script setup lang="ts">
24-
import { useMusicStore, useStatusStore } from "@/stores";
52+
import { useMusicStore, useSettingStore, useStatusStore } from "@/stores";
2553
import { openSetting, openCopyLyrics } from "@/utils/modal";
2654
2755
const musicStore = useMusicStore();
56+
const settingStore = useSettingStore();
2857
const statusStore = useStatusStore();
2958
3059
/**
@@ -33,13 +62,23 @@ const statusStore = useStatusStore();
3362
const currentSongId = computed(() => musicStore.playSong?.id as number | undefined);
3463
3564
/**
36-
* 当前进度偏移值(显示为秒,保留1位小数)
65+
* 当前进度偏移值
3766
*/
3867
const currentTimeOffsetValue = computed(() => {
3968
const currentTimeOffset = statusStore.getSongOffset(currentSongId.value);
40-
// 将毫秒转换为秒显示(保留1位小数)
41-
const offsetSeconds = (currentTimeOffset / 1000).toFixed(1);
42-
return currentTimeOffset > 0 ? `+${offsetSeconds}` : offsetSeconds;
69+
if (currentTimeOffset === 0) return "0";
70+
// 将毫秒转换为秒显示
71+
const offsetSeconds = parseFloat((currentTimeOffset / 1000).toFixed(2));
72+
return currentTimeOffset > 0 ? `+${offsetSeconds}` : `${offsetSeconds}`;
73+
});
74+
75+
const offsetMilliseconds = computed({
76+
get: () => {
77+
return statusStore.getSongOffset(currentSongId.value);
78+
},
79+
set: (val: number | null) => {
80+
statusStore.setSongOffset(currentSongId.value, val || 0);
81+
},
4382
});
4483
4584
/**
@@ -123,6 +162,36 @@ const resetOffset = () => {
123162
}
124163
}
125164
165+
.offset-menu {
166+
width: 180px;
167+
.title {
168+
font-size: 14px;
169+
line-height: normal;
170+
}
171+
.tip {
172+
font-size: 12px;
173+
opacity: 0.6;
174+
}
175+
:deep(.n-input) {
176+
--n-caret-color: rgb(var(--main-cover-color));
177+
--n-color: rgba(var(--main-cover-color), 0.1);
178+
--n-color-focus: rgba(var(--main-cover-color), 0.1);
179+
--n-text-color: rgb(var(--main-cover-color));
180+
--n-border-hover: 1px solid rgba(var(--main-cover-color), 0.28);
181+
--n-border-focus: 1px solid rgba(var(--main-cover-color), 0.28);
182+
--n-suffix-text-color: rgb(var(--main-cover-color));
183+
--n-box-shadow-focus: 0 0 8px 0 rgba(var(--main-cover-color), 0.3);
184+
// 文本选中颜色
185+
input {
186+
&::selection {
187+
background-color: rgba(var(--main-cover-color));
188+
}
189+
}
190+
.n-button {
191+
--n-text-color: rgb(var(--main-cover-color));
192+
}
193+
}
194+
}
126195
.lyric,
127196
.lyric-am {
128197
&:hover {

src/components/Player/MainAMLyric.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
:class="['lyric-am', { pure: statusStore.pureLyricMode }]"
66
:style="{
77
'--amll-lp-color': 'rgb(var(--main-cover-color, 239 239 239))',
8+
'--amll-lp-hover-bg-color': 'rgba(var(--main-cover-color), 0.08)',
89
}"
910
>
1011
<div v-if="statusStore.lyricLoading" class="lyric-loading">歌词正在加载中...</div>

0 commit comments

Comments
 (0)