把 IFU 接进现有系统
1. 集成方式总览
| 方式 | 传输 | 默认监听 | 适用 | 特点 |
|---|---|---|---|---|
| HTTP | TCP | 0.0.0.0:58000 |
管理后台、第三方控制、素材上传、状态查询 | REST + JWT;可选 HTTPS |
| TCP | TCP | 0.0.0.0:58001 |
中控长连接 | 每行一条 JSON(JSON Line);最多 50 客户端 |
| UDP | UDP | 0.0.0.0:58002 |
局域网广播/轻量控制 | 单数据报一条 JSON;无连接、不保证到达 |
| WebSocket | TCP | 0.0.0.0:58003 |
需要实时状态的监控端 | 订阅-推送;最多 100 会话 |
四个通道共用同一套指令契约:同样的 cmd 与 params,在任一通道上的执行结果一致。区别只在传输、鉴权方式与响应回法。
事件上报是另一条链路:程序可以主动把
progress等事件推到指定的 HTTP/TCP/UDP 目标,由progress_http_enabled、progress_tcp_enabled、progress_udp_enabled控制,默认关闭。
2. HTTP 接口清单
2.1 公开接口(无需 JWT)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/pub/playlist |
已启用节目单(enabled=1),按 sort_order 升序;失败返回 500 而不是空数组 |
| GET | /api/pub/status |
播放状态 + 监视页布局字段 |
| POST | /api/pub/control |
下发播放指令;配置 control_secret 后需 token 或 JWT |
| GET | /api/auth/machine-code |
获取机器码(用于申请授权) |
| GET | /media/{filename} |
静态媒体文件,支持 HTTP Range |
2.2 认证接口
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| POST | /api/auth/login |
否 | 登录,返回 JWT;同 IP 10 分钟失败 5 次锁定 |
| POST | /api/auth/logout |
Bearer(可选) | 令牌版本自增,旧 token 立即失效;幂等 |
| PUT | /api/auth/password |
Bearer | 修改密码,body 字段 old / new |
| GET | /api/auth/license |
Bearer | 授权状态、机器码、到期日、剩余天数 |
| POST | /api/auth/license |
Bearer | 激活,body 字段 licenseKey |
| DELETE | /api/auth/license |
Bearer | 清除授权文件 |
2.3 管理与业务接口(需 Bearer)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/playlist |
全部节目单项(含停用项),LEFT JOIN 素材信息 |
| POST | /api/playlist |
新增节目单项,成功返回 201 与 id |
| PUT | /api/playlist/{id} |
更新节目单项 |
| DELETE | /api/playlist/{id} |
删除节目单项(只删编排,不删素材) |
| PUT | /api/playlist/batch |
批量替换,body 为 {"items":[...]},先清空再事务写入 |
| PUT | /api/playlist/order |
重排,body 为 {"ids":[3,1,2]} |
| POST | /api/player/control |
下发播放指令;body id 非空时校验 device_id |
| GET | /api/player/status |
播放器状态 |
| GET | /api/files |
素材列表(过滤物理文件已不存在的记录) |
| DELETE | /api/files/{filename} |
删除素材:停播 → 软禁用节目单 → 删记录 → 删文件 |
| POST | /api/upload |
多文件上传,multipart/form-data |
| GET | /api/config |
全部配置 + platform + capabilities |
| PUT | /api/config |
批量写配置,广播 config_changed |
| GET | /api/logs |
操作日志,支持 channel / cmd / result 筛选,最多 500 条 |
| DELETE | /api/logs |
清空操作日志 |
| GET | /api/network/info |
IP / 掩码 / 网关 / DNS |
| POST | /api/system/shutdown |
系统关机(Android 返回 ignored) |
| POST | /api/system/restart |
系统重启(Android 返回 ignored) |
| POST | /api/reload |
重新加载节目单,不中断当前播放 |
| POST | /api/transcode/start |
提交转码,body {"filename":"..."} |
| POST | /api/transcode/cancel |
取消转码,body {"filename":"..."} |
| GET | /api/transcode/status |
busy / queue_size / current_file / current_progress |
除上述接口外,其余非
/api/、非/media/的路径由 SPA fallback 交给内置 Web 后台;未知/api/路径返回 404{}。
3. 认证
3.1 管理端 JWT
POST /api/auth/login
Content-Type: application/json
{"username":"admin","password":"admin123"}
成功:
{"status":"ok","token":"eyJhbGciOiJIUzI1NiIs..."}
之后请求携带:
Authorization: Bearer
规则:
- 算法 HS256,有效期 24 小时
- payload 含
user与ver(令牌版本号) - 登出或改密会使
ver自增,所有旧 token 立即失效 - 密钥首次启动随机生成并持久化,不硬编码
- 密码使用 PBKDF2-HMAC-SHA256(10 万次迭代 + 随机盐)存储
3.2 控制通道共享密钥
配置项 control_secret 为空时,TCP/UDP/WebSocket 与 /api/pub/control 默认开放(便于联调)。一旦填入非空值:
- TCP / UDP / WebSocket:指令 JSON 顶层必须带
"token":",常量时间比较" - HTTP
/api/pub/control:可用 query?token=、body 顶层token,或管理端 JWT 三者之一 - 鉴权失败返回
{"status":"error","message":"unauthorized"};HTTP 公开控制接口对应 401
正式交付必须配置
control_secret,并同步给所有调用方。
3.3 授权码
- 机器码:网卡名 + 主机名 + 操作系统与版本 + CPU 核数 的 SHA-256 前 32 位十六进制
- 授权码:
Base64(JSON payload).Base64(RSA-SHA256 签名) - payload:
{"machineId":"<机器码>","expiry":<毫秒时间戳>} - 校验顺序:机器码匹配 → RSA-2048/SHA-256 验签 → 到期时间
- 状态:有效 / 30 天内到期 / 已过期 / 未授权
- 文件:数据目录
license.key;程序每 60 秒复查
4. 指令格式
4.1 请求
{
"id": "",
"cmd": "setplay",
"params": {"file": "#1"},
"token": ""
}
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
id |
string | 否 | 指令标识。WS/TCP 中非空时只回给来源连接;为空则广播响应。HTTP /api/player/control 中它被当作设备 ID,非空且不等于本机 device_id 时返回 400 |
cmd |
string | 是 | 指令名,使用小写(注册表按原样匹配) |
params |
object | 否 | 指令参数,缺省为空对象 |
token |
string | 视配置 | 共享密钥;control_secret 非空时 TCP/UDP/WS 必填 |
timestamp |
number | 否 | 毫秒时间戳;缺省由服务端补齐 |
4.2 响应
指令通道统一返回:
{"status":"ok","id":"","data":{...}}
{"status":"ignored","id":"","message":"已在播放状态"}
{"status":"error","id":"","message":"未知指令: xxx"}
HTTP 管理接口的写操作多为 202 Accepted:
{"status":"accepted"}
HTTP 业务查询直接返回数据对象(数组或对象),错误统一:
{"status":"error","message":"..."}
4.3 去重与延迟队列
setplay对同一文件有 200 ms 防抖:窗口内重复切同一文件返回ignored("操作过于频繁,请稍后重试")- 播放器处于 Preparing 时,
setplay进入最多 3 条的延迟队列;准备完成后只执行最先入队的一条,其余丢弃(避免连发导致连续跳项) - 其他指令不做全局去重
5. 指令表
| 指令 | 参数 | 行为 | 通道限制 |
|---|---|---|---|
setplay |
file 或 playlist_id;#N 表示第 N 项(1 起) |
切到指定文件;不在节目单中则按外部文件路径兜底播放 | 四通道 |
play |
无 / file / playlist_id |
无参数:暂停→恢复、停止→从当前项/第 1 项开始、播放中→ignored;带文件:等价 setplay |
四通道 |
pause |
无 | 仅 playing 可暂停,否则 ignored | 四通道 |
resume |
无 | 仅 paused 可恢复,否则 ignored | 四通道 |
replay |
无 | 重播当前项;图片重置停留计时,视频 seek(0) 续播 | 四通道 |
seek |
mode:absolute/relative/progress;seconds 或 percent |
绝对秒、相对秒、百分比(0–1);图片返回 error | 四通道 |
volume |
level 0–100 |
设置音量并推送 volume 事件 |
四通道 |
next / previous |
无 | 下一项 / 上一项 | 四通道 |
status |
无 | 返回 state、position、duration、volume、current_file、media_type |
四通道 |
playlist |
无 | 返回 current_index、total、items |
四通道 |
window_show / window_hide |
无 | 显示 / 隐藏窗口 | 四通道 |
debug |
无 | 切换调试面板 | 四通道 |
setConfig |
key、value |
写配置;key 必须在默认配置白名单内 | 仅 HTTP |
shutdown |
无 | 系统关机(5 秒后) | 仅 HTTP |
restart |
无 | 系统重启(5 秒后) | 仅 HTTP |
quit_app |
无 | 退出应用 | 仅 HTTP |
restart_app |
无 | 重启应用 | 仅 HTTP |
受控指令(
shutdown/restart/quit_app/restart_app/setConfig)从 TCP/UDP/WebSocket 调用会返回:该指令仅允许 HTTP 管理通道调用。
5.1 setplay 细节
{"file":"#3"}:节目单第 3 项{"playlist_id":"#3"}:同上{"file":"abc.mp4"}:按文件名在节目单中查找;找不到则按路径兜底播放{"playlist_id":"1"}且无file:回退到节目单第 1 项- 序号越界:
节目单序号无效: N - 节目单为空且无目标:
无播放目标且节目单为空
5.2 seek 细节
{"cmd":"seek","params":{"mode":"absolute","seconds":30}}
{"cmd":"seek","params":{"mode":"relative","seconds":-10}}
{"cmd":"seek","params":{"mode":"progress","percent":0.5}}
图片类型返回:图片类型不支持seek操作。
6. HTTP 调用示例
6.1 公开控制(未配置 control_secret)
Invoke-RestMethod -Method Post -Uri 'http://192.168.1.50:58000/api/pub/control' `
-ContentType 'application/json' `
-Body '{"cmd":"setplay","params":{"file":"#2"}}'
6.2 公开控制(已配置 control_secret)
Invoke-RestMethod -Method Post -Uri 'http://192.168.1.50:58000/api/pub/control?token=你的密钥' `
-ContentType 'application/json' `
-Body '{"cmd":"volume","params":{"level":60}}'
6.3 登录后控制(推荐管理端使用)
$base = 'http://192.168.1.50:58000'
$login = Invoke-RestMethod -Method Post -Uri "$base/api/auth/login" `
-ContentType 'application/json' `
-Body '{"username":"admin","password":"你的密码"}'
$h = @{ Authorization = "Bearer $($login.token)" }
# 带设备校验的控制
Invoke-RestMethod -Method Post -Uri "$base/api/player/control" -Headers $h `
-ContentType 'application/json' `
-Body '{"id":"FOX-STORE-001","cmd":"next"}'
# 查询状态
Invoke-RestMethod -Uri "$base/api/player/status" -Headers $h
6.4 上传素材
$h = @{ Authorization = "Bearer $($login.token)" }
Invoke-RestMethod -Method Post -Uri "$base/api/upload" -Headers $h -Form @{
files = Get-Item 'D:\media\promo.mp4'
}
字段名
files;扩展名白名单与大小上限见《使用手册》。上传接口必须带 JWT。
6.5 媒体文件与 Range
GET http://192.168.1.50:58000/media/3f2a....mp4
Range: bytes=0-1048575
返回 206 与 Content-Range;越界返回 416。未知扩展名强制 application/octet-stream + 附件下载。
7. TCP / UDP / WebSocket
7.1 TCP(58001)
- 每行一条 JSON;分隔符支持
LF、CR、CRLF、NUL、$ - 行首
^自动剥离;推荐^{"cmd":"play"}$ - 响应为一行 JSON +
\n id非空只回来源连接;id为空广播给所有客户端- 最多 50 客户端;单连接缓冲超过 1 MB 断开
$client = New-Object System.Net.Sockets.TcpClient('192.168.1.50', 58001)
$stream = $client.GetStream()
$writer = New-Object System.IO.StreamWriter($stream)
$writer.AutoFlush = $true
$writer.WriteLine('^{"cmd":"status"}$')
$reader = New-Object System.IO.StreamReader($stream)
$reader.ReadLine()
$client.Close()
7.2 UDP(58002)
- 一个数据报一条 JSON;回复发给最后发送者
- 无连接、不保证到达;源码当前未实现令牌桶限流,上层应自行控制发送频率
- 单包建议不超过 64 KB
$udp = New-Object System.Net.Sockets.UdpClient
$bytes = [Text.Encoding]::UTF8.GetBytes('{"cmd":"status"}')
$udp.Send($bytes, $bytes.Length, '192.168.1.50', 58002) | Out-Null
$ep = New-Object System.Net.IPEndPoint([Net.IPAddress]::Any, 0)
[Text.Encoding]::UTF8.GetString($udp.Receive([ref]$ep))
7.3 WebSocket(58003)
连接:ws://192.168.1.50:58003
订阅:
{"type":"subscribe","events":["progress","player_changed","command","volume"]}
发送指令:
{"id":"c1","cmd":"setplay","params":{"file":"#1"}}
响应会回到该连接(因为 id 非空)。服务端每 30 秒发协议 Ping 与应用层 {"type":"ping"};客户端可回 {"type":"pong"}。二进制帧被忽略;最多 100 会话。
8. 推送事件
事件信封(WS pushEvent / 事件上报):
{
"device_id": "FoxPlayer-001",
"event": "progress",
"ts": 1750000000,
"data": { }
}
config_changed、files_changed 走广播通道,信封为:
{"event":"config_changed","data":{ },"ts":1750000000}
| 事件 | 触发 | data 主要字段 |
|---|---|---|
progress |
播放状态流(约 250 ms) | position、duration、state、volume、current_file、media_url、media_url_transcoded、media_type |
player_changed |
当前文件变更 | current_file、media_url、media_url_transcoded、state、duration、media_type |
command |
收到有效指令 | cmd、params |
volume |
volume 指令 |
volume |
config_changed |
PUT /api/config |
变更键值;monitor_* 变更时补全结构化字段 |
files_changed |
上传完成 | action、files、count |
WS 的
progress/player_changed/command/volume受progress_ws_enabled与push_ws_events白名单过滤(默认只开progress,player_changed)。想收到command/volume,需把它们加入push_ws_events。config_changed/files_changed不受该白名单限制。
8.1 外推目标(可选)
| 配置 | 作用 |
|---|---|
progress_http_enabled + progress_http_url |
把事件 POST 到指定 URL |
progress_tcp_enabled + progress_tcp_target_host/port |
把事件以 JSON Line 推到指定 TCP 服务 |
progress_udp_enabled + progress_udp_target_host/port |
把事件以数据报推到指定 UDP 地址 |
progress_interval_ms |
推送间隔基准,默认 250 |
push_http_events / push_tcp_events / push_udp_events |
各通道事件白名单,逗号分隔 |
9. 错误码与常见消息
9.1 HTTP 状态码
| 状态码 | 场景 | 响应示例 |
|---|---|---|
| 200 | 查询/更新成功 | {"status":"ok"} 或数据对象 |
| 201 | 新增节目单项 | {"status":"ok","id":12} |
| 202 | 指令已接受、关机/重启已下发 | {"status":"accepted"} |
| 400 | 参数缺失、旧密码错误、授权码格式错、device_id 不匹配 | {"status":"error","message":"旧密码错误"} |
| 401 | 未带 token / token 无效 / 登录失败 / 控制密钥错误 | {"status":"error","message":"认证令牌无效或已过期"} |
| 404 | 节目单项不存在、未知 API 路径 | {"status":"error","message":"项目不存在"} |
| 415 | 上传扩展名不在白名单 | {"status":"error","message":"不支持的文件类型: x.exe"} |
| 416 | Range 越界 | Range Not Satisfiable |
| 500 | 服务端异常 | {"status":"error","message":"操作失败"} |
| 503 | 节目单服务未初始化 | {"status":"error","message":"节目单服务未初始化"} |
9.2 指令层消息
| 消息 | 含义 |
|---|---|
未知指令: xxx |
cmd 不在注册表 |
操作过于频繁,请稍后重试 |
setplay 200 ms 去重 |
当前不在播放状态 / 当前不在暂停状态 |
pause / resume 状态不匹配 |
当前无播放内容 |
replay 时无当前项 |
图片类型不支持seek操作 |
对图片执行 seek |
节目单序号无效: N |
#N 越界 |
无播放目标且节目单为空 |
setplay 无目标且节目单为空 |
该指令仅允许 HTTP 管理通道调用 |
受控指令走了非 HTTP 通道 |
unauthorized |
共享密钥缺失或不匹配 |
非法配置键: xxx |
setConfig 的 key 不在白名单 |
10. 集成注意事项
id有两层含义:在 WS/TCP/UDP//api/pub/control中是指令标识(用于定向回包);在/api/player/control中同时被当作设备 ID 校验。跨系统对接时建议统一使用空id+ 业务侧自建请求号。cmd用小写:注册表按字符串精确匹配,SetPlay不会命中setplay。- 系统指令只走 HTTP:不要让中控通过 UDP 关机;这是有意的安全边界。
- 控制密钥必须配:不配
control_secret等于把播放控制开放给整个局域网。 - 重复切同一文件会被去重:200 ms 内的第二次
setplay返回ignored,不是故障。 - 状态查询优先用
/api/pub/status:字段最全(含media_url_transcoded与监视布局);/api/player/status更精简。 - 上传要 JWT:
/api/upload已套认证,匿名上传会 401。 - 视频 seek 用 Range:浏览器/播放器拉
/media/时可能发Range,服务端已支持 206/416。 - HTTPS 是可选:证书与私钥都配置才启用;加载失败会回退 HTTP 并记录告警,不会中断启动。
- 事件不是可靠消息:UDP 推送会丢包;关键状态以轮询
/api/pub/status为准。
事实依据:http_server.dart、auth_routes.dart、pub_routes.dart、player_routes.dart、playlist_routes.dart、command_service.dart、websocket_server.dart、tcp_server.dart、udp_server.dart