Skip to content

Commit f5db0f8

Browse files
committed
✨ feat: 完善进度条 & 进度调节时间吸附
1 parent 20b7ec1 commit f5db0f8

6 files changed

Lines changed: 129 additions & 37 deletions

File tree

src/components/Player/PlayerSlider.vue

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717
</template>
1818

1919
<script setup lang="ts">
20-
import { useStatusStore } from "@/stores";
20+
import { useMusicStore, useSettingStore, useStatusStore } from "@/stores";
2121
import { msToTime } from "@/utils/time";
2222
import { usePlayerController } from "@/core/player/PlayerController";
23+
import { LyricLine } from "@applemusic-like-lyrics/lyric";
2324
2425
withDefaults(defineProps<{ showTooltip?: boolean }>(), { showTooltip: true });
2526
26-
const player = usePlayerController();
27+
const musicStore = useMusicStore();
2728
const statusStore = useStatusStore();
29+
const settingStore = useSettingStore();
30+
31+
const player = usePlayerController();
2832
2933
// 拖动时的临时值
3034
const dragValue = ref(0);
@@ -45,9 +49,7 @@ const sliderProgress = computed({
4549
return;
4650
}
4751
// 结束或者为点击
48-
useThrottleFn((value: number) => {
49-
player.setSeek(value);
50-
}, 30);
52+
useThrottleFn((value: number) => setSeek(value), 30);
5153
},
5254
});
5355
@@ -62,12 +64,47 @@ const startDrag = () => {
6264
const endDrag = () => {
6365
isDragging.value = false;
6466
// 直接更改进度
65-
player.setSeek(dragValue.value);
67+
setSeek(dragValue.value);
68+
};
69+
70+
/**
71+
* 获取当前时间最近一句歌词
72+
* @param value 当前时间
73+
* @returns 最近一句歌词的开始时间和内容
74+
*/
75+
const getCurrentLyric = (value: number) => {
76+
const lyric = toRaw(musicStore.songLyric.lrcData);
77+
if (!lyric?.length) return null;
78+
// 查找最近一句歌词
79+
let nearestLyric: LyricLine | null = null;
80+
for (let i = lyric.length - 1; i >= 0; i--) {
81+
if (value >= lyric[i].startTime) {
82+
nearestLyric = lyric[i];
83+
break;
84+
}
85+
}
86+
return {
87+
time: nearestLyric?.startTime,
88+
text: nearestLyric?.words?.[0]?.word || "",
89+
};
90+
};
91+
92+
// 调节进度
93+
const setSeek = (value: number) => {
94+
if (settingStore.progressAdjustLyric) {
95+
const nearestLyric = getCurrentLyric(value);
96+
player.setSeek(nearestLyric?.time ?? value);
97+
return;
98+
}
99+
player.setSeek(value);
66100
};
67101
68102
// 格式化提示
69103
const formatTooltip = (value: number) => {
70-
return `${msToTime(value)} / ${msToTime(statusStore.duration)}`;
104+
const nearestLyric = getCurrentLyric(value);
105+
return nearestLyric
106+
? `${msToTime(value)} / ${nearestLyric.text.length > 30 ? nearestLyric.text.slice(0, 30) + "..." : nearestLyric.text}`
107+
: msToTime(value);
71108
};
72109
</script>
73110

@@ -85,5 +122,20 @@ const formatTooltip = (value: number) => {
85122
}
86123
}
87124
}
125+
:deep(.n-slider-handles) {
126+
.n-slider-handle {
127+
opacity: 0;
128+
transform: scale(0.6);
129+
}
130+
}
131+
&:hover,
132+
&.drag {
133+
:deep(.n-slider-handles) {
134+
.n-slider-handle {
135+
opacity: 1;
136+
transform: scale(1);
137+
}
138+
}
139+
}
88140
}
89141
</style>

src/components/Setting/PlaySetting.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@
3030
</div>
3131
<n-switch v-model:value="settingStore.memoryLastSeek" class="set" :round="false" />
3232
</n-card>
33+
<n-card class="set-item">
34+
<div class="label">
35+
<n-text class="name">进度调节吸附最近歌词</n-text>
36+
<n-text class="tip" :depth="3">进度调节时吸附最近一句歌词</n-text>
37+
</div>
38+
<n-switch v-model:value="settingStore.progressAdjustLyric" class="set" :round="false" />
39+
</n-card>
3340
<n-card class="set-item">
3441
<div class="label">
3542
<n-text class="name">音乐渐入渐出</n-text>
@@ -192,6 +199,13 @@
192199
/>
193200
</n-card>
194201
</n-collapse-transition>
202+
<n-card class="set-item">
203+
<div class="label">
204+
<n-text class="name">播放器主色跟随封面</n-text>
205+
<n-text class="tip" :depth="3">播放器主颜色是否跟随封面主色,下一曲生效</n-text>
206+
</div>
207+
<n-switch v-model:value="settingStore.playerFollowCoverColor" class="set" :round="false" />
208+
</n-card>
195209
<n-card class="set-item">
196210
<div class="label">
197211
<n-text class="name">显示前奏倒计时</n-text>

src/core/player/PlayerController.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,10 @@ class PlayerController {
539539
return Math.floor(audioManager.currentTime * 1000);
540540
}
541541

