☰
基于Vue的EasyMedia-ui:RTSP转FLV流媒体播放前端实践与部署指南
2026/10/21 13:52:25 网站建设 项目流程

简介:EasyMedia-ui.zip 是一套面向流媒体直播与点播场景的 Vue.js 前端页面方案,内置 RTSP、FLV 播放支持及单屏/四分屏/九分屏/十六分屏布局,适合开发者直接集成、二次改造,也可作为前端工程师学习流媒体交互与组件化开发的参考。包内共 22 个文件,仅 101KB,以 7 个 js 和 7 个 vue 源码为核心,辅以 json 配置、html 入口、ico/png 图标以及 eslint、babel 等工程化配置,目录结构清晰,从 public 静态资源到 src 源码目录一应俱全,路由、状态管理与 HTTP 接口封装均独立成模块,便于按需阅读。目前已有 16570 人学习下载,涵盖完整前端工程骨架,从浏览器兼容到生产构建优化均有体现。阅读源码可掌握 Vue Router 页面组织、请求封装和多路视频流布局思路,节省从零搭建播控界面的时间。

1. RTSP 转 FLV 播放场景:EasyMedia-ui 到底是什么

一个实际问题:某公司要把十几个监控摄像头画面搬到浏览器里,刷新页面就能看,不用再装厂商的客户端。摄像头那边只有 RTSP 流,浏览器里却放不了 RTSP,必须在中间加一个服务,把 RTSP 拉下来转成 HTTP-FLV 或 HLS,再用 flv.js 或普通 video 标签播放。很多人第一反应是自己起 Nginx 做 RTMP 转 HLS,结果延迟高、配置绕,最后往往被卡在管理界面上——服务起来了,却得对着接口文档一行行调。EasyMedia-ui.zip 就是干这个的:它是一整套流媒体服务的管理前端,把设备接入、通道管理、视频预览、录像回放和后台拉流转发配置整理成可视化页面。这套资源适合两类人:一类是有后端基础、想快速交付摄像头直播网页的全栈工程师;另一类是刚接触流媒体项目、需要一个能照着改造的前端界面做二次开发的从业者。

2. 先拆包再动手:目录结构、开发环境与启动配置

2.1 解压之后先看哪些目录,前端是怎么和后端对接的

拿到 EasyMedia-ui.zip,别急着 npm install,我一般会先看根目录。这个包本质是一个基于 Vue 的前端工程,里面通常会有一层 src,src 下分 views、api、router、store 等模块。和普通管理后台相比,重点要看两个目录:src/api 下面的接口定义文件,流媒体服务的增删改查、拉流地址获取,全部是通过这一层封装出来的,后端地址和路径也主要在这里改;src/views 下面的 video 或 live 相关页面,这是最核心的业务代码,通道列表、播放器组件、录像回放入口都在这里。

打开 src/api 目录,大概率能看到类似这样的文件命名:

src ├─ api │ ├─ media.ts │ └─ system.ts ├─ views │ ├─ media │ │ ├─ device.vue │ │ ├─ channel.vue │ │ └─ play.vue │ └─ system └─ router

把接口文件打开,你会看到里面用 axios 封装了一批请求,常见做法是统一放在一个 media.js 或 media.ts 文件里,访问 /media/channel/list、/media/channel/play 之类的路径。如果你对接的是同一套后端,接口路径基本可以直接用;如果要对接自己写的服务,只需要改 baseURL 和方法路径。

这里有个容易出错的地方:接口返回的数据结构,不同后端风格差异很大。有些返回 { code: 200, data: [...] },有些直接返回数组。拿到包后第一件事不是改页面,而是先用接口调试工具或直接浏览器访问一下,确认后端返回结构和前端代码里 res.data 的取值是否一致。不一致的话,改封装层比改每个页面要省事得多。

2.2 开发环境怎么准备,npm 版本和依赖装在哪一步容易翻车

