mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-26 03:44:57 +08:00
✨ feat: 新增 定时关闭 & 播放速度
This commit is contained in:
3
components.d.ts
vendored
3
components.d.ts
vendored
@@ -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']
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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%;
|
||||||
|
|||||||
@@ -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) {
|
||||||
// 注册全局快捷键
|
// 注册全局快捷键
|
||||||
|
|||||||
@@ -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("自动关闭已触发,播放已暂停");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user