ESP32 监控中台:Vue 3 的实时视频界面

从多路视频流、端口管理到权限与部署,记录实验室监控中心的前端结构与工程取舍。

本文目录

monitor 是一个面向 ESP32-S3 摄像头集群的实验室监控前端。它把多路视频、设备端口和系统配置放在同一个 Vue 3 应用里,后端通过 HTTP API 管理节点,视频流则由每台设备独立推送。

技术栈与页面

项目使用 Vue 3(Composition API + <script setup>)、TypeScript、Vite、Element Plus、Pinia、Vue Router 和 Axios,视频解码使用 jmuxer。

页面职责
实时预览多路 MJPEG / WebSocket 视频流,支持展开与关闭
历史录像录像文件查询与回放
通讯终端节点端口状态、启停管理
系统配置设备调度与系统参数(管理员)
登录/角色访客只读、管理员可操作

侧边栏在桌面端常驻,移动端切换为抽屉导航;主题偏好保存在本地。

视频流:WebSocket 与 jmuxer

H.264 流通过 WebSocket 以二进制帧到达,前端把每一帧交给 jmuxer 解码到 <video>。关键不是“能播”,而是断流、重连和组件卸载时不要留下定时器。

ws = new WebSocket(props.streamUrl)
ws.binaryType = 'arraybuffer'

ws.onmessage = (event) => {
  jmuxer.feed({ video: new Uint8Array(event.data) })
}

ws.onclose = () => {
  if (destroyed) return
  reconnectTimer = window.setTimeout(connect, 2000)
}

组件卸载时会清空首次连接与重连定时器、摘掉 onclose 后主动关闭 WebSocket,并销毁 jmuxer;否则离开页面后仍可能继续建连。MJPEG 模式则直接使用 HTTP 流地址,不经过 jmuxer。

配置集中在一个文件

最容易出问题的不是请求本身,而是地址散落在各处。项目把 API 与流地址收敛到 src/config.ts:

export const API_BASE =
  import.meta.env.VITE_API_BASE ?? 'http://<YOUR_SERVER_IP>:3000/api'

export const STREAM_HOST =
  import.meta.env.VITE_STREAM_HOST ?? '<YOUR_SERVER_IP>'

节点只保存端口,URL 由 streamHttpUrl(port) / streamWsUrl(port) 拼接。换服务器只需要改 .env,不用搜索替换业务代码。

节点与端口管理

节点模型很克制:

interface DeviceNode {
  id: string
  udp: number
  http: number
}

Pinia 里的 usedPorts 会把所有节点的 UDP/HTTP 端口收进一个集合,启动新节点前检查冲突;界面上 UDP 范围是 9001–9050,HTTP 范围是 9051–9100,已占用端口会标红。启停、重置等操作只对管理员开放,普通访客只能看流。

权限:客户端门禁不是安全边界

项目用 VITE_ADMIN_PASSWORD 做前端管理员门禁,登录状态存在 localStorage。这适合实验室内网演示,但必须明确:构建进前端的密码不是授权机制。任何能拿到前端资源的人都能绕过它,真正的启停与配置接口必须在服务端校验身份。

如果要把这套系统放到不可信网络,至少需要:

  • 服务端会话或 Token 校验;
  • 对启停、重置、配置等写接口做权限控制与审计;
  • 不再把管理员密码放进 VITE_* 变量。

部署

仓库提供了一个 Windows 一键脚本:构建后通过 SSH 清理旧目录,SCP 上传 dist/,再更新 Nginx 权限。脚本里的服务器地址是占位值,部署前必须替换;生产环境更建议把部署步骤放进 CI,而不是把凭据留在批处理文件旁边。

复盘

  • 视频类页面的重点在生命周期管理:断流重连、卸载清理、重复建连。
  • 配置集中化能显著降低部署与排错成本。
  • 角色分离要前后端一起做;前端门禁只负责界面状态。
  • 监控系统没有“差不多能看”这一说,端口冲突和权限边界都要在代码里写清楚。

讨论

评论由 GitHub Discussions 提供,点击后加载。未加载前不会产生第三方请求。