这个包是前后端分离里的纯前端部分,环境准备不复杂。需要本机有 Node.js 和包管理器,我用的是 Node 16 左右的 LTS 版本,npm install 能顺利跑完。如果你机器上只有最新版 Node,遇到旧版 node-sass 类的依赖,编译经常会报错,这时候不要硬刚,直接看 package.json 里的构建脚本,换到低版本 Node 再装依赖。

装依赖的命令没什么特别:

cd EasyMedia-ui npm install npm run dev

执行完 npm run dev 后,终端会输出一个本地访问地址,通常是 http://localhost:80 或 http://localhost:8080 之类。这一步理论上很简单,但实际翻车概率最高的是两件事。

第一件,依赖安装时卡在某个二进制包下载,表现为进度条卡住不动或报 ECONNRESET。常见原因是 npm 镜像源不稳定,解决办法是切换镜像源:

npm config set registry https://registry.npmmirror.com npm install

第二件,启动后页面白屏,打开控制台看到很多接口 404。这通常是后端起在别的端口,而前端代码里写死的接口地址是另一个端口。此时去 src 目录里找到环境变量文件,常见是 .env.development 或 src/settings.js,把 VITE_PROXY_TARGET 或 baseURL 改成后端实际地址,再重启 dev server 就好。

2.3 关键配置项:这些参数不调,后面播放全踩坑

开发模式下,前端访问后端一般走代理,目标地址配置在 vite.config.js 或 webpack 的 proxy 配置里。一个典型的代理配置长这样:

// vite.config.js export default defineConfig({ server: { host: '0.0.0.0', port: 80, proxy: { '/api': { target: 'http://127.0.0.1:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), }, '/rtp': { target: 'http://127.0.0.1:8080', changeOrigin: true, }, }, }, });

这里的 /api 是接口层代理,/rtp 是流媒体代理。我做这个项目时最常被人问的就是:为什么视频预览总是请求一个带 rtp 或 streams 的地址,然后一直转圈?

原因是播放 RTSP 转出来的 FLV 流时,后端返回的不一定是静态文件路径,而是一个会话型地址,比如 http://host:port/rtp/xxx.flv。这类地址需要经过后端转发,如果代理没覆盖到,浏览器直接请求真实端口能通,但页面代码里的 baseURL 会把地址拼成前端域名,导致 404。把 /rtp 也加进代理,基本能解决大半的播放失败问题。

3. 把 RTSP 拉流转成 FLV:界面与后端能力的映射

3.1 添加摄像头通道的完整流程

EasyMedia-ui 这类前端的核心业务是设备管理。设备一般分两层:一个是设备,对应一台摄像头;一个是通道,对应设备上的一个视频源。打开前端页面,你会看到左侧菜单里大概率有设备管理和通道管理。

添加一个摄像头,常见路径是设备管理里点新增设备,页面会弹一个表单让你填设备名称、设备类型、RTSP 地址、用户名密码等。一个典型 RTSP 地址格式如下:

rtsp://admin:password@192.168.1.64:554/h264/ch1/main/av_stream

需要特别说明的是,这个字段在后端会直接用于拉流。有一些摄像头在 URL 里带特殊字符,比如密码里有 @ 符号,必须做 URL 编码,否则后端解析地址时会截断到第一个 @,下面的失败日志里你会看到它把用户名当成了 admin:password 的中间内容,怎么都鉴权失败。

填完提交,前端会调用设备的保存接口。之后页面会显示这条设备的在线状态。有些后端支持通道探测,点一下探测通道,后端会去尝试连接摄像头,返回具体的码流信息,前端把通道列表刷新出来。这一步是可选的,但不做它,后面添加播放地址时只能手填,容易填错。

我一般会先加一个设备,再进通道管理里确认通道状态显示为在线才开始下一步。这一步能帮你把问题定位到拉流失败还是播放失败,别上来就去调播放器,否则排查范围会大很多。

3.2 播放器为什么用 flv.js,延迟状态怎么控制

在线视频预览页是这个包的核心。打开通道列表,右侧会有一个播放按钮,点击后弹窗或跳转页面,内部是一个 video 标签,通过 flv.js 拉取 HTTP-FLV 流进行播放。flv.js 的核心是把 FLV 封装转成 fMP4 喂给浏览器。