542-
/** 设置进度 */
542+
/**
543+
* 设置进度
544+
* @param time 时间 (ms)
545+
*/
543546
public setSeek(time: number) {
544547
const statusStore = useStatusStore();
545548
const audioManager = useAudioManager();

src/stores/setting.ts

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@ export interface SettingState {
1212
themeMode: "light" | "dark" | "auto";
1313
/** 主题类别 */
1414
themeColorType:
15-
| "default"
16-
| "orange"
17-
| "blue"
18-
| "pink"
19-
| "brown"
20-
| "indigo"
21-
| "green"
22-
| "purple"
23-
| "yellow"
24-
| "teal"
25-
| "custom";
15+
| "default"
16+
| "orange"
17+
| "blue"
18+
| "pink"
19+
| "brown"
20+
| "indigo"
21+
| "green"
22+
| "purple"
23+
| "yellow"
24+
| "teal"
25+
| "custom";
2626
/** 主题自定义颜色 */
2727
themeCustomColor: string;
2828
/** 全局着色 */
@@ -103,14 +103,14 @@ export interface SettingState {
103103
proxyPort: number;
104104
/** 歌曲音质 */
105105
songLevel:
106-
| "standard"
107-
| "higher"
108-
| "exhigh"
109-
| "lossless"
110-
| "hires"
111-
| "jyeffect"
112-
| "sky"
113-
| "jymaster";
106+
| "standard"
107+
| "higher"
108+
| "exhigh"
109+
| "lossless"
110+
| "hires"
111+
| "jyeffect"
112+
| "sky"
113+
| "jymaster";
114114
/** 播放设备 */
115115
playDevice: "default" | string;
116116
/** 自动播放 */
@@ -141,6 +141,8 @@ export interface SettingState {
141141
autoHidePlayerMeta: boolean;
142142
/** 记忆最后进度 */
143143
memoryLastSeek: boolean;
144+
/** 进度调节吸附最近歌词 */
145+
progressAdjustLyric: boolean;
144146
/** 显示播放列表数量 */
145147
showPlaylistCount: boolean;
146148
/** 是否显示音乐频谱 */
@@ -250,6 +252,8 @@ export interface SettingState {
250252
registryProtocol: {
251253
orpheus: boolean;
252254
};
255+
/** 播放器跟随封面主色 */
256+
playerFollowCoverColor: boolean;
253257
}
254258

255259
export const useSettingStore = defineStore("setting", {
@@ -297,6 +301,7 @@ export const useSettingStore = defineStore("setting", {
297301
playerBackgroundFlowSpeed: 4,
298302
autoHidePlayerMeta: true,
299303
memoryLastSeek: true,
304+
progressAdjustLyric: false,
300305
showPlaylistCount: true,
301306
showSpectrums: false,
302307
smtcOpen: true,
@@ -376,6 +381,7 @@ export const useSettingStore = defineStore("setting", {
376381
registryProtocol: {
377382
orpheus: false,
378383
},
384+
playerFollowCoverColor: true,
379385
}),
380386
getters: {
381387
/**
@@ -432,11 +438,12 @@ export const useSettingStore = defineStore("setting", {
432438
}
433439
window.$message.info(
434440
`已切换至
435-
${this.themeMode === "auto"
436-
? "跟随系统"
437-
: this.themeMode === "light"
438-
? "浅色模式"
439-
: "深色模式"
441+
${
442+
this.themeMode === "auto"
443+
? "跟随系统"
444+
: this.themeMode === "light"
445+
? "浅色模式"
446+
: "深色模式"
440447
}`,
441448
{
442449
showIcon: false,

src/style/main.scss

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,20 @@ audio {
104104

105105
// slider
106106
.n-slider {
107-
.n-slider-rail {
108-
backdrop-filter: blur(10px);
107+
// .n-slider-rail {
108+
// backdrop-filter: blur(10px);
109+
// }
110+
.n-slider-handle {
111+
transition:
112+
opacity 0.3s var(--n-bezier),
113+
box-shadow 0.2s var(--n-bezier),
114+
transform 0.3s var(--n-bezier),
115+
background-color 0.3s var(--n-bezier) !important;
109116
}
110117
}
118+
.n-slider-handle-indicator {
119+
--n-indicator-border-radius: 8px !important;
120+
}
111121

112122
// popover
113123
.n-popover {
@@ -124,9 +134,11 @@ audio {
124134
color: rgb(var(--main-cover-color));
125135
}
126136
}
127-
.n-slider{
128-
--n-handle-color: rgb(var(--main-cover-color)); --n-rail-color: rgba(var(--main-cover-color), 0.2);
129-
--n-rail-color-hover: rgba(var(--main-cover-color), 0.3); --n-fill-color: rgb(var(--main-cover-color));
137+
.n-slider {
138+
--n-handle-color: rgb(var(--main-cover-color));
139+
--n-rail-color: rgba(var(--main-cover-color), 0.2);
140+
--n-rail-color-hover: rgba(var(--main-cover-color), 0.3);
141+
--n-fill-color: rgb(var(--main-cover-color));
130142
--n-fill-color-hover: rgb(var(--main-cover-color));
131143
}
132144
}

src/utils/color.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export const getCoverColorData = (dom: HTMLImageElement) => {
158158
export const getCoverColor = async (coverUrl: string) => {
159159
if (!coverUrl) return;
160160
const statusStore = useStatusStore();
161+
const settingStore = useSettingStore();
161162
// 创建图像元素
162163
const image = new Image();
163164
image.crossOrigin = "Anonymous";
@@ -167,6 +168,9 @@ export const getCoverColor = async (coverUrl: string) => {
167168
// 获取图片数据
168169
const coverColorData = getCoverColorData(image);
169170
if (coverColorData) statusStore.songCoverTheme = coverColorData;
171+
if (!settingStore.playerFollowCoverColor) {
172+
statusStore.songCoverTheme.main = { r: 239, g: 239, b: 239 };
173+
}
170174
// 移除元素
171175
image.remove();
172176
};

0 commit comments

Comments
 (0)