本文讲海康 无插件版 (
bNoPlugin: true+ jsPlugin / WASM)在网页中的接入方式与注意事项。不绑定具体业务,只聚焦播放器技术链路。适合和《插件版》对照阅读。
无插件版的核心变化:用户不用装 OCX (Windows 上可嵌入网页的 ActiveX 本地控件,插件版依赖它),解码走浏览器侧的 WASM / Worker;设备侧通常需要能走 WebSocket 取流。
一、无插件版是什么
同一套前端门面仍是 WebVideoCtrl,但运行时开关不同:
arduino
I_InitPlugin('100%', '100%', {
bNoPlugin: true, // 关键:走无插件
// ...
})
SDK 会再去加载 jsPlugin、PlayCtrlWasm 等资源,用 Canvas/WebGL + Worker 播 H.264 / H.265。
| 对比项 | 插件版 | 无插件版 |
|---|---|---|
| 安装 | 必需本地插件 | 不需要 |
| 能力检测 | I_CheckPluginInstall |
I_SupportNoPlugin |
| 销毁 | I_DestroyPlugin |
I_DestroyWorker |
| 取流 | 本地插件通道 | 常见为 WebSocket(可经 Nginx 代理) |
| 浏览器 | 历史方案偏 IE/定制壳 | 建议 Chrome 91+ / Chromium |
二、静态资源与加载
典型目录(示意):
arduino
public/static/hcWebSdk/
webVideoCtrl.js
jsPlugin/
jsPlugin-3.0.0.min.js
playctrl/PlayCtrlWasm/...
transform/...
encryption/
index.html 提前注入 jQuery、加密脚本、webVideoCtrl.js。业务启动时:
javascript
async function ensureNoPluginReady() {
const ok = await waitForWebVideoCtrl()
if (!ok) return false
// SDK 提供的能力探测
if (!WebVideoCtrl.I_SupportNoPlugin()) {
console.warn('请使用 Chrome 91+(Chromium),并确认设备支持 WebSocket 取流')
return false
}
return true
}
三、标准流程:Init → Insert → Login → 预览 / 回放
scss
I_SupportNoPlugin() === true
↓
I_InitPlugin(..., { bNoPlugin: true, cbInitPluginComplete })
↓
I_InsertOBJECTPlugin(containerId) // 返回 -1 失败
↓
延时 I_Resize(w, h) // 避免 0 高度黑屏
↓
I_Login(ip, protocol, port, user, pass)
↓
I_GetDevicePort → 检查 WebSocket 端口 !== -1
↓
I_StartRealPlay / I_RecordSearch + I_StartPlayback
↓
离开:I_StopAll → I_DestroyWorker → I_Logout
1. 初始化(务必 bNoPlugin: true)
typescript
function initInContainer(containerId: string) {
return new Promise<void>((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('无插件初始化超时')), 20000)
WebVideoCtrl.I_InitPlugin('100%', '100%', {
bWndFull: true,
iPackageType: 2,
bNoPlugin: true,
iWndowType: 1, // 1×1;2→2×2;3→3×3
cbInitPluginComplete() {
clearTimeout(timer)
const ret = WebVideoCtrl.I_InsertOBJECTPlugin(containerId)
if (ret === -1) {
reject(new Error('I_InsertOBJECTPlugin 失败'))
return
}
resolve()
},
cbPluginErrorHandler(wndIndex: number, errorCode: number) {
console.warn('播放错误', wndIndex, errorCode)
// 出错后通常 Stop 当前窗口,避免残窗
},
cbPerformanceLack() {
console.warn('性能不足')
},
cbSecretKeyError(wndIndex: number) {
console.warn('码流加密密钥错误', wndIndex)
},
// proxyAddress: { ip, port } // 慎用,见下文「代理」一节
})
})
}
布局就绪后再:
scss
// 侧栏动画、flex 定高之后再调,可用 200--300ms 或 ResizeObserver
WebVideoCtrl.I_Resize(container.clientWidth, container.clientHeight)
WebVideoCtrl.I_ChangeWndNum(1) // 1/2/3 → 1/4/9 分屏
2. 登录
typescript
const iProtocol = location.protocol === 'https:' ? 2 : 1
const deviceIdentify = `${ip}_${port}`
const ret = WebVideoCtrl.I_Login(ip, iProtocol, port, username, password, {
success() {},
error(err: { errorCode?: number }) {
if (err?.errorCode === 2001) {
// 已登录:切页未 Logout 时很常见,可按成功处理
return
}
console.error('登录失败', err)
},
})
if (ret === -1) {
// 同步已登录
}
3. 确认设备支持无插件取流
javascript
const ports = WebVideoCtrl.I_GetDevicePort(deviceIdentify)
// 关注 iWebSocketPort / iWebSocketsPort
// 约定:为 -1 时设备未开启或对不齐无插件 WebSocket 取流
if (ports.iWebSocketPort === -1) {
throw new Error('设备不支持 WebSocket 取流,无插件无法出画')
}
可选:I_GetAnalogChannelInfo / I_GetDigitalChannelInfo 拉通道列表(返回多为 XML,需用 jQuery 等解析)。
4. 实时预览
javascript
const iWndIndex = 0
const status = WebVideoCtrl.I_GetWindowStatus(iWndIndex)
if (status) {
WebVideoCtrl.I_Stop({ iWndIndex })
}
WebVideoCtrl.I_StartRealPlay(deviceIdentify, {
iWndIndex,
iStreamType: 1,
iChannelID: channelId,
bZeroChannel: false,
bProxy: true, // 是否经网关/WebSocket 代理取流
iRtspPort: rtspPort, // I_GetDevicePort 拿到且 > 0 时可传
success() {},
error(status) {
// 403:常与鉴权 / 未开通 WS / 代理策略有关
},
})
5. 回放
javascript
WebVideoCtrl.I_RecordSearch(deviceIdentify, channelId, start, end, {
iStreamType: 1,
iSearchPos: 0,
success(xmlDoc) {
// OK | MORE | NO MATCHES
// MORE → 增大 iSearchPos 继续搜
},
})
WebVideoCtrl.I_StartPlayback(deviceIdentify, {
iStreamType: 1,
iChannelID: channelId,
szStartTime: '2026-07-15 10:00:00',
szEndTime: '2026-07-15 11:00:00',
bProxy: true,
success() {},
error() {},
})
// 控制:I_Pause / I_Resume / I_Stop
// 倍速:I_PlaySlow / I_PlayFast
// 进度:I_GetOSDTime
// 拖动进度常用「Stop + 新 startTime 再 StartPlayback」
6. 云台
与插件版同一套 I_PTZControl:先 bStop=false,成功后短延时再 bStop=true;速度通常 1--7。
7. 销毁(和无插件生命周期强相关)
csharp
async function destroyNoPlugin() {
if (typeof WebVideoCtrl.I_StopAll === 'function') {
await WebVideoCtrl.I_StopAll() // 无插件里常返回 Promise
}
if (typeof WebVideoCtrl.I_DestroyWorker === 'function') {
WebVideoCtrl.I_DestroyWorker() // 释放 WASM Worker,否则下次 Init 易失败
}
await WebVideoCtrl.I_Logout(deviceIdentify)
}
仅切换通道、子组件卸载、又要保留设备登录时:可以 只 StopAll + DestroyWorker,不 Logout;整页离开则建议连 Logout 一起做。
四、代理怎么配:最容易踩的坑
实战里常有两层能力混在一起:
- 取流代理 (预览/回放 WebSocket)→
I_StartRealPlay/I_StartPlayback的bProxy: true - ISAPI 代理 (登录、查通道等 HTTP API)→
I_InitPlugin的proxyAddress
常见错误组合:
Nginx 只 反代了 WebSocket 取流,却把
proxyAddress指到同一个网关。结果:
I_Login的 ISAPI 打到网关 → 404,登录直接挂。
更稳妥的两种方案:
| 方案 | 做法 |
|---|---|
| A. 同源改写 ISAPI | 页面跑在 localhost:3000,把 /ISAPI 代理到设备或业务后端;不要 轻易给 Init 传 proxyAddress。Cookie / 同源策略更顺。 |
| B. 网关同时代理 ISAPI + WS | 只有在网关完整具备 ISAPI 转发时,才给 I_InitPlugin 配 proxyAddress: { ip, port }。 |
bProxy 负责「码流走不走代理」,proxyAddress 负责「ISAPI 打哪」------两者不是一回事,不要绑死成同一个开关。
五、注意事项清单
1. 浏览器基线
不支持时直接提示用户换 Chrome 91+ / Chromium 内核。Edge(Chromium)、国内双核浏览器的「极速模式」一般可用;IE、极旧内核基本可以直接放弃。
2. 设备必须开 WebSocket 取流
I_GetDevicePort 里 WebSocket 端口为 -1 → 无插件出不了画。这是 设备能力/配置问题,不是前端少调一个 API。
3. 编码只认 H.264 / H.265
错误码 1006:编码不支持。前端改不了设备编码时,只能换通道/改设备编码配置。
4. 容器必须是空 DOM + 有尺寸
I_InsertOBJECTPlugin会改写节点,Vue/React 不要往里面塞子树- 父级
display:none/ 高度为 0 时 Init,极易黑屏 - 布局变化后补
I_Resize - 无插件一般 没有 旧的
I_ShowPlugin/I_HidPlugin可依赖,显隐靠停播或重建
5. Worker 一定要释放
切路由、切容器时若只停播不 I_DestroyWorker:
- 下次
I_InitPlugin异常 - 内存和 Worker 堆积
- 「本地点了就绪,其实内核已乱」
换容器前:先 StopAll + DestroyWorker(以及按需 Logout),再对新容器 Init。
6. 登录 2001 / 同步 -1
含义仍是「已登录」。页面卸载时若没 Logout,返回视频页会高频撞上。策略:
- 常态:离页
Logout + DestroyWorker - 容错:2001 /
-1当成功,且本地标识同步为已登录
7. 先停窗口再开流
I_GetWindowStatus 有占用 → I_Stop → 再 I_StartRealPlay / I_StartPlayback。多路分屏切换布局时,先停各窗,再 I_ChangeWndNum。
8. HTTPS / HTTP 一致性
登录 iProtocol 跟页面协议走。站点 HTTPS、设备仅 HTTP、中间又无可靠网关时,会卡在混合内容或证书问题------这通常要运维侧反向代理解决,而不是前端硬切协议。
9. 错误回调要落表
初始化时挂上 cbPluginErrorHandler,把海康 demo 里常见错误码映射成可读文案,客服和排障会轻松很多:
| 错误码 | 含义 |
|---|---|
| 1001 | 码流传输过程异常 |
| 1002 | 回放结束 |
| 1003 | 取流失败,连接被动断开 |
| 1006 | 编码不支持(仅 H264/H265) |
| 1007 | 网络异常导致 WebSocket 断开 |
| 1008 | 首帧回调超时 |
| 1012 | 播放资源不足 |
| 1015 | 获取播放 URL 失败 |
| 1017 | 密码错误 |
| 1019 | 未找到录像片段 |
另外:
cbSecretKeyError:码流加密密钥不对 → 停窗cbPerformanceLack:机器解码吃力 → 降分屏、降码流、少开窗口
预览/回放 HTTP 403 :优先查 WS 是否通、鉴权、代理白名单,而不是反复 Init。
10. 性能与分屏
无插件是浏览器硬解码/软解码在扛。9 宫格 + 主码流很容易触发性能回调。产品上可:
- 默认主码流,弱网改子码流(
iStreamType) - 限制同时窗口数
- 弱机提示降分屏
六、一张生命周期图
scss
┌──────────── 页面进入 ────────────┐
│ wait script → I_SupportNoPlugin │
│ I_InitPlugin(bNoPlugin) │
│ I_InsertOBJECTPlugin + I_Resize │
│ I_Login → 检查 WS 端口 │
│ I_StartRealPlay / Playback │
└─────────────────────────────────┘
│
▼
┌──────────── 页面离开 ────────────┐
│ I_StopAll │
│ I_DestroyWorker │
│ I_Logout(建议) │
└─────────────────────────────────┘
七、和插件版怎么选
| 场景 | 更合适 |
|---|---|
| 客户机不可控、不能装软件 | 无插件 |
| 只要 Chrome 内网工作站 | 无插件(运维成本低) |
| 需要覆盖多操作系统 | 无插件(见下) |
| 设备完全不提供 WS 取流 | 插件版或其它取流方案 |
| 专机房可统一装包、旧固件(且多为 Windows) | 插件版仍可能是权宜之计 |
多操作系统:无插件的明显优势
插件版强依赖本地安装包,实践中多半绑死 Windows ;Mac 包经常缺位,国产系统(如 麒麟)也难有对应 OCX 可装。
无插件版跑在浏览器里(WASM / Worker),只要客户端是支持 I_SupportNoPlugin 的 Chromium 内核浏览器,同一套前端即可覆盖:
- Windows
- macOS
- 麒麟 等国产操作系统(使用自带或预装的 Chromium 系浏览器)
落地时仍建议在目标 OS + 浏览器组合上做一次冒烟:确认 I_SupportNoPlugin() === true、WebSocket 取流畅通、音视频可出画。系统再多,瓶颈通常不在「能不能装插件」,而在浏览器能力与设备取流是否开通。
长期看:无插件 + 网关正确拆分「ISAPI / WebSocket」是 Web 监控页的主流方向,也更适合多 OS 终端并存的现场。
小结
无插件版记住四句话就够落地:
bNoPlugin: true+I_SupportNoPlugin是准入条件- 设备 WebSocket 端口不能是 -1
bProxy管取流,proxyAddress管 ISAPI,别混- 离页必须
StopAll+DestroyWorker(外加建议Logout)
把容器 DOM、尺寸、错误码表和代理拓扑一次设计清楚,比纠结某个按钮回调细节更重要。