播放器初始化代码大致是这么写的:

import flvjs from 'flv.js'; const videoElement = document.getElementById('videoPlayer'); const flvPlayer = flvjs.createPlayer({ type: 'flv', isLive: true, url: flvUrl, }, { enableStashBuffer: false, stashInitialSize: 128, }); flvPlayer.attachMediaElement(videoElement); flvPlayer.load(); flvPlayer.play();

这里有两个参数值得单独拿出来讲。enableStashBuffer 设置为 false,表示不让播放器自己攒很长一段缓冲再开始播,这对低延迟监控场景是必要的。如果保持默认 true,你会看到画面总是比现场慢两三秒以上,而且时间越长缓冲越厚。

stashInitialSize 是初始缓存字节数,单位是 KB。网络环境差时可以适当调大,但如果是内网,128 足够。这个值不是越大越好,过度缓存会直接吞掉低延迟优势。哪些参数影响延迟,可以看这张表:

参数作用内网建议值
enableStashBuffer是否启用播放器缓冲false
stashInitialSize初始缓存大小(KB)128
isLive是否为直播流true
autoCleanupSourceBuffer是否自动清理缓存片段true

我一般习惯先把 stashInitialSize 设成 128,实测延迟还不能接受,再把 enableStashBuffer 关掉的同时调小 internalBuffer 相关参数。这几个参数在不同版本的 flv.js 里表现略有差异,但大方向一致:压低缓冲优先级,画面延迟下来。

3.3 用浏览器控制台验证整条链路是不是通的

前端写完、页面也渲染出来了,但不能只看画面。我在排查播放问题时候的固定动作是打开浏览器开发者工具,切到 Network 面板,筛选 Media 类型,看那个 .flv 请求的状态码。

如果看到 200 且响应体里带上 video/x-flv 或 application/octet-stream,说明拉流转发链路是通的,问题大概率出在播放器参数或缓冲设置。如果状态码是 404,基本是代理没配全或后端流媒体端口没开。如果状态码是 401,则是会话鉴权问题,看请求头里是否带了 token。

再进一步,点击那条 flv 请求,看响应头是不是没有固定长度或者显示 chunked。HTTP-FLV 直播流是持续输出的,正常情况不会有固定 Content-Length。如果显示一个很小的固定长度,那后端可能返回的是错误 JSON 而不是流,播放器自然起不来。

提示:如果发现 .flv 请求返回的是文本内容而非二进制流,先检查后端拉流日志,多半是 RTSP 地址鉴权失败后返回的错误报文。这种报错在前端页面上往往只表现为黑屏或转圈,不弹任何提示。

用这套方法,我能在两分钟内定位一个黑屏是前端缓冲问题还是后端拉流问题,而不是瞎调播放器参数。

4. 部署与集成:从开发机到生产环境的完整落地

4.1 打包构建与 nginx 部署细节

开发环境调通后,下一步就是打包。执行:

npm run build

构建完成后,输出目录一般在 dist 下面,里面是一个纯静态站点,可以丢到任意 Web 服务里。我这里以 nginx 为例,最常见的部署做法是新建一个 server 块,把 root 指到 dist 目录,并为接口和流媒体路径配置反向代理:

