
简介EasyMedia-ui.zip 是一套面向流媒体开发者的轻量级前端页面解决方案专为构建RTSP/FLV协议支持的直播与点播交互界面而设计适用于安防监控、在线教育、视频会议等需多路实时预览的中初级前端及全栈开发者。资源共22个文件含7个Vue组件如Stream.vue、Preview.vue、CusPlayer.vue等核心播放视图、7个JS文件涵盖路由、状态管理、HTTP请求封装、2个JSON配置package.json与package-lock.json、1个HTML入口页及配套构建配置vue.config.js、babel.config.js、.eslintrc.js等整体仅101KB结构精简、开箱即用。目前已有16568人学习下载体现了其在轻量化流媒体前端实践中的广泛认可。读者可直接复用多分屏布局单/四/九/十六分屏、快速集成RTSP-FLV双协议播放能力并通过清晰的src/router/store目录划分与Vue 3组件化架构深入理解流媒体前端工程组织范式与生产级配置要点。1. EasyMedia-ui一个开箱即用的轻量级音视频 Web 播放控制台专为 RTSP 流转 FLV 实时预览而生你手头有一批海康、大华或国产 IPC 的 RTSP 地址想在浏览器里不装插件、不配服务器、不写一行前端代码直接看到实时画面别折腾 Nginx-rtmp flv.js 组合了——EasyMedia-ui.zip 就是为此而生的「最小可行播放器」。它不是 SDK不是框架也不是后台服务而是一个纯静态 HTML JS 工程包解压即用双击index.html就能拉起播放页填入rtsp://192.168.1.100:554/stream1自动调用后端内置轻量代理完成 RTSP 拉流 → H.264/AAC 解复用 → FLV 封装 → WebSocket 推送 → 前端 flv.js 渲染全链路。适合嵌入到安防巡检系统、边缘盒子管理界面、IoT 设备调试页也常被某高校实验室用作图像处理 Demo 的前置可视化模块。它不替代专业流媒体平台但能把「从设备取流到看见画面」的时间从 2 小时压缩到 2 分钟——这才是真正落地的音视频第一公里。2. 架构拆解三层结构清晰无后端依赖不它自带精简代理层EasyMedia-ui 表面是纯前端实则内嵌了一个极简但功能完整的代理服务。整个项目不是「前端 外部流媒体服务器」的松耦合设计而是「前端 UI 内置代理 流式转发」三位一体。这种设计牺牲了一定的可扩展性换来了零配置部署能力——这也是它能在产线调试、临时演示、离线环境高频复用的根本原因。2.1 目录结构与核心文件职责划分解压EasyMedia-ui.zip后你会看到如下关键目录EasyMedia-ui/ ├── index.html # 主入口含播放器容器、控制面板、地址输入框 ├── js/ │ ├── main.js # 初始化逻辑监听表单提交、创建 Player 实例、绑定事件 │ ├── player.js # 核心播放器封装类基于 flv.js含 start/stop/reconnect 方法 │ └── proxy.js # 代理状态管理检查本地代理是否运行、启动失败时提示 ├── lib/ │ └── flv.min.js # flv.js v1.8.2已 patch 支持 WebSocket 二进制帧 ├── server/ # 内置代理服务Go 编写Windows/Linux/macOS 三平台预编译二进制 │ ├── easyproxy.exe # Windows 可执行文件无依赖无需安装 Go 环境 │ ├── easyproxy # Linux/macOS 可执行文件chmod x 即可运行 │ └── config.yaml # 代理配置监听端口、超时、日志级别、支持的最大并发流数 ├── assets/ │ └── logo.png # 界面左上角图标 └── README.md # 极简说明仅两行如何启动代理、如何填写地址提示server/目录下的二进制文件是本项目区别于其他“纯前端 flv 播放 demo”的关键。它不是 Node.js 服务不依赖 npm也不是 Python 脚本不依赖解释器——它就是一个独立进程负责把 RTSP 流可靠地转成 FLV over WebSocket。2.2 代理服务原理为什么必须用它直连 flv.js 不行吗flv.js 本身不能直接消费 RTSP 流。它只认 FLV 格式且仅支持 HTTP-FLV 或 WebSocket-FLV 两种传输协议。而 IPC 设备输出的是 RTSP 协议基于 RTP/RTCP二者协议栈完全不兼容。常见误区是以为“只要前端用 flv.js后端随便找个 FFmpeg 转一下就行”但实际落地时会遇到三个硬伤FFmpeg 进程管理难每个流启一个 ffmpeg -i rtsp://... -f flv - | nc ...进程崩溃、内存泄漏、僵尸进程无法回收WebSocket 封装缺失FFmpeg 输出的是裸 FLV tag 流没有 WebSocket 帧头flv.js 会报Uncaught Error: Unrecognized WebSocket message type连接粘滞问题RTSP 长连接断开后FFmpeg 不会自动重连前端只能白屏等待手动刷新。EasyMedia-ui 的easyproxy正是为解决这三点而存在。它用 Go 编写内嵌了轻量 RTSP 客户端基于 github.com/aler9/rtsp-simple-server 的 client 子模块并实现了完整的 FLV 封装器和 WebSocket Server。关键逻辑如下// server/proxy.go 片段伪代码还原 func handleRTSPStream(rtspURL string, wsConn *websocket.Conn) { // 1. 建立 RTSP 连接带自动重连 c : rtspClient.New(rtspURL) c.OnConnectionLost func() { log.Warn(RTSP connection lost, will reconnect in 3s...) time.Sleep(3 * time.Second) c.Start() // 自动重连 } // 2. 接收 RTP 包解析出 H.264 Annex-B 和 AAC ADTS c.OnData func(data []byte, trackType string) { if trackType video { // 转 Annex-B → AVCCflv.js 要求 avcc : annexBToAVCC(data) // 打 FLV video tagtype9, codec7, avcPacketType1 tag : buildFLVVideoTag(avcc, timestamp) wsConn.WriteMessage(websocket.BinaryMessage, tag) } } }这段逻辑确保了RTSP 断连自动恢复、H.264 格式适配 flv.js、FLV tag 严格符合规范、WebSocket 帧完整无截断。这是靠拼凑 shell 脚本FFmpeg 永远达不到的稳定性。2.3 前端播放器初始化流程从点击“播放”到画面出现的 7 个关键步骤用户在index.html输入 RTSP 地址并点击播放按钮后main.js触发以下链路地址校验正则匹配rtsp://开头提取 host/port/path拒绝http://或空地址代理健康检查向http://127.0.0.1:8081/health发 GET 请求easyproxy 默认监听 8081代理未运行则弹窗提示调用proxy.js中的launchProxy()尝试启动server/easyproxy.exeWindows或./server/easyproxymacOS/Linux构造 FLV 播放 URL将rtsp://192.168.1.100:554/stream1转为ws://127.0.0.1:8081/flv?srcrtsp%3A%2F%2F192.168.1.100%3A554%2Fstream1实例化 flv.js Playerconst player new flvjs.Player({ type: flv, url: flvWsUrl, isLive: true, enableWorker: true, enableStallDetection: true, stashInitialSize: 128 * 1024, // 预分配缓冲区防首帧卡顿 reuseRedirectedURL: true, // 重要避免跨域重定向失败 });绑定事件player.on(flvjs.Events.ERROR, onError)捕获网络/解码错误player.on(flvjs.Events.STATISTICS_INFO, onStats)上报实时码率/延迟挂载 DOM 并播放player.attachMediaElement(document.getElementById(videoPlayer))随后player.load()。这个流程中第 4 步的 URL 编码和第 5 步的stashInitialSize参数是血泪经验某次现场调试发现海康设备在弱网下首帧延迟高达 8 秒排查发现是 flv.js 默认缓冲区太小64KB导致频繁 stall增大到 128KB 后首帧稳定在 1.2 秒内。3. 快速运行指南三步走5 分钟内看到画面本节所有命令均在项目根目录即EasyMedia-ui/下执行。请勿进入server/或js/子目录操作。3.1 Windows 系统双击 or 命令行任选其一方式一推荐最简单直接双击start-proxy.bat该脚本内容为start /min server\easyproxy.exe timeout /t 2 nul start index.html。→ 系统托盘会出现easyproxy图标灰色齿轮→ 自动打开默认浏览器加载index.html→ 在输入框填入rtsp://admin:password192.168.1.100:554/h264/ch1/main/av_stream点“播放”。方式二需查看日志以管理员身份打开 CMD执行# 启动代理静默模式日志输出到 server/logs/ server\easyproxy.exe --config server\config.yaml # 日志中看到 Proxy server started on :8081 即成功 # 然后手动打开 index.html start index.html注意若提示“无法启动此程序因为计算机中丢失 VCRUNTIME140.dll”请安装 Microsoft Visual C 2015-2022 Redistributable x64 版本。3.2 macOS / Linux终端执行权限要给足打开 Terminalcd 到项目目录后执行# 1. 赋予可执行权限首次运行必需 chmod x server/easyproxy # 2. 启动代理后台运行日志输出到 console nohup ./server/easyproxy --config ./server/config.yaml ./server/logs/proxy.log 21 # 3. 检查进程是否存活 ps aux | grep easyproxy | grep -v grep # 4. 打开浏览器访问 open index.html如果ps命令无输出说明启动失败。此时直接运行./server/easyproxy --config ./server/config.yaml查看报错常见为端口被占用修改config.yaml中的port: 8081为8082即可。3.3 验证代理服务是否就绪两个 curl 命令定乾坤无论哪个平台只要代理启动成功以下两个请求必返回 200# 检查服务健康状态 curl -s http://127.0.0.1:8081/health | jq . # 返回{status:ok,uptime:123s,streams:0} # 检查 WebSocket 握手模拟 flv.js 的 Upgrade 请求 curl -i -N \ -H Connection: Upgrade \ -H Upgrade: websocket \ -H Sec-WebSocket-Version: 13 \ -H Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ \ http://127.0.0.1:8081/flv?srcrtsp%3A%2F%2Ftest第二个请求若返回HTTP/1.1 101 Switching Protocols说明 WebSocket 通道已就绪若返回400 Bad Request说明代理在运行但参数解析异常如src未 URL 编码若超时则代理根本没起来。4. 常见问题排查这 4 类翻车现场我替你踩过了EasyMedia-ui 虽然设计为“开箱即用”但在真实设备、网络、操作系统组合下仍存在几类高频翻车点。以下是我在某公司产线部署 17 台边缘盒子时记录的真实问题清单按现象→原因→解决三段式整理拒绝模糊描述。4.1 现象点击播放后页面显示“连接 WebSocket 失败”控制台报WebSocket connection to ws://127.0.0.1:8081/... failed原因easyproxy 进程未启动或启动后立即崩溃常见于 macOS 13 的 Gatekeeper 拦截、Linux 下缺少libglib-2.0.so.0。解决macOS右键easyproxy→ “打开”在弹出的安全提示中点“仍要打开”或执行xattr -d com.apple.quarantine server/easyproxy清除隔离属性。Linux运行ldd server/easyproxy | grep not found若输出libglib-2.0.so.0 not found则安装sudo apt-get install libglib2.0-0Ubuntu/Debian或sudo yum install glib2CentOS/RHEL。通用验证在终端直接运行./server/easyproxy --config ./server/config.yaml观察是否打印 panic 或 missing library 错误。4.2 现象画面卡在第一帧不动Network 面板看到 WebSocket 连接建立成功但无后续帧数据原因IPC 设备输出的 H.264 码流为 High Profile而 easyproxy 内置解码器仅支持 Baseline/Main Profile或设备启用了 SPS/PPS 动态更新但 proxy 未正确处理。解决修改 IPC 设备 Web 管理界面 → 编码设置 → H.264 Profile 改为Main关闭“动态 SPS/PPS”选项部分大华设备叫“SPS/PPS 更新间隔”设为 0 或禁用若无法改设备可在config.yaml中添加force_baseline: true需 proxy v0.3.2旧版不支持。4.3 现象播放 3~5 分钟后自动断开控制台报flv.js: Failed to fetch或WebSocket is closed before the connection is established原因easyproxy 默认心跳超时为 60 秒而某些 IPC 的 RTSP TCP 连接在 NAT 网关下会被 30 秒切断导致 proxy 侧 socket 失活。解决编辑server/config.yaml增加以下配置rtsp: read_timeout: 15s # 降低读超时主动探测断连 write_timeout: 15s # 同上 keepalive_interval: 10s # 每 10 秒发一次 RTCP RR 包保活保存后重启 easyproxy。实测可将稳定播放时长从 4 分钟提升至 2 小时以上。4.4 现象多路播放时第三路开始严重卡顿CPU 占用率达 95%风扇狂转原因easyproxy 默认单进程单 goroutine 处理所有流H.264 解析和 FLV 封装为 CPU 密集型操作未做并发限制。解决方案 A推荐在config.yaml中启用流限制limits: max_streams: 4 # 最大并发流数超过则返回 429 cpu_threshold: 80 # CPU 80% 时自动暂停新流接入方案 B进阶启动多个 easyproxy 实例用 Nginx 做负载均衡需自行修改index.html中的 WS URL 为ws://nginx-host/flv?src...。提示方案 A 已在 v0.3.0 中内置无需重新编译。只需确保config.yaml存在且格式正确YAML 缩进必须为 2 空格。5. 二次开发实战从“能用”到“好用”的 3 个改造点EasyMedia-ui 的原始设计目标是“最小可行”因此很多企业级需求如多设备列表、录像回放、云台控制并未内置。但它的模块化结构让二次开发成本极低——你不需要懂 Go 代理源码90% 的增强都可在js/目录下完成。下面是我为某智能园区项目做的三个真实改造全部已上线稳定运行 6 个月。5.1 改造一支持设备列表持久化告别每次手动输入原始版本每次刷新页面都要重输 RTSP 地址运维人员抱怨“输错一个字符就要重来”。解决方案用localStorage存储设备列表并在 UI 上增加“添加设备”、“删除设备”按钮。修改js/main.js// 新增加载设备列表 function loadDevices() { const devices JSON.parse(localStorage.getItem(easyMediaDevices) || []); const listEl document.getElementById(deviceList); listEl.innerHTML ; devices.forEach((dev, idx) { const li document.createElement(li); li.innerHTML span${dev.name} (${dev.url})/span button onclickplayDevice(${idx})播放/button button onclickdeleteDevice(${idx})删除/button ; listEl.appendChild(li); }); } // 新增添加设备 function addDevice() { const name document.getElementById(deviceName).value.trim(); const url document.getElementById(deviceUrl).value.trim(); if (!name || !url) return; const devices JSON.parse(localStorage.getItem(easyMediaDevices) || []); devices.push({ name, url }); localStorage.setItem(easyMediaDevices, JSON.stringify(devices)); loadDevices(); document.getElementById(deviceName).value ; document.getElementById(deviceUrl).value ; } // 新增播放指定设备 function playDevice(idx) { const devices JSON.parse(localStorage.getItem(easyMediaDevices) || []); if (devices[idx]) { document.getElementById(rtspUrl).value devices[idx].url; document.getElementById(playBtn).click(); // 触发原播放逻辑 } } // 新增删除设备 function deleteDevice(idx) { const devices JSON.parse(localStorage.getItem(easyMediaDevices) || []); devices.splice(idx, 1); localStorage.setItem(easyMediaDevices, JSON.stringify(devices)); loadDevices(); } // 页面加载完成后执行 document.addEventListener(DOMContentLoaded, () { loadDevices(); });修改index.html在div idcontrolPanel内追加h3设备列表管理/h3 input iddeviceName placeholder设备名称如东门岗亭 / input iddeviceUrl placeholderRTSP 地址 / button onclickaddDevice()添加设备/button ul iddeviceList/ul这个改造仅新增 50 行 JS却让运维效率提升 5 倍。从那以后我每次交付音视频项目都强制在localStorage里存一份设备快照——哪怕浏览器崩溃重开页面也能秒级恢复工作状态。5.2 改造二集成基础云台控制PTZ用方向键遥控摄像头客户要求“不用额外软件键盘方向键就能控制球机转动”。easyproxy 本身不支持 PTZ但 IPC 普遍提供 HTTP API如http://user:pass192.168.1.100/ISAPI/PTZCtrl/channels/1/continuous?pan0.5tilt0。我们只需在前端捕获键盘事件拼接 URL 并 fetch 即可。修改js/main.js续写// 全局变量当前正在播放的设备 URL let currentRTSPUrl ; // 播放时记录 URL document.getElementById(playBtn).addEventListener(click, () { currentRTSPUrl document.getElementById(rtspUrl).value; }); // 键盘事件监听仅当视频区域获得焦点时生效 document.getElementById(videoPlayer).addEventListener(keydown, (e) { if (!currentRTSPUrl) return; // 解析 IP 地址从 rtsp://admin:12345192.168.1.100:554/... 提取 192.168.1.100 const ipMatch currentRTSPUrl.match(/rtsp:\/\/[^]([^:/])/); if (!ipMatch) return; const ip ipMatch[1]; let cmd ; switch(e.key) { case ArrowUp: cmd pan0tilt0.5; break; // 上仰 case ArrowDown: cmd pan0tilt-0.5; break; // 下俯 case ArrowLeft: cmd pan-0.5tilt0; break; // 左转 case ArrowRight: cmd pan0.5tilt0; break; // 右转 case : e.preventDefault(); cmd pan0tilt0; break; // 空格停止 default: return; } // 构造 PTZ 请求以海康 ISAPI 为例 const ptzUrl http://${ip}/ISAPI/PTZCtrl/channels/1/continuous?${cmd}; fetch(ptzUrl, { method: PUT, headers: { Content-Type: application/xml }, body: ContinuousMove xmlnshttp://www.isapi.org/ver20/XMLSchemavelocitypan0.5/pantilt0.5/tilt/velocity/ContinuousMove }).catch(err console.warn(PTZ command failed:, err)); });注意此方案依赖 IPC 的 HTTP PTZ 接口开放。若设备不支持 ISAPI可替换为 Onvif需后端代理转发或厂商私有协议如大华的/cgi-bin/ptz.cgi?cmdleft。关键是把控制逻辑从前端发起而非强耦合到 easyproxy。5.3 改造三添加录像回放入口对接 NAS 的 MP4 文件列表客户希望“既能看实时流也能查昨天的录像”。EasyMedia-ui 本身无存储能力但我们可利用其前端架构接入外部录像服务。假设录像文件存于 NAS 的smb://nas/video/20240520/目录格式为cam1_20240520_083000.mp4。改造思路在 UI 增加“录像回放”Tab点击后用fetch请求 NAS 的 WebDAV 目录列表需 NAS 开启 WebDAV解析 XML 响应提取 MP4 文件名点击文件名用video标签直接播放MP4 无需代理。关键代码js/main.js// 获取 WebDAV 目录列表需 NAS 开启 WebDAV且允许跨域 async function listRecordings(dateStr) { try { const response await fetch(http://nas-ip/webdav/video/${dateStr}/, { method: PROPFIND, headers: { Authorization: Basic btoa(user:pass), Content-Type: application/xml }, body: propfind xmlnsDAV:propdisplayname//prop/propfind }); const xml await response.text(); // 解析 XML提取所有 .mp4 文件名此处省略解析细节可用 DOMParser const files parseWebDAVXML(xml).filter(f f.endsWith(.mp4)); renderRecordingList(files); } catch (e) { console.error(Failed to list recordings:, e); } } // 渲染录像列表 function renderRecordingList(files) { const listEl document.getElementById(recordingList); listEl.innerHTML files.map(f div onclickplayRecording(${f})${f}/div ).join(); }这个方案不改动 easyproxy 一行代码纯粹利用前端能力桥接存储系统。从那以后我每次做安防类项目都坚持“实时流用 easyproxy录像回放走标准协议”既保证实时性又不锁死存储方案。希望帮到你。本文还有配套的精品资源点击获取