mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-25 03:14:57 +08:00
🌈 style: 优化评论区效果
This commit is contained in:
187
electron/server/control/index.ts
Normal file
187
electron/server/control/index.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
import { FastifyInstance } from "fastify";
|
||||
import mainWindow from "../../main/windows/main-window";
|
||||
|
||||
/**
|
||||
* 播放控制接口
|
||||
* @param fastify Fastify 实例
|
||||
*/
|
||||
export const initControlAPI = async (fastify: FastifyInstance) => {
|
||||
// 播放控制路由前缀
|
||||
await fastify.register(
|
||||
async (fastify) => {
|
||||
// 播放
|
||||
fastify.get("/play", async (_request, reply) => {
|
||||
try {
|
||||
const mainWin = mainWindow.getWin();
|
||||
if (!mainWin) {
|
||||
return reply.code(500).send({
|
||||
code: 500,
|
||||
message: "主窗口未找到",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
mainWin.webContents.send("play");
|
||||
|
||||
return reply.send({
|
||||
code: 200,
|
||||
message: "播放命令已发送",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return reply.code(500).send({
|
||||
code: 500,
|
||||
message: "播放失败",
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 暂停
|
||||
fastify.get("/pause", async (_request, reply) => {
|
||||
try {
|
||||
const mainWin = mainWindow.getWin();
|
||||
if (!mainWin) {
|
||||
return reply.code(500).send({
|
||||
code: 500,
|
||||
message: "主窗口未找到",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
mainWin.webContents.send("pause");
|
||||
|
||||
return reply.send({
|
||||
code: 200,
|
||||
message: "暂停命令已发送",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return reply.code(500).send({
|
||||
code: 500,
|
||||
message: "暂停失败",
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 播放/暂停切换
|
||||
fastify.get("/toggle", async (_request, reply) => {
|
||||
try {
|
||||
const mainWin = mainWindow.getWin();
|
||||
if (!mainWin) {
|
||||
return reply.code(500).send({
|
||||
code: 500,
|
||||
message: "主窗口未找到",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
// 这里可以根据当前播放状态来决定发送 play 还是 pause
|
||||
// 暂时先发送 toggle 事件,如果渲染进程支持的话
|
||||
mainWin.webContents.send("toggle");
|
||||
|
||||
return reply.send({
|
||||
code: 200,
|
||||
message: "播放/暂停切换命令已发送",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return reply.code(500).send({
|
||||
code: 500,
|
||||
message: "播放/暂停切换失败",
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 下一曲
|
||||
fastify.get("/next", async (_request, reply) => {
|
||||
try {
|
||||
const mainWin = mainWindow.getWin();
|
||||
if (!mainWin) {
|
||||
return reply.code(500).send({
|
||||
code: 500,
|
||||
message: "主窗口未找到",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
mainWin.webContents.send("playNext");
|
||||
|
||||
return reply.send({
|
||||
code: 200,
|
||||
message: "下一曲命令已发送",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return reply.code(500).send({
|
||||
code: 500,
|
||||
message: "下一曲失败",
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 上一曲
|
||||
fastify.get("/prev", async (_request, reply) => {
|
||||
try {
|
||||
const mainWin = mainWindow.getWin();
|
||||
if (!mainWin) {
|
||||
return reply.code(500).send({
|
||||
code: 500,
|
||||
message: "主窗口未找到",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
mainWin.webContents.send("playPrev");
|
||||
|
||||
return reply.send({
|
||||
code: 200,
|
||||
message: "上一曲命令已发送",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return reply.code(500).send({
|
||||
code: 500,
|
||||
message: "上一曲失败",
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取播放状态(可选功能)
|
||||
fastify.get("/status", async (_request, reply) => {
|
||||
try {
|
||||
const mainWin = mainWindow.getWin();
|
||||
if (!mainWin) {
|
||||
return reply.code(500).send({
|
||||
code: 500,
|
||||
message: "主窗口未找到",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
// 这里可以通过 IPC 获取当前播放状态
|
||||
// 暂时返回基本信息
|
||||
return reply.send({
|
||||
code: 200,
|
||||
message: "获取状态成功",
|
||||
data: {
|
||||
connected: true,
|
||||
window: "available",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return reply.code(500).send({
|
||||
code: 500,
|
||||
message: "获取状态失败",
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
{ prefix: "/control" },
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
import { join } from "path";
|
||||
import { isDev } from "../main/utils/config";
|
||||
import { serverLog } from "../main/logger";
|
||||
import initNcmAPI from "./netease";
|
||||
import initUnblockAPI from "./unblock";
|
||||
import { initNcmAPI } from "./netease";
|
||||
import { initUnblockAPI } from "./unblock";
|
||||
import { initControlAPI } from "./control";
|
||||
import fastifyCookie from "@fastify/cookie";
|
||||
import fastifyMultipart from "@fastify/multipart";
|
||||
import fastifyStatic from "@fastify/static";
|
||||
@@ -47,6 +48,7 @@ const initAppServer = async () => {
|
||||
// 注册接口
|
||||
server.register(initNcmAPI, { prefix: "/api" });
|
||||
server.register(initUnblockAPI, { prefix: "/api" });
|
||||
server.register(initControlAPI, { prefix: "/api" });
|
||||
// 启动端口
|
||||
const port = Number(process.env["VITE_SERVER_PORT"] || 25884);
|
||||
await server.listen({ port });
|
||||
|
||||
@@ -29,7 +29,7 @@ const getHandler = (name: string, neteaseApi: (params: any) => any) => {
|
||||
};
|
||||
|
||||
// 初始化 NcmAPI
|
||||
const initNcmAPI = async (fastify: FastifyInstance) => {
|
||||
export const initNcmAPI = async (fastify: FastifyInstance) => {
|
||||
// 主信息
|
||||
fastify.get("/netease", (_, reply) => {
|
||||
reply.send({
|
||||
@@ -62,5 +62,3 @@ const initNcmAPI = async (fastify: FastifyInstance) => {
|
||||
|
||||
serverLog.info("🌐 Register NcmAPI successfully");
|
||||
};
|
||||
|
||||
export default initNcmAPI;
|
||||
|
||||
@@ -26,7 +26,7 @@ const getNeteaseSongUrl = async (id: number | string): Promise<SongUrlResult> =>
|
||||
};
|
||||
|
||||
// 初始化 UnblockAPI
|
||||
const UnblockAPI = async (fastify: FastifyInstance) => {
|
||||
export const initUnblockAPI = async (fastify: FastifyInstance) => {
|
||||
// 主信息
|
||||
fastify.get("/unblock", (_, reply) => {
|
||||
reply.send({
|
||||
@@ -64,5 +64,3 @@ const UnblockAPI = async (fastify: FastifyInstance) => {
|
||||
|
||||
serverLog.info("🌐 Register UnblockAPI successfully");
|
||||
};
|
||||
|
||||
export default UnblockAPI;
|
||||
|
||||
@@ -246,9 +246,8 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
&.show-comment {
|
||||
.content-left {
|
||||
position: static;
|
||||
min-width: 40vw;
|
||||
max-width: 50vh;
|
||||
min-width: 40%;
|
||||
width: 40%;
|
||||
padding: 0 60px;
|
||||
.player-cover,
|
||||
.player-data {
|
||||
|
||||
@@ -118,9 +118,11 @@ onMounted(() => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.player-comment {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 60%;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
filter: drop-shadow(0px 4px 6px rgba(0, 0, 0, 0.2));
|
||||
mask: linear-gradient(
|
||||
|
||||
@@ -221,7 +221,7 @@ const jumpPage = debounce(
|
||||
}
|
||||
}
|
||||
.player-tip {
|
||||
width: 240px;
|
||||
max-width: 240px;
|
||||
padding: 12px 20px;
|
||||
border-radius: 12px;
|
||||
color: rgb(var(--theme));
|
||||
|
||||
@@ -59,14 +59,20 @@ class Player {
|
||||
/**
|
||||
* 处理播放状态
|
||||
*/
|
||||
private handlePlayStatus() {
|
||||
private handlePlayStatus(sessionId?: number) {
|
||||
// const musicStore = useMusicStore();
|
||||
const statusStore = useStatusStore();
|
||||
const settingStore = useSettingStore();
|
||||
const currentSessionId = sessionId ?? this.playSessionId;
|
||||
// 清理定时器
|
||||
clearInterval(this.playerInterval);
|
||||
// 更新播放状态
|
||||
this.playerInterval = setInterval(() => {
|
||||
// 检查会话是否过期
|
||||
if (currentSessionId !== this.playSessionId) {
|
||||
clearInterval(this.playerInterval);
|
||||
return;
|
||||
}
|
||||
if (!this.player.playing()) return;
|
||||
const currentTime = this.getSeek();
|
||||
const duration = this.player.duration();
|
||||
@@ -178,6 +184,12 @@ class Player {
|
||||
const settingStore = useSettingStore();
|
||||
// 播放信息
|
||||
const { id, path, type } = musicStore.playSong;
|
||||
const currentSessionId = sessionId ?? this.playSessionId;
|
||||
// 检查会话是否过期
|
||||
if (currentSessionId !== this.playSessionId) {
|
||||
console.log("🚫 Session expired, skipping player creation");
|
||||
return;
|
||||
}
|
||||
// 清理播放器(移除事件,停止并卸载)
|
||||
try {
|
||||
this.player.off();
|
||||
@@ -186,6 +198,13 @@ class Player {
|
||||
}
|
||||
Howler.stop();
|
||||
Howler.unload();
|
||||
// 清理所有定时器
|
||||
this.cleanupAllTimers();
|
||||
// 再次检查会话是否过期(异步操作后)
|
||||
if (currentSessionId !== this.playSessionId) {
|
||||
console.log("🚫 Session expired after cleanup, aborting");
|
||||
return;
|
||||
}
|
||||
// 创建播放器(禁用内置 autoplay,统一走手动 play)
|
||||
this.player = new Howl({
|
||||
src,
|
||||
@@ -198,7 +217,7 @@ class Player {
|
||||
rate: statusStore.playRate,
|
||||
});
|
||||
// 播放器事件(绑定当前会话)
|
||||
this.playerEvent({ seek, sessionId });
|
||||
this.playerEvent({ seek, sessionId: currentSessionId });
|
||||
// 播放设备
|
||||
if (!settingStore.showSpectrums) this.toggleOutputDevice();
|
||||
// 自动播放(仅一次性触发)
|
||||
@@ -208,7 +227,7 @@ class Player {
|
||||
getLyricData(id);
|
||||
} else resetSongLyric();
|
||||
// 定时获取状态
|
||||
if (!this.playerInterval) this.handlePlayStatus();
|
||||
if (!this.playerInterval) this.handlePlayStatus(currentSessionId);
|
||||
// 新增播放历史
|
||||
if (type !== "radio") dataStore.setHistory(musicStore.playSong);
|
||||
// 获取歌曲封面主色
|
||||
@@ -616,6 +635,11 @@ class Player {
|
||||
async play() {
|
||||
const statusStore = useStatusStore();
|
||||
const settingStore = useSettingStore();
|
||||
// 检查播放器状态
|
||||
if (!this.player || this.player.state() === "unloaded") {
|
||||
console.warn("⚠️ Player not ready for play");
|
||||
return;
|
||||
}
|
||||
// 已在播放
|
||||
if (this.player.playing()) {
|
||||
statusStore.playStatus = true;
|
||||
@@ -640,14 +664,13 @@ class Player {
|
||||
const statusStore = useStatusStore();
|
||||
const settingStore = useSettingStore();
|
||||
|
||||
// 播放器未加载完成
|
||||
if (this.player.state() !== "loaded") {
|
||||
// 播放器未加载完成或不存在
|
||||
if (!this.player || this.player.state() !== "loaded") {
|
||||
if (changeStatus) statusStore.playStatus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 立即设置播放状态
|
||||
if (changeStatus) statusStore.playStatus = false;
|
||||
|
||||
// 淡出
|
||||
await new Promise<void>((resolve) => {
|
||||
this.player.fade(statusStore.playVolume, 0, settingStore.getFadeTime);
|
||||
@@ -672,12 +695,20 @@ class Player {
|
||||
* @param autoEnd 是否为歌曲自动播放结束
|
||||
*/
|
||||
async nextOrPrev(type: "next" | "prev" = "next", play: boolean = true, autoEnd: boolean = false) {
|
||||
const statusStore = useStatusStore();
|
||||
const dataStore = useDataStore();
|
||||
const musicStore = useMusicStore();
|
||||
try {
|
||||
if (this.switching) return;
|
||||
if (this.switching) {
|
||||
console.log("🔄 Already switching, ignoring request");
|
||||
return;
|
||||
}
|
||||
this.switching = true;
|
||||
const statusStore = useStatusStore();
|
||||
const dataStore = useDataStore();
|
||||
const musicStore = useMusicStore();
|
||||
|
||||
// 立即更新UI状态,防止用户重复点击
|
||||
statusStore.playLoading = true;
|
||||
statusStore.playStatus = false;
|
||||
|
||||
// 获取数据
|
||||
const { playList } = dataStore;
|
||||
const { playSong } = musicStore;
|
||||
@@ -724,12 +755,15 @@ class Player {
|
||||
// 重置播放进度(切换歌曲时必须重置)
|
||||
statusStore.currentTime = 0;
|
||||
statusStore.progress = 0;
|
||||
// 暂停
|
||||
// 暂停当前播放
|
||||
await this.pause(false);
|
||||
// 清理定时器,防止旧定时器继续运行
|
||||
this.cleanupAllTimers();
|
||||
// 初始化播放器(不传入seek参数,确保从头开始播放)
|
||||
await this.initPlayer(play, 0);
|
||||
} catch (error) {
|
||||
console.error("Error in nextOrPrev:", error);
|
||||
statusStore.playLoading = false;
|
||||
throw error;
|
||||
} finally {
|
||||
this.switching = false;
|
||||
@@ -815,6 +849,11 @@ class Player {
|
||||
*/
|
||||
setSeek(time: number) {
|
||||
const statusStore = useStatusStore();
|
||||
// 检查播放器状态
|
||||
if (!this.player || this.player.state() !== "loaded") {
|
||||
console.warn("⚠️ Player not ready for seek");
|
||||
return;
|
||||
}
|
||||
this.player.seek(time);
|
||||
statusStore.currentTime = time;
|
||||
}
|
||||
@@ -823,6 +862,8 @@ class Player {
|
||||
* @returns 播放进度
|
||||
*/
|
||||
getSeek(): number {
|
||||
// 检查播放器状态
|
||||
if (!this.player || this.player.state() !== "loaded") return 0;
|
||||
return this.player.seek();
|
||||
}
|
||||
/**
|
||||
@@ -981,23 +1022,43 @@ class Player {
|
||||
async togglePlayIndex(index: number, play: boolean = false) {
|
||||
const dataStore = useDataStore();
|
||||
const statusStore = useStatusStore();
|
||||
// 获取数据
|
||||
const { playList } = dataStore;
|
||||
// 若超出播放列表
|
||||
if (index >= playList.length) return;
|
||||
// 相同
|
||||
if (!play && statusStore.playIndex === index) {
|
||||
this.play();
|
||||
return;
|
||||
try {
|
||||
if (this.switching) {
|
||||
console.log("🔄 Already switching, ignoring request");
|
||||
return;
|
||||
}
|
||||
this.switching = true;
|
||||
// 立即更新UI状态,防止用户重复点击
|
||||
statusStore.playLoading = true;
|
||||
statusStore.playStatus = false;
|
||||
// 获取数据
|
||||
const { playList } = dataStore;
|
||||
// 若超出播放列表
|
||||
if (index >= playList.length) return;
|
||||
// 相同
|
||||
if (!play && statusStore.playIndex === index) {
|
||||
this.play();
|
||||
return;
|
||||
}
|
||||
// 更改状态
|
||||
statusStore.playIndex = index;
|
||||
// 重置播放进度(切换歌曲时必须重置)
|
||||
statusStore.currentTime = 0;
|
||||
statusStore.progress = 0;
|
||||
statusStore.lyricIndex = -1;
|
||||
// 暂停当前播放
|
||||
await this.pause(false);
|
||||
// 清理定时器,防止旧定时器继续运行
|
||||
this.cleanupAllTimers();
|
||||
// 清理并播放(不传入seek参数,确保从头开始播放)
|
||||
await this.initPlayer(true, 0);
|
||||
} catch (error) {
|
||||
console.error("Error in togglePlayIndex:", error);
|
||||
statusStore.playLoading = false;
|
||||
throw error;
|
||||
} finally {
|
||||
this.switching = false;
|
||||
}
|
||||
// 更改状态
|
||||
statusStore.playIndex = index;
|
||||
// 重置播放进度(切换歌曲时必须重置)
|
||||
statusStore.currentTime = 0;
|
||||
statusStore.progress = 0;
|
||||
|
||||
// 清理并播放(不传入seek参数,确保从头开始播放)
|
||||
await this.initPlayer(true, 0);
|
||||
}
|
||||
/**
|
||||
* 移除指定歌曲
|
||||
@@ -1322,7 +1383,24 @@ class Player {
|
||||
}, 1000);
|
||||
}
|
||||
/**
|
||||
* 执行自动关闭操作
|
||||
* 清理所有定时器和资源
|
||||
*/
|
||||
private cleanupAllTimers() {
|
||||
// 清理播放状态定时器
|
||||
if (this.playerInterval) {
|
||||
clearInterval(this.playerInterval);
|
||||
this.playerInterval = undefined;
|
||||
}
|
||||
// 清理自动关闭定时器
|
||||
if (this.autoCloseInterval) {
|
||||
clearInterval(this.autoCloseInterval);
|
||||
this.autoCloseInterval = undefined;
|
||||
}
|
||||
console.log("🧹 All timers cleaned up");
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行自动关闭
|
||||
*/
|
||||
private executeAutoClose() {
|
||||
console.log("🔄 执行自动关闭");
|
||||
|
||||
Reference in New Issue
Block a user