server { listen 80; server_name your-domain; root /opt/easymedia-ui/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; } location /rtp/ { proxy_pass http://127.0.0.1:8080/rtp/; proxy_set_header Host $host; } }

在这里,try_files 是必须的。因为 Vue Router 用的是 history 模式,刷新一个子页面时,比如访问 /media/channel,如果 nginx 找不到对应的物理文件,会直接返回 404,加这行配置让它回退到 index.html 就能解决。

前端代码里如果写死了 /api 前缀,上面的 location /api/ 就能对上。如果开发时用的是 vite 的 rewrite 把 /api 干掉了,那部署时要么在 nginx 里也做一层 rewrite,要么直接把接口封装层改成不带 /api 的前缀。我自己更倾向保留 /api 前缀,让生产环境和开发环境的语义保持一致。

4.2 登录鉴权模块的改造套路

很多人在拿到这个包后要做的第一件事,不是加摄像头,而是把它接入公司现有的统一登录体系。这类流媒体管理界面的典型鉴权流程是:登录页输入账号密码,后端验证后返回 token,前端把 token 存起来,后续每个请求带上 token 字段或请求头。

前端代码里,负责这个逻辑的通常是 store 下面的 user 模块和 axios 的拦截器。打开请求封装文件,你会看到类似下面的代码:

axios.interceptors.request.use(config => { const token = localStorage.getItem('Access-Token'); if (token) { config.headers['Authorization'] = 'Bearer ' + token; } return config; });

如果你要对接自己系统的登录接口,只需要改两处。第一处是登录页提交后调用的那个登录接口地址和参数格式;第二处是上面这段拦截器里的 token 名称和存放位置,要和现有系统保持一致。

还有一个小坑:跨系统接入时,有些系统的 token 不是放在 Authorization 头里,而是放在名为 access_token 的 query 参数里。这类接口,需要在拦截器里同时处理 query 拼接逻辑。接口层改完,页面层的路由守卫也要看一眼,beforeEach 里判断没有 token 就重定向到登录页,否则会出现一个现象:token 过期后,页面还停在原处,但所有接口都返回 401,看起来像系统卡死。

4.3 多环境配置:环境变量怎么隔离开发、测试、生产

生产环境往往不是一套。为了省事,我会在 src 下面创建三份环境文件:.env.development、.env.test、.env.production,定义接口地址、token 名称、日志开关等。构建时通过 --mode 区分:

npm run build --mode production

一个环境文件的示例:

VITE_APP_BASE_API=/api VITE_APP_WS_URL=ws://your-domain:8080/ws VITE_APP_LOG_ENABLED=false

前端代码里用 import.meta.env.VITE_APP_BASE_API 读这些变量,网络请求封装里统一拼到 baseURL 上。这样做的好处是以后换环境,不用重新打包,前端构建产物可以复用,只要 nginx 或后端网关改一下转发目标就行。

有一点要注意,如果部署时把前端文件放在 CDN 上,接口地址就不要再写相对路径,要保证接口指向一个固定的网关域名,避免用户拿到页面域名去请求接口域名造成跨域。这类坑在真正上线时才会冒出来,开发阶段基本不会暴露。

5. 常见问题排查与避坑:五条高频翻车记录

5.1 flv 黑屏但音频正常,甚至整页卡死

现象:通道播放页面能连上流,控制台也没有报 404,但 video 画面黑掉,偶尔有声音没有图像,严重时整个标签页卡顿。

原因:这不是这个 ui 包独有的问题,而是 flv.js 对浏览器版本和视频编码格式的兼容性限制。摄像头输出的 H.265 编码流,浏览器原生不支持,而 flv.js 大多版本也只完整支持 H.264。后端把 RTSP 转成 FLV 时,如果没有把编码转成 H.264,浏览器播放端就会挂。

解决:先确认摄像头输出的视频编码,在设备后台把主码流编码改成 H.264,子码流同理;改不了就在后端转码配置里开启转 H.264,但代价是 CPU 占用会上去。如果是 H.264 仍然黑屏,就看浏览器是否启用了硬件解码,关掉硬件加速再试。

5.2 RTSP 地址里带特殊字符导致设备一直离线

现象:新增设备填完 RTSP 地址,保存后通道状态始终是离线,后端日志看不到明显的连接错误,只有解析异常。

原因:密码或用户名里包含 @、:、/ 这类 URL 保留字符,后端解析 rtsp 地址时直接按字符串处理,把保留字符截断或解析错了位置,导致拉流地址根本不是你想填的那个。

解决:填地址前先把用户名密码部分做 URL 编码,比如密码本来是 abc@123,在地址里写成 abc%40123。在页面提交前加一个校验函数,把 @ 和 : 自动编码,能省掉后面大量的拉流排查时间。

5.3 点击录像回放白屏,接口返回 404

现象:预览正常,但点录像回放时弹窗空白,打开 Network 面板看到一个回放接口 404。

原因:录像回放依赖后端录像模块,很多部署方案默认没开启录像存储路径,或者回放地址和预览地址用的是不同的代理前缀,比如预览是 /rtp,回放是 /record,而 nginx 或 vite 代理里只配了 /rtp。

解决:先请求后端接口确认录像文件真实存在;如果存在,就把 /record 之类的前缀补进代理配置。如果不存在,检查后端的录像配置,确认 record 路径有存储权限并且已经写入 FLV 或 MP4 文件。这个问题我遇到过不止一次,每次都是代理前缀漏配,而不是前端页面写错。

5.4 登录成功后页面反复跳回登录页

现象:登录接口正常返回,token 也存到了 localStorage,但路由守卫仍把用户弹回登录页,形成死循环。

原因:前端把 token 存到了 localStorage,但路由守卫或 axios 拦截器读取时用的是 sessionStorage,或者是大小写不一致。也有一种可能,是 token 过期时间太短,后端返回的有效期不到一分钟,校验随即失败,页面刚跳转就又被弹回登录页。

解决:统一 token 读写位置和 key,建议定义成一个单独模块,比如 src/utils/auth.js,所有页面和拦截器都从这里读,不各自直接操作 localStorage。对于后端 token 有效期极短的情况,在响应拦截器里判断 401 时不要急着清 token,而是先刷新 token,只有刷新失败才跳登录页。

5.5 配置改了但页面不生效,像被缓存住一样

现象:改了前端页面里的某个文案或接口地址,重新刷新页面还是旧内容,甚至 npm run build 之后再访问还是旧版本。

原因:浏览器对静态资源做了强缓存,前端页面文件名如果没带 hash,旧版本会被浏览器直接命中;或者 nginx 端返回了没有 Cache-Control 的默认缓存策略。

解决:构建工具默认生成带 hash 的静态文件名,关键是要在 nginx 里给 index.html 设置 no-cache,同时给带 hash 的资源设置长缓存:

location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; } location ~* \.(js|css|png|jpg)$ { add_header Cache-Control "public, max-age=31536000"; }

