SPlayer WebSocket API 完整指南:实时控制播放器与接收播放状态
【免费下载链接】SPlayer🎵 A cross-platform music player with Jellyfin / Navidrome / Emby media server support, word-by-word lyrics, desktop & taskbar lyrics, cloud music drive, local library management, audio spectrum visualization and mobile-friendly UI. 简约的跨平台音乐播放器,支持逐字歌词、桌面歌词、任务栏歌词、云盘音乐、本地音乐管理及流媒体播放项目地址: https://gitcode.com/GitHub_Trending/spl/SPlayer
导读
本文档系统讲解 SPlayer(基于 Vue 3 + TypeScript + Electron 的跨平台音乐播放器)内置的 WebSocket 本地服务:通过ws://localhost:25885建立双向实时通信,客户端既可以下发play/pause/next/prev等控制命令远程操纵播放器,也能实时订阅歌曲切换、播放进度、歌词数据等状态广播。阅读本文后,你将能够编写自己的 WebSocket 客户端(如桌面小组件、智能家居面板、脚本或移动端遥控器),在数分钟内接入 SPlayer 的播放控制与状态监控能力。
WebSocket API 概览
SPlayer 在 Electron 主进程中内置了一个基于ws库的 WebSocket 服务器(核心实现见 SocketService.ts),专门用于对外提供实时双向通信能力:
- 基础路径:
ws://localhost:25885(默认端口,可在设置中修改,范围为 1~65535) - 服务模式:仅在 Electron 桌面端生效,监听
127.0.0.1回环地址,属于本地服务 - 默认状态:默认关闭,需在「设置 → 网络 → WebSocket 配置」中手动开启;开启后每次启动应用会自动拉起服务
- 消息载体:所有业务消息均为 JSON 文本帧;心跳检测使用裸文本
PING/PONG
从 electron/main/store/index.ts 与defaults配置可见,WebSocket 相关状态在本地持久化存储中的结构为:
websocket: { enabled: false, // 是否启用 port: 25885, // 监听端口 }在设置界面启用并配置服务
WebSocket 服务默认不启动。在 SPlayer 的「设置 → 网络」分组中找到WebSocket 配置区块(实现见 network.ts),包含三个配置项:
| 配置项 | 类型 | 说明 |
|---|---|---|
| 启用 WebSocket | 开关 | 开启后可通过 WebSocket 获取状态或控制播放器 |
| WebSocket 端口 | 数字输入(1~65535) | 更改后需要测试并保存才能生效;服务运行时该输入框被禁用 |
| 测试端口配置 | 按钮("测试并保存") | 仅在端口值与已保存值不一致时显示 |
启用流程遵循"先测试、再启用"的安全逻辑(对应 network.ts):
- 输入新的端口号;
- 点击测试并保存:应用会通过 IPC 通道
socket-test-port调用主进程的testPort()检测端口是否可绑定(实现见 SocketService.ts),检测到EADDRINUSE(端口被占用)或EACCES(权限不足)即判定不可用,测试成功才把配置写入本地存储; - 打开启用 WebSocket开关:应用通过 IPC 通道
socket-start启动服务(见 ipc-socket.ts),启动成功后持久化{ enabled: true, port },此后每次应用启动都会调用SocketService.tryAutoStart()自动恢复服务(见 electron/main/index.ts)。
如果启动时端口已被占用,服务会自动把websocket.enabled重置为false,避免下次启动再次失败(见 SocketService.ts)。
建立连接
在服务已开启的前提下,任意 WebSocket 客户端都可以直连:
const ws = new WebSocket("ws://localhost:25885");连接成功后,服务器会自动发送一条welcome欢迎消息(见handleClientConnection中的sendWelcome,SocketService.ts),客户端可据此确认握手完成。
提示:服务绑定的是
127.0.0.1,因此仅本机可访问;如需局域网内遥控播放器,需要在系统层面做端口转发或反向代理,SPlayer 默认不开放外部访问。
消息协议总览
所有业务消息统一采用如下 JSON 信封格式:
{ "type": "消息类型", "data": {} }其中type为必填字段,data为可选载荷。按方向划分,协议包含三类消息:
- 客户端 → 服务器(请求):
control(播放控制)、get-song-info(获取当前歌曲信息); - 服务器 → 客户端(响应):
control-response、song-info、error; - 服务器 → 客户端(广播):
welcome、status-change、song-change、progress-change、lyric-change; - 心跳:客户端发送文本
PING,服务器回复文本PONG。
服务端对消息的解析与分发逻辑位于 SocketService.ts:先做PING判定,再尝试JSON.parse,接着校验"根对象必须是非数组对象"、"必须包含type字段",最后按type分发到对应的处理函数,未知类型会返回error。
控制播放器
消息类型:control
请求格式:
{ "type": "control", "data": { "command": "toggle|play|pause|next|prev" } }命令说明:
| 命令 | 作用 | 底层 IPC 事件 |
|---|---|---|
toggle | 播放/暂停切换 | playOrPause |
play | 播放 | play |
pause | 暂停 | pause |
next | 下一曲 | playNext |
prev | 上一曲 | playPrev |
命令到 IPC 事件的映射关系见 SocketService.ts:handleControlCommand收到命令后,通过mainWin.webContents.send(ipcEvent)把指令转发给渲染进程,由前端播放器状态机真正执行操作。执行前会校验主窗口是否存在(未初始化或已销毁则报错应用程序未找到或已销毁)。
成功响应:
{ "type": "control-response", "data": { "success": true, "command": "toggle", "message": "播放/暂停切换命令已执行" } }错误响应:
{ "type": "error", "data": { "message": "错误信息" } }获取当前播放信息
消息类型:get-song-info,无需data字段:
{ "type": "get-song-info" }服务端收到后,会通过 IPC 链路向渲染进程请求当前曲目快照(见 track-info.ts 与 initIpc.ts):主进程发送request-track-info,渲染进程汇总播放状态、当前歌曲对象与歌词数据后以return-track-info回复,整个过程有 2 秒超时保护,超时返回获取当前播放信息失败。
成功响应:
{ "type": "song-info", "data": { "playStatus": "play", "playName": "歌曲名", "artistName": "歌手名", "albumName": "专辑名", "currentTime": 123.45, "volume": 1, "playRate": 1, "id": 123456, "name": "歌曲名", "artists": "歌手名", "album": "专辑名", "cover": "http://...", "duration": 300, "lrcData": [], "yrcData": [] } }字段含义补充说明(依据 initIpc.ts 的组装逻辑):
| 字段 | 含义 | 来源 |
|---|---|---|
playStatus | 播放状态(如play/pause) | 播放器状态 store |
playName/artistName/albumName | 当前曲目的名称/歌手/专辑(格式化展示用) | getPlayerInfoObj() |
currentTime | 当前播放进度(秒) | 播放器状态 store |
volume | 音量(0~1) | 播放器状态 store |
playRate | 播放倍率 | 播放器状态 store |
id/name/artists/album/cover/duration | 当前歌曲对象的原始字段(展开playSong) | 音乐 store |
lrcData/yrcData | 普通歌词 / 逐字歌词数组 | 歌词 store(歌词未加载完成时返回空数组) |
lyricLoading/lyricIndex | 歌词加载状态与当前行索引(附加字段) | 歌词 store |
错误响应:
{ "type": "error", "data": { "message": "获取当前播放信息失败" } }事件广播
当播放器状态发生变化时,服务器会向所有已连接客户端广播消息。广播统一走SocketService.broadcast()(SocketService.ts),其内部会遍历客户端集合,仅向OPEN状态的连接发送,并统计成功/失败数量写入日志。广播的触发入口集中在 ipc-socket.ts,由渲染进程通过play-status-change、play-song-change、play-lyric-change、set-progress等 IPC 事件驱动。
欢迎消息
连接成功后服务器自动发送:
{ "type": "welcome", "data": { "message": "欢迎连接到 SPlayer WebSocket 服务", "timestamp": 1234567890123 } }播放状态更新
当播放/暂停状态改变时触发(渲染进程sendPlayStatus→play-status-change,见 PlayerIpc.ts):
{ "type": "status-change", "data": { "status": true, "timestamp": 1234567890123 } }status为布尔值:true表示播放中,false表示暂停。
歌曲信息更新
当切换歌曲或歌曲信息加载完成时触发(渲染进程sendSongChange→play-song-change,见 PlayerIpc.ts):
{ "type": "song-change", "data": { "title": "歌曲名 - 歌手", "name": "歌曲名", "artist": "歌手", "album": "专辑名", "duration": 240000, "timestamp": 1234567890123 } }注意duration单位为毫秒(ms),与song-info中按秒计量的duration不同。
播放进度更新
播放过程中实时触发。渲染进程通过sendSocketProgress以500ms 节流的频率推送(见 PlayerIpc.ts),因此实际广播间隔约 500ms:
{ "type": "progress-change", "data": { "currentTime": 12000, "duration": 240000, "timestamp": 1234567890123 } }currentTime与duration单位均为毫秒。服务端收到set-progress时会校验currentTime与duration是否都已提供,缺一即丢弃(见 ipc-socket.ts)。
歌词更新
当歌词数据加载或改变时触发(渲染进程sendLyric→play-lyric-change,同样带 500ms 节流,见 PlayerIpc.ts):
{ "type": "lyric-change", "data": { "lrcData": [], "yrcData": [], "timestamp": 1234567890123 } }lrcData为普通(逐行)歌词数据,yrcData为逐字歌词数据。服务端只有在两者之一非空时才进行广播(见 ipc-socket.ts),避免无歌词时产生无意义消息。
心跳检测
为避免长连接被中间设备或操作系统回收,客户端可以定期发送文本消息PING,服务器会立即自动回复文本消息PONG:
// 发送心跳(建议每 20~30 秒一次) ws.send("PING"); // 服务器自动回复 PONG ws.onmessage = (event) => { if (event.data === "PONG") { console.log("心跳正常"); } };服务端的实现非常轻量:在handleMessage中对消息做trim().toUpperCase()后直接与"PING"比较,匹配则向该连接回发"PONG"并提前返回,不进入 JSON 解析流程(见 SocketService.ts)。客户端可以据此实现断线自动重连逻辑。
错误处理与常见错误
服务端在以下场景会向客户端发送error消息:
{ "type": "error", "data": { "message": "错误描述信息" } }常见错误及触发条件汇总:
| 错误消息 | 触发条件 |
|---|---|
应用程序未找到或已销毁 | 主窗口未初始化或已被销毁(控制命令无法转发到渲染进程) |
缺少 command 参数 | control消息的data中没有command字段 |
未知的控制命令 | command不在toggle/play/pause/next/prev之内 |
消息格式错误,请发送有效的 JSON 格式消息 | 消息无法被JSON.parse解析(附带原始消息前 100 字符便于排查) |
消息格式错误,根对象必须是对象类型 | 根对象是数组或非对象类型 |
消息格式错误,缺少 type 字段 | 对象中没有type字段 |
未知的消息类型: xxx | type不在control/get-song-info之内 |
获取当前播放信息失败 | 渲染进程未返回歌曲信息或 2 秒内超时 |
客户端最佳实践:收到error后检查data.message做对应处理;连接意外关闭时(onclose)结合PING/PONG心跳做指数退避重连。
完整客户端示例
以下代码演示一个可直接运行的 WebSocket 控制端:连接后自动获取当前歌曲信息,发送控制命令,并实时打印各类状态广播。
const ws = new WebSocket("ws://localhost:25885"); // 连接成功后:获取歌曲信息 + 执行播放/暂停切换 ws.onopen = () => { console.log("已连接 SPlayer WebSocket 服务"); // 获取当前播放信息 ws.send(JSON.stringify({ type: "get-song-info" })); // 播放/暂停切换 ws.send( JSON.stringify({ type: "control", data: { command: "toggle" }, }), ); // 下一曲 ws.send( JSON.stringify({ type: "control", data: { command: "next" }, }), ); }; // 接收消息 ws.onmessage = (event) => { // 心跳回复是纯文本 PONG,先做文本判断 if (event.data === "PONG") return; const message = JSON.parse(event.data); switch (message.type) { case "welcome": console.log("欢迎消息:", message.data.message); break; case "song-info": console.log("当前歌曲:", message.data.playName, "-", message.data.artistName); break; case "control-response": console.log("控制命令已执行:", message.data.command); break; case "status-change": console.log("播放状态:", message.data.status ? "播放中" : "已暂停"); break; case "song-change": console.log("切换歌曲:", message.data.title); break; case "progress-change": console.log("进度:", message.data.currentTime, "/", message.data.duration, "ms"); break; case "lyric-change": console.log("歌词已更新:", message.data.lrcData, message.data.yrcData); break; case "error": console.error("发生错误:", message.data.message); break; default: console.log("收到消息:", message); } }; // 心跳:每 25 秒发送一次 PING setInterval(() => { if (ws.readyState === WebSocket.OPEN) ws.send("PING"); }, 25000); // 断线自动重连(简单实现) ws.onclose = () => { console.log("连接断开,3 秒后重连..."); setTimeout(() => { location.reload(); // 在浏览器/Node 场景改为重新 new WebSocket(...) }, 3000); };底层实现要点
- 单例服务:
SocketService采用单例模式,SocketService.getInstance()全局唯一;isRunning()/getPort()分别暴露运行状态与当前端口,供 IPC 层查询(SocketService.ts)。 - 端口可用性预检:启动前先用
net.createServer()探测端口,捕获EADDRINUSE/EACCES即判定不可用(SocketService.ts),避免WebSocketServer启动即报错。 - 连接生命周期管理:服务用
Set<WebSocket>维护在线客户端;连接建立时加入集合、发送欢迎消息;close时移除;stop()时先逐个关闭客户端再关闭服务并清理(SocketService.ts)。 - 全链路广播链路:渲染进程(Vue 播放器)→ IPC(
play-status-change/play-song-change/play-lyric-change/set-progress)→ 主进程 ipc-socket.ts →SocketService.broadcast()→ 所有 WebSocket 客户端。换言之,客户端收到的每条广播最终都源自播放器状态 store 的变更,数据一致性有保障。 - 配套 HTTP API:WebSocket 服务与 SPlayer 的本地 HTTP API 服务(默认端口
25884,见 docs/api.md)并存互补——HTTP 适合一次性请求/轮询,WebSocket 适合实时控制与订阅。两者共享同一套播放控制能力,可混合使用。
相关文档
- SPlayer 使用指南:播放器整体功能与操作说明
- 本地 HTTP API 接口文档:
25884端口的 REST 控制接口 - SocketService 核心实现:WebSocket 服务端完整源码
- WebSocket IPC 桥接层:IPC 事件与广播消息的映射
- 网络设置(含 WebSocket 配置):设置界面中 WebSocket 开关、端口与测试逻辑
- 渲染进程 IPC 封装:
request-track-info/return-track-info的歌曲信息组装逻辑
【免费下载链接】SPlayer🎵 A cross-platform music player with Jellyfin / Navidrome / Emby media server support, word-by-word lyrics, desktop & taskbar lyrics, cloud music drive, local library management, audio spectrum visualization and mobile-friendly UI. 简约的跨平台音乐播放器,支持逐字歌词、桌面歌词、任务栏歌词、云盘音乐、本地音乐管理及流媒体播放项目地址: https://gitcode.com/GitHub_Trending/spl/SPlayer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考