资讯动态

从ActiveX到WASM:WEB无插件开发包V3.2安防视频预览

发布时间:2026/10/29 15:41:31 来源:尧图企业网站定制
简介海康威视 WEB 无插件开发包 V3.2 面向需要在浏览器端集成海康设备预览能力的 Web 开发者与项目集成人员重点解决高版本 Chrome、Firefox 无法继续使用传统插件播放的问题。它要求设备支持 Websocket 取流并配套 nginx 代理服务器完成无插件访问。压缩包共 75 个文件约 9.42MB包含 24 个 JavaScript、4 个 XML、4 个 HTML 页面以及 PDF 开发文档、可执行程序、license、nginx 配置与启动脚本等兼顾示例代码、运行环境与说明材料。已有 6737 人学习下载说明其在同类集成场景中具有较高参考价值。读者可从中获得控件开发包编程指南、无插件 Demo 测试说明、依赖库说明和已知问题列表并借助现成的 nginx 配置、启停脚本与前端页面快速搭建联调环境定位取流失败、代理配置和浏览器兼容性等常见问题适合需要落地设备预览功能的开发与实施人员查阅。1. 从插件装了还是黑屏的现场说起上周又碰到一个老项目安防监控页面在旧浏览器里跑得好好的换到新版 Chrome 之后一片黑右下角还挂着请安装插件的提示。这就是典型的老式 ActiveX/NPAPI 控件撞上新浏览器内核的结果——插件模型被彻底移除装什么运行时都没用。WEB 无插件开发包 V3.2 就是为这种场景准备的它把原来由本地控件干的活拆成 WebSocket 取流、WASM 软解、Canvas 渲染三件事在浏览器里完成对外仍然保留一套 JS 接口老项目的调用代码不用推倒重来。适合正在做安防平台前端、NVR/DVR 页面嵌入、监控大屏拼接的人也适合被必须装插件卡了很久、要给自己找一个可复现出口的开发者。2. 拆开 V3.2 的包结构一次从 ActiveX 到 WASM 的换芯拿到压缩包之后不要急着往项目里塞先花十分钟把目录看明白。这一版最容易被误判的地方在于它对外暴露的 JS 接口命名跟老的插件版高度相似很多人以为只是换了个壳实际上底下整条链路已经重写了。命名相似是为了兼容链路不同决定了后面所有的坑。2.1 老插件方案的三条死路先想清楚为什么非换不可。老方案依赖浏览器提供的本地二进制控件加载能力这条路现在有三处硬伤。其一是加载机制没了。控件靠object标签加 classid 注册到系统里浏览器内核不再支持这种注册方式页面里那段I_InsertOBJECTPlugin调用只会静默失败连报错都不给。其二是部署成本下不去。每个客户端都要下安装包、要管理员权限、要处理杀软误报一次版本升级就得把所有终端重刷一遍。做内网项目的人应该有体会光是让现场 IT 放行一个 exe来回能磨一周。其三是跨平台彻底断掉。国产系统和移动端根本没有这套控件同一个监控页面在平板和信创终端上打不开项目验收就卡在这儿。无插件方案把这三条一起绕过去了纯前端资源浏览器能跑就能用升级只是替换静态文件Windows、Linux、移动端共用一套代码。代价是解码从调用系统/显卡能力变成在浏览器里自己算CPU 开销明显上升这一点后面会专门讲。2.2 包里到底有什么五类文件的职责划分解压之后通常能看到这么几类东西名字各版本略有差异职责是稳定的文件/目录作用能不能改常见误操作主 JS如 websVideo.js对外 API 层初始化、登录、开流、控制不改逻辑只在外面包一层被压缩工具二次混淆导致内部符号丢失解码库.wasm / .memH.264/H.265 软解核心不能改只拷了 js 没拷 wasm路径 404依赖库jquery 等老接口内部用到不建议换版本项目里已有高版本 jQuery 覆盖掉样式与图标分屏布局、工具栏可覆盖全局样式冲突导致窗口高度为 0demo 页面最小可跑示例随便改直接拿去当生产代码没做错误处理这里有个血泪经验.wasm文件的加载路径是相对主 JS 所在目录算的不是相对页面。你把 js 单独抽到static/js/下、wasm 留在原目录运行时一定报解码库加载失败控制台里的提示还很含糊。2.3 取流链路拆解六步从设备到 Canvas把一次预览拆开看大致是这么六步每一层出问题表现都不一样定位时按层往下压效率最高。第一步JS 层发起登录。浏览器用 HTTP 请求把账号密码发到设备拿回一个会话标识。这一步失败的表现是登录超时或用户名密码错误跟画面无关。第二步建立 WebSocket 长连接。通常连的是设备侧一个专门端口常见做法是 7681 这类私有端口具体以设备手册为准这条链路上跑的是私有信令加媒体数据不是标准 RTSP。第三步下发开流指令。告诉设备我要第几通道、主码流还是子码流、什么编码格式设备开始往这条连接上推数据。第四步解复用。推过来的是封装格式PS 或自定义包头需要在 JS 里把音视频轨道拆出来还原成一帧帧 NALU。第五步WASM 解码。把 NALU 喂给解码库输出 YUV 数据。这一步是纯 CPU 计算也是最吃性能的地方。第六步渲染。YUV 转 RGB 后画到 Canvas 上或者交给 WebGL 做纹理上传。音频则走 WebAudio 播放。提示排查时先分清没连上还是连上了没画面。前者看网络和鉴权后者看解码和渲染两者混在一起查会浪费大量时间。2.4 为什么是 WASM 软解而不是 WebRTC 或 MSE这是个绕不开的选型问题很多人第一反应是都什么年代了还用软解。把三条路的账算一下就清楚了方案端到端延迟CPU 占用浏览器兼容部署前提MSE 服务端转封装25 秒低H.265 基本不可用需要流媒体服务中转WebRTC500ms 以内中需网关做协议转换额外网关 证书WebSocket WASM 软解300ms1 秒高全兼容设备直连即可设备侧私有协议不对外开放服务端转封装要先破解协议或者加一台网关机WebRTC 要额外部署信令和转发服务小项目根本划不来。WASM 软解的好处是浏览器只要能跑 JS 就能跑设备直连不用中间件坏处是把算力压力全甩给客户端。所以这套方案的合理边界是内网、并发路数可控、单机看 4 到 9 路、对成本敏感的场景。要做几十上百路的分发还是得在服务端做转封装。3. 最小可跑 Demo先把一路预览点亮不要一上来就往业务代码里塞先做一个只有一路画面的空白页。能稳定点亮一路后面 4 路 9 路才有意义。3.1 静态资源落位与目录约定目录结构建议保持原样只在外面套一层业务目录# 推荐落位整个 codebase 目录原封不动拷贝不改名不拆散 static/ └── webs/ ├── websVideo.js # 主 API ├── jsPlugin-1.0.0.js # 依赖 ├── jquery-1.12.4.min.js ├── decoder.wasm # 解码库必须与主 JS 同目录 ├── codebase.css └── demo.html这里的关键点是不拆散。decoder.wasm的默认加载路径跟主 JS 绑死你把它挪到 CDN 上或者放到assets/下面运行时会直接失败。如果确实要换路径得在主 JS 加载之前先设一个全局的 basePath 变量具体变量名看包里的 demo 注释别猜。静态服务用 nginx 的话注意给.wasm补 MIME 类型否则浏览器会拒绝实例化# 缺这一段时控制台会出现 Incorrect response MIME type 之类的报错 location ~ \.wasm$ { types { application/wasm wasm; } add_header Cache-Control no-cache; }application/wasm是必须的no-cache是为了升级版本时不至于拿到旧解码库——解码库和主 JS 版本不匹配时的表现非常玄学有时能出画面但颜色发绿有时干脆黑屏。3.2 页面骨架与脚本引入顺序引入顺序不能乱依赖必须在主库之前主库必须在调用代码之前。!DOCTYPE html html head meta charsetutf-8 title无插件预览最小页/title link relstylesheet href./webs/codebase.css !-- 顺序敏感jquery - 插件依赖 - 主库 -- script src./webs/jquery-1.12.4.min.js/script script src./webs/jsPlugin-1.0.0.js/script script src./webs/websVideo.js/script style /* 容器必须有确定高度否则播放器算出来的画布尺寸是 0 */ #divPlugin { width: 800px; height: 450px; background: #000; } /style /head body div iddivPlugin/div script src./app.js/script /body /html这段里最容易被忽略的是容器高度。播放器初始化时会读取容器宽高来创建 Canvas如果容器是靠 flex 自动撑开的、当时高度还是 0画面就会渲染到一个 0×0 的画布上表现为接口全部成功但就是黑的。我一般先写死宽高跑通再改成动态计算。3.3 初始化与登录四个必填参数的来源// 1) 初始化分屏数、全屏行为、窗口切换回调 WebVideoCtrl.I_InitPlugin({ bWndFull: true, // 双击单个窗口是否全屏 iWndowType: 1, // 1/4/9/16 分屏先跑通 1 再改 cbSelWnd: function (data) { console.log(当前选中窗口索引, data.index) } }) // 2) 挂载播放器必须在 DOM 就绪后调用且只调用一次 WebVideoCtrl.I_InsertOBJECTPlugin(divPlugin) // 3) 登录设备 WebVideoCtrl.I_Login(192.168.1.64, 1, 80, admin, Admin12345, { success: function () { console.log(登录成功) }, error: function (status, xml) { // status 是数字xml 里通常带设备返回的错误描述 console.error(登录失败, status, xml) } })I_Login的五个参数依次是设备 IP、协议类型1 走 http、2 走 https、HTTP 端口、用户名、密码。协议类型和端口必须配对写1配443这种组合不会报错只会一直超时。密码里有特殊字符时先确认没被前端转义抓包看到的是乱码就是这一层的问题。I_InsertOBJECTPlugin的入参是容器 id注意它是挂载不是创建重复调用会把之前的实例覆盖掉多设备场景下调用时机要控制好。3.4 开流与参数对照表登录成功之后才允许开流顺序反了会返回一个语焉不详的错误码。WebVideoCtrl.I_StartRealPlay(192.168.1.64, { iStreamType: 2, // 1主码流 2子码流 3第三码流 iChannelID: 1, // 通道号从 1 开始不是从 0 bZeroChannel: false, // 零通道多画面拼接流只有部分设备支持 success: function () { console.log(开流指令已下发注意此时还没有首帧) }, error: function (status) { console.error(开流失败, status) } })参数里最容易翻车的是iStreamType和iChannelID。参数取值说明踩坑点iStreamType1 / 2 / 3主/子/第三码流主码流 1080P 以上多路必卡iChannelID1 起通道号从 0 开始会返回通道不存在bZeroChanneltrue/false零通道拼接设备不支持时直接失败端口80 / 443 / 自定义与协议类型配对端口改了但协议没改必超时sSuccess回调只代表指令下发成功不代表画面出来了。真正的首帧要等播放事件这一点在第 4 章的事件体系里展开。3.5 点亮之后先确认一件事解码走的是哪条路画面出来先别急着庆祝打开开发者工具看两处。一看 Network 面板里decoder.wasm有没有被真正拉下来状态码是不是 200。如果显示(from disk cache)但你刚替换过解码库那就是缓存没清干净版本对不上。二看 Performance 面板录五秒看 CPU 曲线。软解的特征是有一个核跑满、曲线呈锯齿状如果 CPU 很平稳而画面流畅可能是设备侧直接推了浏览器能硬解的格式但这种情况在 H.265 上很少见。注意H.265 通道在部分浏览器上会走兼容路径表现为首帧很慢35 秒但之后流畅。这是解码器初始化耗时不是网络问题别去调设备参数。4. 播放控制与事件体系把能看变成能用一路能亮只是及格线。真实项目里要求分屏、要求断线自愈、要求能抓图能回放这些都要靠事件体系撑起来。4.1 分屏布局与多实例一页挂 4 路不走样分屏不是开四个播放器实例而是把播放器设成 4 窗口模式然后按窗口索引分别开流。// 切到 4 分屏必须在开流之前设置 WebVideoCtrl.I_InitPlugin({ bWndFull: true, iWndowType: 4, // 4 窗口 cbSelWnd: function (data) { currentWnd data.index // 记住当前选中的窗口后续抓图/录像作用于它 } }) // 依次给 1~4 号窗口开流同一台设备的不同通道 const chs [1, 2, 3, 4] chs.forEach(function (ch, i) { WebVideoCtrl.I_StartRealPlay(192.168.1.64, { iStreamType: 2, // 多路场景一律先上子码流 iChannelID: ch, iWndIndex: i, // 窗口索引从 0 开始 success: function () {}, error: function (s) { console.warn(窗口 i 开流失败, s) } }) })注意iWndIndex是从 0 开始而iChannelID是从 1 开始这两个下标基准不一致是抄代码时最常见的错误来源。多设备场景下不要反复调I_InsertOBJECTPlugin一个页面一个播放器实例不同设备靠 IP 区分同一个窗口切设备时先I_StopRealPlay再重新开流。四路以上还想流畅必须满足两个条件全部走子码流一般 720P、12Mbps以及单机不要超过 9 路。我实测过一台普通办公本4 路 1080P 主码流软解CPU 直接跑到 90% 以上风扇声音盖过会议室空调。4.2 事件回调断流、错误码与重连事件是这套方案里文档最薄、坑最深的部分。核心要盯三类播放状态变化、异常断开、窗口切换。// 播放状态回调只有这里触发才代表真正出画面 WebVideoCtrl.I_SetPlayStateCallback(function (state) { // state 常见值播放中 / 暂停 / 停止 / 缓冲 console.log(播放状态变化, state) if (state playing) { firstFrameTime Date.now() // 记录首帧时间用于监控开流耗时 } }) // 异常回调网络断开、会话失效都会走这里 WebVideoCtrl.I_SetExceptionCallback(function (type, msg) { console.warn(异常, type, msg) if (type 0) { // 连接类异常常见做法是延迟重连 setTimeout(reconnectAll, 3000) } })WebSocket 层的关闭码可以拿来辅助判断1000是正常关闭1006是链路异常断开多半是网络或设备重启1008通常是策略拒绝并发数超限或者鉴权过期。看到1008就别傻等重连了先把会话重新登一遍。重连一定要做退避别用固定 1 秒死循环。我一般写成 3 秒、6 秒、12 秒、封顶 30 秒重连超过 10 次就弹个提示让用户手动刷新避免在设备已经挂掉的情况下把浏览器拖死。还有一个隐蔽问题浏览器切到后台标签页之后定时器和 requestAnimationFrame 会被节流WebSocket 的接收缓冲会越堆越多切回来时要么延迟暴涨要么直接卡死。处理办法是监听visibilitychange切走的时候主动停流切回来重开。4.3 抓图、本地录像与回放的时间参数抓图返回的是 base64浏览器不能直接写磁盘得自己触发下载。WebVideoCtrl.I_GetSnapshot(192.168.1.64, { iChannelID: 1, iWndIndex: 0, success: function (data) { if (!data || !data.base64) return // 首帧之前调用会返回空 const a document.createElement(a) a.href data:image/jpeg;base64, data.base64 a.download snap_ Date.now() .jpg a.click() } })抓图失败最常见的原因是调用时机在首帧之前其次是分辨率和当前窗口不一致导致拿到空白图。稳妥做法是等playing状态回调之后再允许点击抓图按钮。回放走的是另一套接口时间格式是 ISO 风格字符串不带时区WebVideoCtrl.I_StartPlayback(192.168.1.64, { iChannelID: 1, startTime: 2024-05-01T08:00:00, endTime: 2024-05-01T09:00:00, iStreamType: 1, success: function () {}, error: function (s) { console.error(回放失败, s) } })时间区间不要跨天跨天的请求有些设备会返回空流。做法是按天拆段前端用时间轴控件切。回放和实时预览不能同时占用同一个窗口切换前先停流否则会出现音画不同步这种很难查的现象。4.4 封一层把初始化、登录、开流收成一个方法业务代码里到处散着回调维护起来很痛苦。我在项目里一般包一层 Promise 化的壳// 简单封装登录 开流串行化失败可 catch async function openChannel(ip, port, user, pwd, ch, streamType) { await new Promise((resolve, reject) { WebVideoCtrl.I_Login(ip, 1, port, user, pwd, { success: resolve, error: (s, xml) reject(new Error(登录失败 s)) }) }) await new Promise((resolve, reject) { WebVideoCtrl.I_StartRealPlay(ip, { iStreamType: streamType, iChannelID: ch, success: resolve, error: (s) reject(new Error(开流失败 s)) }) }) }封装之后重试逻辑、日志埋点、错误上报都能收敛到一处比在每个页面里抄一遍回调干净得多。5. 避坑与排查五个反复出现的现场问题下面这几条是同一类问题反复出现后攒下来的按现象 → 原因 → 解决记遇到相似症状可以直接对号入座。5.1 五个高频故障的定位路径现象一页面白的接口调用了但控制台没反应。原因多半是加载顺序错了或者主 JS 被二次压缩把内部符号改名了。解决把压缩插件对webs/目录的排除规则加上确认引入顺序是 jquery → 插件依赖 → 主库 → 业务脚本。用window.WebVideoCtrl在控制台探一下是 undefined 就说明主库根本没加载成功。现象二登录回调成功画面一直转圈。先看协议。页面是 https 而设备只提供 ws:// 时浏览器会以混合内容为由直接掐掉连接且很多版本不在控制台明显报错。解决要么页面降到 http内网可接受要么给设备配好证书走 wss要么在本地做一层同源反向代理把 ws 转出去。第二种最省客户端改动但证书维护成本在服务端。现象三四路以上卡顿、CPU 打满、风扇狂转。原因就是软解在跑主码流。1024×768 的子码流两路跟 1920×1080 主码流一路的 CPU 开销差不多。解决多路场景全部切子码流单页并发上限设成 9在设置里提供画质档位让用户自己选超过 9 路就上服务端转封装。现象四切到别的标签页再回来画面卡住或者延迟好几秒。浏览器节流导致的缓冲堆积前面提过。解决监听visibilitychangehidden时调I_StopRealPlayvisible时重新开流。代价是切回来有一点重连延迟但比卡死强。现象五抓图返回空或者下载下来一张纯黑图。两种原因一是在首帧之前调用了二是容器尺寸变化后没有重新计算画布渲染区域和抓图区域对不上。解决抓图按钮跟playing状态绑定容器尺寸变化时调一次窗口重排接口包里有对应的 resize 方法看 demo 里的注释别自己改 canvas 的宽高属性。5.2 一份三十秒定位清单手上没有抓包工具的时候按这个顺序看能覆盖八成问题控制台有没有 404特别是.wasm和依赖 jswindow.WebVideoCtrl是不是对象登录回调有没有进 successNetwork 里 WebSocket 那条连接的帧数是不是在涨容器 offsetHeight 是不是 0当前页面协议和取流协议是否一致。第 4 步最关键帧数在涨而画面黑问题一定在解码或渲染帧数不涨问题在网络或设备侧往下翻没意义。6. 进阶码流、性能与部署边界怎么摸前面都是把画面点亮的活真正决定项目能不能交付的是性能账和部署边界。先算性能。下表是同一台办公本四核低压 U上实测的开销子码流按 1280×720、2Mbps 算路数码流类型分辨率平均 CPU内存占用主观感受1主码流1920×108025%35%300MB流畅4子码流1280×72045%60%700MB流畅4主码流1920×108080%95%1.1GB掉帧明显9子码流1280×72085%100%1.4GB勉强可用这张表能得出两个结论。第一路数和码流类型的组合决定上限而不是单看路数第二9 路以内是这套方案的合理区间再往上必须走服务端。再说部署。最常见的翻车点是 https 页面配 http 设备前面提过。另一个是跨域静态资源和设备不同源时抓图和部分控制接口会被浏览器拦。稳妥的部署结构是把页面、静态资源和反向代理放在同一台服务器上由代理转发到设备这样前端看到的永远是同源证书只需要维护一层。# 同源反代页面和取流都走同一个域名规避混合内容与跨域 location /device/ { proxy_pass http://192.168.1.64/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; # WebSocket 升级必须透传 proxy_set_header Connection upgrade; proxy_read_timeout 3600s; # 长连接别被默认 60s 掐断 }proxy_read_timeout这一行是坑王。默认 60 秒正好对上很多设备的保活周期表现就是每分钟黑一次日志里还看不到明显错误。最后补一个验证方法上线前用 Chrome 的 Performance 面板录 10 分钟连续播放看内存曲线是不是单调上涨。这套方案在长时间播放后偶有内存不回收的情况涨得慢可以接受涨得快就得在业务层加定时重建实例。从那以后我每次交付这类页面都会强制跑一遍十分钟压测把内存曲线和 CPU 曲线截图存档出了问题至少有对比基准。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