按这个配置后,每次发布只需让用户强制刷新一次,后续不会再遇到旧版缓存顽固不更新的情况。发布新版本时,我一般会在构建机上加一步,上传前清空 dist 目录,避免遗留旧文件造成新旧资源混合加载。

6. 进阶:给通道管理加一个独立的审核预览权限

当你把整个界面跑通以后,最值得动手的改造是给流媒体页面加一层业务权限。很多实际项目里,不是所有用户都能看所有摄像头,比如某个平台账号只能看某几个通道,甚至连通道列表都不应该出现。前端如果只是通过隐藏按钮来应付,很不安全,因为接口是可以直接访问的。

我建议做一个通道授权模块,在用户表或角色表里增加一个 channelIds 字段,保存允许该用户观看的通道 ID 列表。后端在返回通道列表和拉流地址时,都按这个列表过滤。前端要做的事情则比较简单:在路由跳转到通道详情时,先请求一次用户可访问通道接口,用返回结果去控制播放按钮的 disabled 状态和路由守卫:

router.beforeEach(async (to, from, next) => { if (to.meta.requiresPermission) { const accessible = await fetch('/api/user/channel-ids'); if (!accessible.includes(to.params.channelId)) { next('/403'); return; } } next(); });

注意,这种前端权限校验只是体验层的兜底,真正的安全边界必须由后端拉流接口去校验,否则直接拿 flv 地址就能播放。

做完这个改造以后,我每次接新的流媒体界面项目,都会强制自己走一遍完整流程:先加一个真实设备、发起拉流、再刷新页面验证状态、最后查看接口返回的权限列表。这套动作下来,能避免交付给客户后反复出问题。如果你也拿到这份 EasyMedia-ui.zip 做流媒体界面,建议别急着改业务,先把播放链路整体跑通。希望这些经验能帮到你,少走几步弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询