@hzab/hk-video
v0.0.2
Published
海康 Web 视频插件(VideoWebPlugin **V1.5.2**)的函数式 React 封装,配合 TypeScript 全量类型。
Readme
HikVideoPlayer
海康 Web 视频插件(VideoWebPlugin V1.5.2)的函数式 React 封装,配合 TypeScript 全量类型。
特性
- 函数式组件 + Hooks 实现:完全替代旧的类组件
- 完整的 TS 类型:覆盖 SDK 接口、回调消息、Props、Ref,避免裸
any - 常量分层:
FIXED_*(SDK 强制写死,禁止修改)、DEFAULT_*(可被 Props 覆盖) - 位置自适应:自动跟踪容器尺寸 / 页面滚动 / 视口大小变化;支持
autoFitParent撑满父容器(详见 位置自适应) - 命令式 API:通过
ref暴露 20+ 个方法,覆盖预览、回放、抓图、布局、全屏、字符叠加等 - 强类型回调:8 类 SDK 消息(窗口选中 / 播放状态 / 抓图 / 录像 / 全屏 / 布局 / 双击 / 时间轴)按类型分发为独立的
onXxxprops
前置条件
客户端已安装
VideoWebPlugin.exe(V1.5.2 及以上)。安装目录通常为C:\Program Files (x86)\VideoWebPlugin,安装后系统会注册唤醒协议VideoWebPlugin://, 并在后台运行WebControl.exe页面已加载 SDK 资源(推荐放在
public/index.html或入口 HTML):<script src="/jsencrypt.min.js"></script> <!-- RSA 公钥加密 --> <script src="/jsWebControl-1.0.0.min.js"></script> <!-- WebControl 主库 -->加载后会向
window上挂WebControl与JSEncrypt两个全局对象,组件内 通过declare global已声明类型,IDE 不会再报"未定义"。平台信息(
appKey/secret/ip):
- 直接通过 Props 传入(最高优先级)
- 通过
HikVideoPlayerProvider注入子树(推荐用于多处使用同一平台) - 通过
configureHikVideoPlayer()全局配置一次(兼容老项目 bootstrap) - 兜底从 window 读取(默认键路径
projectInfo.videoHk,可改、可关) - 详见下文 平台配置(三层模型)
快速上手
import HikVideoPlayer from "@/components/HikVideoPlayer";
export default function Demo() {
return (
<HikVideoPlayer
cameraCodes={["c633ef048fe141e1ac6dbeb36aaf21d3"]}
mode="preview"
layout="1x1"
width={800}
height={450}
/>
);
}
cameraCodes数组下标 +1 即对应播放窗口序号(wndId),所以传 4 个 code +layout="2x2"就能填满四宫格。
Props 详解
基本播放
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| cameraCodes | string[] | [] | 监控点编号列表,按下标自动分配窗口 |
| mode | "preview" \| "playback" | "preview" | 播放模式 |
| layout | HikLayout | "1x1" | 画面布局,支持 1x1 ~ 5x5 / 1+5 / 4+9 等 |
| autoFitParent | boolean | false | 是否撑满父容器并随父容器尺寸自动校正;开启后 width/height 可省略 |
| width / height | number | — | 容器尺寸(单位 px);autoFitParent=false 时必填 |
| style / className | — | — | 标准 React 样式入口 |
平台信息
这里只列 Props 入口,完整解析顺序见 平台配置(三层模型)。
| 属性 | 默认值 | 说明 |
|---|---|---|
| appKey / secret / ip | 由三层配置合并 | 平台凭证 |
| getSecret | — | 异步 / 同步获取 secret 的函数,优先于 secret,适合走后端代理 |
| port | 443 | HTTPS 端口 |
| enableHTTPS | 1 | 目前仅支持 HTTPS |
取流默认值(可被运行时 ref 调用覆盖)
| 属性 | 默认值 | 说明 |
|---|---|---|
| defaultStreamMode | 0 | 0-主码流 / 1-子码流 |
| defaultTransMode | 1 | 0-UDP / 1-TCP |
| defaultGpuMode | 0 | 0-关闭 / 1-启用 GPU 硬解 |
| defaultRecordLocation | 0 | 0-中心存储 / 1-设备存储 |
回放专用
| 属性 | 说明 |
|---|---|
| startTime / endTime | 支持 Date / 毫秒时间戳 / "yyyy-MM-dd HH:mm:ss" 字符串 |
UI
| 属性 | 默认值 | 说明 |
|---|---|---|
| showToolbar | 1 | 工具栏开关 |
| showSmart | 0 | 智能信息(移动侦测线框等) |
| buttonIDs | "" | 上 / 下工具条按钮 ID,逗号分隔;空串=全部默认 |
| toolBarButtonIDs | "" | 工具栏按钮 ID,逗号分隔 |
| snapDir | "D:\\SnapDir" | 抓图保存路径 |
| videoDir | "D:\\VideoDir" | 紧急录像 / 剪辑保存路径 |
| language | "zh_CN" | 多语言:zh_CN / en_US |
| levelType | — | 时间轴级别(仅回放) |
服务 / 重连
| 属性 | 默认值 | 说明 |
|---|---|---|
| servicePortStart / servicePortEnd | 15900 / 15909 | WebControl 服务端口范围(建议使用默认值) |
| reconnectTimes | 0 | 0 无限 / >0 次数 / <0 不重连 |
| reconnectDuration | 5 | 重连间隔,秒 |
| maxRetry | 3 | 插件实例化失败的最大重试次数 |
位置跟踪
| 属性 | 默认值 | 说明 |
|---|---|---|
| trackPosition | false | 开启后会用 rAF 监测容器位置变化,自动同步插件窗口;用于可拖拽 Modal / 浮窗等场景,详见 可拖拽 Modal 接入 |
事件回调
| 属性 | payload | 说明 |
|---|---|---|
| onReady | — | 插件创建 + 初始化完成 |
| onError | string | 初始化 / 启动失败 |
| onDisconnect | (normal: boolean) | 服务断开(true 正常 / false 异常) |
| onWindowSelected | HikWindowSelectedMsg | 窗口选中(type=1) |
| onPlayStatus | HikPlayStatusMsg | 预览/回放状态(type=2) |
| onSnapshot | HikSnapshotMsg | 抓图结果(type=3) |
| onRecord | HikRecordMsg | 紧急录像 / 剪辑结果(type=4) |
| onFullScreenChange | (isFullScreen: boolean) | 全屏变化(type=5) |
| onLayoutChange | HikLayoutChangeMsg | 布局切换(type=6) |
| onWindowDblClick | HikWindowDblClickMsg | 双击事件(type=7) |
| onTimeBarLevelChange | HikTimeBarLevelMsg | 时间轴级别变化(type=8,仅回放) |
Ref 命令式 API
import { useRef } from "react";
import HikVideoPlayer, { type HikVideoPlayerRef } from "@/components/HikVideoPlayer";
const ref = useRef<HikVideoPlayerRef>(null);
ref.current?.snapShot(); // 抓图
ref.current?.setFullScreen(); // 进入全屏
ref.current?.setLayout("2x2"); // 切换布局
ref.current?.drawOSD({ text: "温度: 50℃", x: 10, y: 10 });
ref.current?.cutPart(0, 0, 200, 100); // 弹窗弹出前手动扣掉一块
ref.current?.repairPart(0, 0, 200, 100); // 弹窗关闭后还原
ref.current?.destroy(); // 主动释放完整列表:
| 方法 | 说明 |
|---|---|
| startPreview(item) / startMultiPreview(list) | 单点 / 批量预览 |
| startPlayback(item) / startMultiPlayback(list) | 单点 / 批量回放 |
| stopAllPreview() / stopAllPlayback() | 停止全部 |
| stopMultiPlay(wndIds) | 按窗口序号批量停止 |
| setLayout(layout) / getLayout() | 设置 / 获取布局 |
| snapShot(arg?) | 抓图(不传 arg 则按选中窗口、默认路径) |
| drawOSD(arg) | 画面字符叠加 |
| setFullScreen() / exitFullScreen() | 进入 / 退出全屏 |
| setTimeBarLevel(level) | 设置时间轴级别(回放) |
| getVersion() | 获取插件版本号 |
| setAuthInfo(list) | 多平台认证信息 |
| resize(w, h) | 手动 JS_Resize |
| syncPosition() | 立即同步位置 / 大小 / 可见性(手动改变容器后用) |
| hide() / show() | 显隐插件窗口 |
| cutPart(l, t, w, h) / repairPart(l, t, w, h) | 手动扣 / 还原插件窗口区域 |
| destroy() | 主动销毁 |
| getInstance() | 拿到原始 WebControl 实例(兜底,谨慎使用) |
平台配置(三层模型)
为了让本组件能作为通用 npm 包独立分发,平台信息(appKey / secret / ip / port / enableHTTPS)采用四层叠加 + 单字段合并:
Props(最高) > Provider(Context) > 全局 configure() > window 兜底(最低)单字段合并 = 高层为 undefined / null / "" 时回退到下一层,所以混用毫无负担。
1) Props(推荐用于一次性 / 临时覆盖)
<HikVideoPlayer appKey="xxx" secret="yyy" ip="10.0.0.1" port={443} />2) Provider(推荐用于「同一棵树多处共用」)
import { HikVideoPlayerProvider } from "@/components/HikVideoPlayer";
<HikVideoPlayerProvider config={{ appKey, ip, getSecret: () => fetch('/api/hik/secret').then(r => r.text()) }}>
<App />
</HikVideoPlayerProvider>3) 全局 configureHikVideoPlayer()(兼容老项目 bootstrap)
// src/main.ts / bootstrap.ts,应用入口处调用一次
import { configureHikVideoPlayer } from "@/components/HikVideoPlayer";
configureHikVideoPlayer({
appKey: import.meta.env.VITE_HIK_APP_KEY,
ip: import.meta.env.VITE_HIK_IP,
port: 443,
getSecret: () => fetch('/api/hik/secret').then(r => r.text()),
});4) window 兜底(默认开启,可配置可关闭)
为兼容历史项目「把配置挂到 window.projectInfo.videoHk 上」的约定,组件默认开启此兜底:
// 老项目入口仍可以维持原有写法
window.projectInfo = {
videoHk: { appkey: 'xxx', secret: 'yyy', ip: '10.0.0.1' }
};字段命名同时兼容 SDK 风格(小写)和组件风格(驼峰):appkey / appKey 都能识别。
自定义键路径 或 完全关闭 兜底:
// 改用其他键路径
configureHikVideoPlayer({ windowKey: 'myApp.hik' });
// 或彻底关闭
configureHikVideoPlayer({ windowKey: null });关于 secret 安全
直接把 secret 明文挂在 window 或 props 上,可能被第三方脚本读取。生产推荐使用 getSecret 走后端代理:
<HikVideoPlayer
appKey="xxx"
ip="10.0.0.1"
getSecret={async () => (await fetch('/api/hik/secret')).text()}
/>存在 getSecret 时会忽略 secret 字段。
可拖拽 Modal / 浮窗接入
由于海康插件窗口是
WebControl.exe在浏览器之上绘制的顶层 OS 窗口:
- 它不受浏览器 z-index 影响(永远置顶)
- 它的位置同步是异步 IPC,快速拖动时会有 1~2 帧拖影
组件已内置「容器尺寸 / 滚动」监听,但 ResizeObserver 不感知 transform 位移——可拖拽 Modal 通常用 transform: translate(x, y) 改位置,所以默认情况下插件窗口不会跟随移动。
方案 A(推荐):trackPosition 自动跟随
直接打开 trackPosition,组件内部会挂一个 rAF 循环,每帧对比容器位置,发现变化立刻同步:
import { Modal } from "antd";
import HikVideoPlayer from "@/components/HikVideoPlayer";
<Modal open={open} maskClosable={false} /* 启用可拖拽逻辑 */>
<HikVideoPlayer
trackPosition // ← 关键:开启位置跟踪
cameraCodes={[code]}
width={800}
height={450}
/>
</Modal>性能开销很小:未变化时只调用一次
getBoundingClientRect,不触发任何 SDK 调用。
方案 B:手动在 onDrag 里同步(不想常驻 rAF)
const playerRef = useRef<HikVideoPlayerRef>(null);
<DraggableModal
onDrag={() => playerRef.current?.syncPosition()}
onDragEnd={() => playerRef.current?.syncPosition()}
>
<HikVideoPlayer ref={playerRef} ... />
</DraggableModal>方案 C:拖动期间 hide / 拖动结束 show(无拖影)
如果不在意拖动过程中的画面,这是体验最一致的做法:
<DraggableModal
onDragStart={() => playerRef.current?.hide()}
onDragEnd={() => {
playerRef.current?.show();
playerRef.current?.syncPosition();
}}
/>关闭动画建议
UI 库的 Modal 关闭带 200ms 缩放动画,插件窗口同步不上时会看到画面「滞留」一下再消失。建议关闭前先调 hide():
const handleClose = () => {
playerRef.current?.hide();
setOpen(false);
};位置自适应
要解决的问题:旧版类组件存在一个长期痛点—— 插件窗口是
WebControl.exe在浏览器之上绘制的顶层窗口,并不是一个<video>标签, 因此既不会跟随 DIV 自动滚动 / 缩放,也不会被父容器的overflow: hidden裁剪。 一旦页面滚动或窗口变化,插件就会"飘"出容器、甚至覆盖到浏览器边框外。
这一版组件已通过以下机制把这个问题解决:
- 尺寸 / 滚动监听:组件内置
ResizeObserver监听容器尺寸变化window上的resize+ 捕获阶段scroll(捕获所有可滚动祖先的滚动事件)width/heightprops 变化时主动校正(固定尺寸模式)
autoFitParent父容器适配(可选):
- 开启后容器样式为
width/height: 100%,插件窗口尺寸由 DOM 实测值驱动 - 父容器尺寸变化时,子元素随之变化,
ResizeObserver自动触发JS_Resize - 要求父容器具有明确的宽高(或 flex 子项等可计算尺寸的布局)
requestAnimationFrame合帧:同一帧内的多次事件合并为 1 次syncPluginWindow, 避免高频滚动引起的卡顿syncPluginWindow完整流程(每次触发执行):
这一套是参照海康官方 demo 的① JS_Resize(w, h) —— 对齐位置 & 大小 ② 完全离开视口? —— JS_HideWnd 整体隐藏 ③ 进入视口? —— JS_ShowWnd 恢复显示 ④ JS_RepairPartWindow(0,0,w+1,h+1) —— 先整体还原上次的扣除 ⑤ 计算四方向溢出(left/right/top/bottom)—— JS_CuttingPartWindow 把溢出块挖掉setWndCover实现,能保证插件画面始终被严格限制在 DIV 的可见区域内。
撑满父容器(autoFitParent)
默认模式下,容器尺寸由 width / height props 固定为像素值;仅当 props 变化时容器才会
跟着变,父容器单独改变尺寸不会触发校正。
若希望播放器随父容器自动伸缩,可开启 autoFitParent:
<div style={{ width: 800, height: 450 }}>
<HikVideoPlayer
autoFitParent
cameraCodes={["cam-code-1"]}
mode="preview"
layout="1x1"
/>
</div>说明:
- 无需传入
width/height,组件会在首次布局完成后用 DOM 实测尺寸创建插件窗口 - 父容器
width/height变化时,插件窗口会自动JS_Resize对齐 - 父容器必须有明确尺寸;若父级高度未设置,
100%高度可能塌缩为 0 - 与
trackPosition可并存:前者管尺寸,后者管 transform / 拖拽位移
弹窗 / 抽屉 / 下拉遮挡场景
插件窗口始终置顶,任何 Antd 的 Modal / Drawer / Popover 都盖不住。
组件已暴露 cutPart / repairPart,由调用方在弹层打开前主动挖掉、关闭后还原:
const playerRef = useRef<HikVideoPlayerRef>(null);
<Modal
open={open}
afterOpenChange={(visible) => {
if (visible) {
// 假设 Modal 居中宽 600 高 400
playerRef.current?.cutPart(100, 50, 600, 400);
} else {
playerRef.current?.repairPart(100, 50, 600, 400);
}
}}
/>如果不在乎过渡体验,最简单的做法是弹窗打开前
playerRef.current?.hide()、关闭后show()。
常量配置(constants.ts)
| 分类 | 命名 | 说明 |
|---|---|---|
| 固定参数 FIXED_* | FIXED_SERVICE_TYPE | 固定 "window",禁止改 |
| | FIXED_DLL_PATH | 固定 "./VideoPluginConnect.dll" |
| | FIXED_ACTIVEX_CLSID | IE10 ActiveX 的 clsid |
| | FIXED_WAKEUP_PROTOCOL | "VideoWebPlugin://" |
| | FIXED_RSA_KEY_LENGTH | 1024 |
| | FIXED_ENCRYPTED_FIELDS | 只对 secret 加密(多字段加密会显著拖慢初始化) |
| 默认参数 DEFAULT_* | 完整列表见 constants.ts | 全部可被 Props 覆盖 |
从旧组件迁移
- import HikVisionPulgin from "@/components/videoHk";
+ import HikVideoPlayer from "@/components/HikVideoPlayer";
- <HikVisionPulgin
- codes={[code]}
- type="preview"
- width={800}
- height={450}
- showToolbar={0}
- />
+ <HikVideoPlayer
+ cameraCodes={[code]}
+ mode="preview"
+ width={800}
+ height={450}
+ showToolbar={0}
+ />主要 Prop 名变化:
| 旧 | 新 |
|---|---|
| codes | cameraCodes |
| type | mode |
| 无 | 新增大量类型化回调 + Ref 方法 |
演示
见 example/pages/videoDemo/HikVideoPlayerDemo.tsx,已默认挂在路由 /demo 下,
覆盖:
- 单点预览 / 单点回放切换
- 多窗口布局(2x2、3x3)
- Ref 方法(抓图、全屏、布局切换、字符叠加)
- 滚动 / 拖动 demo(验证位置自适应)
常见问题
Q1:页面加载后插件没出现?
- 确认浏览器控制台有无
window.WebControl 未定义警告 - 确认
VideoWebPlugin.exe已安装;可手动在终端执行tasklist | findstr WebControl检查 exe 是否运行 - 检查 Props 中
appKey/secret/ip是否齐全
Q2:浏览器开发者工具里看插件窗口是黑色?
这是预期行为。DIV 仅用作位置 / 尺寸锚点,实际画面由 exe 绘制在浏览器之上,
F12 看到的"黑色"就是这个 DIV 自身的背景色(已通过 backgroundColor: "#000" 设置成黑色作占位)。
Q3:路由跳转回来后插件不显示?
通常是由于上一次 unmount 时 JS_Disconnect 尚未完成。组件已做好兜底:
- unmount 时
JS_HideWnd+JS_Disconnect destroyedRef守卫异步链
若仍有问题,可在路由切换前手动 playerRef.current?.destroy()。
