feat: 新增 定时关闭 & 播放速度

This commit is contained in:
imsyy
2025-10-21 00:12:48 +08:00
parent 56045cd338
commit c21f970b86
6 changed files with 115 additions and 80 deletions

3
components.d.ts vendored
View File

@@ -11,7 +11,7 @@ declare module 'vue' {
AboutSetting: typeof import('./src/components/Setting/AboutSetting.vue')['default'] AboutSetting: typeof import('./src/components/Setting/AboutSetting.vue')['default']
ArtistList: typeof import('./src/components/List/ArtistList.vue')['default'] ArtistList: typeof import('./src/components/List/ArtistList.vue')['default']
AutoClose: typeof import('./src/components/Modal/AutoClose.vue')['default'] AutoClose: typeof import('./src/components/Modal/AutoClose.vue')['default']
BatchList: typeof import('./src/components/Modal/BatchList.vue')['default'] BatchList: typeof import('./src/components/Modal/batchList.vue')['default']
ChangeRate: typeof import('./src/components/Modal/ChangeRate.vue')['default'] ChangeRate: typeof import('./src/components/Modal/ChangeRate.vue')['default']
CloudMatch: typeof import('./src/components/Modal/CloudMatch.vue')['default'] CloudMatch: typeof import('./src/components/Modal/CloudMatch.vue')['default']
CommentList: typeof import('./src/components/List/CommentList.vue')['default'] CommentList: typeof import('./src/components/List/CommentList.vue')['default']
@@ -96,7 +96,6 @@ declare module 'vue' {
NP: typeof import('naive-ui')['NP'] NP: typeof import('naive-ui')['NP']
NPopconfirm: typeof import('naive-ui')['NPopconfirm'] NPopconfirm: typeof import('naive-ui')['NPopconfirm']
NPopover: typeof import('naive-ui')['NPopover'] NPopover: typeof import('naive-ui')['NPopover']
NProgress: typeof import('naive-ui')['NProgress']
NQrCode: typeof import('naive-ui')['NQrCode'] NQrCode: typeof import('naive-ui')['NQrCode']
NRadio: typeof import('naive-ui')['NRadio'] NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup'] NRadioGroup: typeof import('naive-ui')['NRadioGroup']

View File

@@ -11,7 +11,11 @@
</n-text> </n-text>
</Transition> </Transition>
</n-flex> </n-flex>
<n-switch v-model:value="statusStore.autoClose.enable" :round="false" /> <n-switch
v-model:value="statusStore.autoClose.enable"
:round="false"
@update:value="handleUpdate"
/>
</n-flex> </n-flex>
</n-card> </n-card>
<!-- 时间选择 --> <!-- 时间选择 -->
@@ -23,6 +27,7 @@
type="primary" type="primary"
size="large" size="large"
round round
@click="player.startAutoCloseTimer(item, item * 60)"
> >
{{ item }}min {{ item }}min
</n-tag> </n-tag>
@@ -35,13 +40,19 @@
type: 'primary', type: 'primary',
}" }"
:show-icon="false" :show-icon="false"
@positive-click="player.startAutoCloseTimer(customTime, customTime * 60)"
> >
<template #trigger> <template #trigger>
<n-tag :bordered="false" type="primary" size="large" round> 自定义时长 </n-tag> <n-tag :bordered="false" type="primary" size="large" round> 自定义时长 </n-tag>
</template> </template>
<n-flex vertical> <n-flex vertical>
<n-text>自定义时长分钟</n-text> <n-text>自定义时长分钟</n-text>
<n-input-number v-model:value="statusStore.autoClose.remainTime" /> <n-input-number
v-model:value="customTime"
:min="1"
:max="120"
placeholder="请输入自定义时长"
/>
</n-flex> </n-flex>
</n-popconfirm> </n-popconfirm>
</n-flex> </n-flex>
@@ -55,8 +66,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { useStatusStore } from "@/stores"; import { useStatusStore } from "@/stores";
import { convertSecondsToTime } from "@/utils/time"; import { convertSecondsToTime } from "@/utils/time";
import player from "@/utils/player";
const statusStore = useStatusStore(); const statusStore = useStatusStore();
// 自定义时长
const customTime = ref(1);
// 是否开启
const handleUpdate = (value: boolean) => {
if (value) {
player.startAutoCloseTimer(statusStore.autoClose.time, statusStore.autoClose.remainTime);
} else {
statusStore.autoClose.enable = false;
statusStore.autoClose.remainTime = statusStore.autoClose.time * 60;
}
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -161,11 +161,34 @@
class="play-menu" class="play-menu"
justify="end" justify="end"
> >
<!-- 播放时间 --> <!-- 时间相关 -->
<div class="time"> <Transition name="fade" mode="out-in">
<n-text depth="2">{{ secondsToTime(statusStore.currentTime) }}</n-text> <n-flex
<n-text depth="2">{{ secondsToTime(statusStore.duration) }}</n-text> :key="statusStore.autoClose.enable ? 'autoClose' : 'time'"
</div> :size="4"
justify="center"
class="time-container"
vertical
>
<div class="time">
<n-text depth="2">{{ secondsToTime(statusStore.currentTime) }}</n-text>
<n-text depth="2">{{ secondsToTime(statusStore.duration) }}</n-text>
</div>
<!-- 定时关闭 -->
<n-tag
v-if="statusStore.autoClose.enable"
size="small"
type="primary"
round
@click="openAutoClose"
>
{{ convertSecondsToTime(statusStore.autoClose.remainTime) }}
<template #icon>
<SvgIcon name="TimeAuto" />
</template>
</n-tag>
</n-flex>
</Transition>
<!-- 功能区 --> <!-- 功能区 -->
<PlayerRightMenu /> <PlayerRightMenu />
</n-flex> </n-flex>
@@ -176,10 +199,16 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownOption } from "naive-ui"; import type { DropdownOption } from "naive-ui";
import { useMusicStore, useStatusStore, useDataStore, useSettingStore } from "@/stores"; import { useMusicStore, useStatusStore, useDataStore, useSettingStore } from "@/stores";
import { secondsToTime, calculateCurrentTime } from "@/utils/time"; import { secondsToTime, calculateCurrentTime, convertSecondsToTime } from "@/utils/time";
import { renderIcon, coverLoaded } from "@/utils/helper"; import { renderIcon, coverLoaded } from "@/utils/helper";
import { toLikeSong } from "@/utils/auth"; import { toLikeSong } from "@/utils/auth";
import { openChangeRate, openDownloadSong, openJumpArtist, openPlaylistAdd } from "@/utils/modal"; import {
openAutoClose,
openChangeRate,
openDownloadSong,
openJumpArtist,
openPlaylistAdd,
} from "@/utils/modal";
import player from "@/utils/player"; import player from "@/utils/player";
const router = useRouter(); const router = useRouter();
@@ -476,11 +505,17 @@ const instantLyrics = computed(() => {
} }
} }
.play-menu { .play-menu {
.time-container {
margin-right: 8px;
.n-tag {
justify-content: center;
font-size: 12px;
}
}
.time { .time {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 12px; font-size: 12px;
margin-right: 8px;
.n-text { .n-text {
color: var(--primary-hex); color: var(--primary-hex);
opacity: 0.8; opacity: 0.8;

View File

@@ -140,7 +140,7 @@ const sliderDragend = () => {
height: 100%; height: 100%;
padding: 0 30px; padding: 0 30px;
transition: opacity 0.3s; transition: opacity 0.3s;
.menu-icon { :deep(.menu-icon) {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -162,6 +162,12 @@ const sliderDragend = () => {
transform: scale(1); transform: scale(1);
} }
} }
:deep(.n-badge-sup) {
background-color: rgba(var(--main-color), 0.14);
.n-base-slot-machine {
color: rgb(var(--main-color));
}
}
} }
.center { .center {
height: 100%; height: 100%;

View File

@@ -32,6 +32,10 @@ const init = async () => {
); );
// 同步播放模式 // 同步播放模式
player.playModeSyncIpc(); player.playModeSyncIpc();
// 初始化自动关闭定时器
if (statusStore.autoClose.enable) {
player.startAutoCloseTimer(statusStore.autoClose.time, statusStore.autoClose.remainTime);
}
if (isElectron) { if (isElectron) {
// 注册全局快捷键 // 注册全局快捷键

View File

@@ -35,7 +35,7 @@ class Player {
// 定时器 // 定时器
private playerInterval: ReturnType<typeof setInterval> | undefined; private playerInterval: ReturnType<typeof setInterval> | undefined;
// 自动关闭定时器 // 自动关闭定时器
private autoCloseTimer: ReturnType<typeof setTimeout> | undefined; private autoCloseInterval: ReturnType<typeof setInterval> | undefined;
// 频谱数据 // 频谱数据
private audioContext: AudioContext | null = null; private audioContext: AudioContext | null = null;
private analyser: AnalyserNode | null = null; private analyser: AnalyserNode | null = null;
@@ -54,8 +54,6 @@ class Player {
this.player = new Howl({ src: [""], format: allowPlayFormat, autoplay: false }); this.player = new Howl({ src: [""], format: allowPlayFormat, autoplay: false });
// 初始化媒体会话 // 初始化媒体会话
this.initMediaSession(); this.initMediaSession();
// 初始化自动关闭定时器
this.toggleAutoCloseTimer();
} }
/** /**
* 获取当前播放歌曲 * 获取当前播放歌曲
@@ -429,15 +427,19 @@ class Player {
if (currentSessionId !== this.playSessionId) return; if (currentSessionId !== this.playSessionId) return;
// statusStore.playStatus = false; // statusStore.playStatus = false;
console.log("⏹️ song end:", playSongData); console.log("⏹️ song end:", playSongData);
// 检查是否需要在歌曲结束时执行自动关闭 // 检查是否需要在歌曲结束时执行自动关闭
const statusStore = useStatusStore(); const statusStore = useStatusStore();
if (statusStore.autoClose.enable && statusStore.autoClose.waitSongEnd && statusStore.autoClose.remainTime <= 0) { if (
statusStore.autoClose.enable &&
statusStore.autoClose.waitSongEnd &&
statusStore.autoClose.remainTime <= 0
) {
// 执行自动关闭 // 执行自动关闭
this.executeAutoClose(); this.executeAutoClose();
return; return;
} }
this.nextOrPrev("next"); this.nextOrPrev("next");
}); });
// 错误 // 错误
@@ -899,12 +901,6 @@ class Player {
statusStore.lyricIndex = -1; statusStore.lyricIndex = -1;
statusStore.currentTime = 0; statusStore.currentTime = 0;
statusStore.progress = 0; statusStore.progress = 0;
// 重置自动关闭计时器(切换歌曲时重新开始计时)
if (statusStore.autoClose.enable) {
this.startAutoCloseTimer(statusStore.autoClose.time, statusStore.autoClose.waitSongEnd);
}
// 暂停 // 暂停
await this.pause(false); await this.pause(false);
// 初始化播放器不传入seek参数确保从头开始播放 // 初始化播放器不传入seek参数确保从头开始播放
@@ -1197,12 +1193,7 @@ class Player {
statusStore.lyricIndex = -1; statusStore.lyricIndex = -1;
statusStore.currentTime = 0; statusStore.currentTime = 0;
statusStore.progress = 0; statusStore.progress = 0;
// 重置自动关闭计时器(切换歌曲时重新开始计时)
if (statusStore.autoClose.enable) {
this.startAutoCloseTimer(statusStore.autoClose.time, statusStore.autoClose.waitSongEnd);
}
// 清理并播放不传入seek参数确保从头开始播放 // 清理并播放不传入seek参数确保从头开始播放
await this.initPlayer(true, 0); await this.initPlayer(true, 0);
} }
@@ -1446,51 +1437,36 @@ class Player {
} }
/** /**
* 开始定时关闭 * 开始定时关闭
* @param time 关闭时间( * @param time 关闭时间(分钟
* @param waitSongEnd 是否等待歌曲结束 * @param remainTime 剩余时间(秒)
*/ */
startAutoCloseTimer(time: number, waitSongEnd: boolean = true) { startAutoCloseTimer(time: number, remainTime: number) {
const statusStore = useStatusStore(); const statusStore = useStatusStore();
// 清除之前的定时器 if (!time || !remainTime) return;
clearTimeout(this.autoCloseTimer); // 如已有定时器在运行,先停止以防叠加
if (this.autoCloseInterval) {
clearInterval(this.autoCloseInterval);
this.autoCloseInterval = undefined;
}
// 重置剩余时间 // 重置剩余时间
statusStore.autoClose = { Object.assign(statusStore.autoClose, {
enable: true, enable: true,
time, time,
remainTime: time, remainTime,
waitSongEnd, });
};
// 开始减少剩余时间 // 开始减少剩余时间
const { pause } = useIntervalFn(() => { this.autoCloseInterval = setInterval(() => {
if (statusStore.autoClose.remainTime <= 0) pause(); if (statusStore.autoClose.remainTime <= 0) {
clearInterval(this.autoCloseInterval);
this.autoCloseInterval = undefined;
if (!statusStore.autoClose.waitSongEnd) {
this.executeAutoClose();
}
return;
}
statusStore.autoClose.remainTime--; statusStore.autoClose.remainTime--;
}, 1000); }, 1000);
// 设置新的定时器
this.autoCloseTimer = setTimeout(() => {
pause();
statusStore.autoClose.remainTime = 0;
// 根据设置决定如何关闭
if (statusStore.autoClose.waitSongEnd) {
// 等待歌曲结束,不在这里执行关闭,而是在歌曲结束事件中处理
console.log("⏰ 自动关闭计时结束,等待当前歌曲播放完毕");
} else {
// 立即执行关闭
this.executeAutoClose();
}
}, time * 1000);
} }
/**
* 开启或者停止自动关闭定时器
*/
toggleAutoCloseTimer() {
const statusStore = useStatusStore();
if (statusStore.autoClose.enable) {
this.startAutoCloseTimer(statusStore.autoClose.time, statusStore.autoClose.waitSongEnd);
} else {
clearTimeout(this.autoCloseTimer);
}
}
/** /**
* 执行自动关闭操作 * 执行自动关闭操作
*/ */
@@ -1498,20 +1474,10 @@ class Player {
console.log("🔄 执行自动关闭"); console.log("🔄 执行自动关闭");
// 暂停播放 // 暂停播放
this.pause(); this.pause();
// 清除定时器
clearTimeout(this.autoCloseTimer);
// 重置状态 // 重置状态
const statusStore = useStatusStore(); const { autoClose } = useStatusStore();
statusStore.autoClose.enable = false; autoClose.enable = false;
statusStore.autoClose.remainTime = 0; autoClose.remainTime = autoClose.time * 60;
// 如果是 Electron 环境,关闭应用
if (isElectron) {
window.electron.ipcRenderer.send("app-close");
} else {
// 浏览器环境,显示提示信息
window.$message.info("自动关闭已触发,播放已暂停");
}
} }
} }