npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hzab/hk-video

v0.0.3

Published

海康 Web 视频插件(VideoWebPlugin **V1.5.2**)的函数式 React 封装,配合 TypeScript 全量类型。

Downloads

27

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 消息(窗口选中 / 播放状态 / 抓图 / 录像 / 全屏 / 布局 / 双击 / 时间轴)按类型分发为独立的 onXxx props

前置条件

  1. 客户端已安装 VideoWebPlugin.exe(V1.5.2 及以上)。安装目录通常为 C:\Program Files (x86)\VideoWebPlugin,安装后系统会注册唤醒协议 VideoWebPlugin://, 并在后台运行 WebControl.exe

  2. 页面已加载 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 不会再报"未定义"。

  3. 平台信息(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()),
});

⚠️ 关于全局单例的「配置污染」

configureHikVideoPlayer() 写入的是模块级单例(整个页面生命周期内唯一),且默认按浅合并写入—— 如果应用中会在不同时机 / 不同地方多次调用(比如动态切换平台、多租户场景),未在本次调用中传入的字段会 保留上一次调用的残留值,可能出现"新平台的 appKey 混用旧平台的 secret/ip"这种隐性串号问题,且默认不会 有任何提示。

排查/规避建议:

  1. 开发环境下已自带告警:字段被静默覆盖为不同新值时,控制台会打印 [HikVideoPlayer] ... 被静默覆盖 的警告,出现请务必确认是否符合预期。

  2. 需要严格隔离时用「完全替换」模式,未传入的字段会被重置,不会残留上一次的值:

    configureHikVideoPlayer(
      { appKey: 'B_KEY', ip: 'B_IP', secret: 'B_SECRET' },
      { merge: false },
    );
  3. 或者在切换平台前显式重置:

    import { resetHikVideoPlayerConfig, configureHikVideoPlayer } from "@/components/HikVideoPlayer";
    
    resetHikVideoPlayerConfig();
    configureHikVideoPlayer({ appKey: 'B_KEY', ip: 'B_IP', secret: 'B_SECRET' });
  4. 同一页面需要并存多个不同平台实例时,不要依赖全局 configure(),改用 Props 或 Provider 逐实例 / 逐子树显式传入,两者优先级都高于全局配置, 不会受全局残留值影响。

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 字段。

⚠️ Provider 失效场景:独立 React 根节点(地图 Marker / 手动 ReactDOM.render / 微前端子应用等)

HikVideoPlayerProvider 基于 React Context 实现,只能在同一棵 React 组件树内传递。以下场景会导致 <HikVideoPlayer> 实际运行在一棵独立的 React 树里,读不到 Provider 注入的配置(useContext 会拿到 null,进而报"缺少平台信息"):

  • 地图库(高德 / 百度 / Leaflet 等)的 Marker / Overlay 内容是通过 ReactDOM.render() / createRoot().render() 手动挂载到地图提供的 DOM 节点上的(这是最常见的场景);
  • 通过其他方式手动 ReactDOM.render / createRoot 挂载到 document.body 或某个游离 DOM 节点;
  • 微前端(qiankun / single-spa 等)子应用与主应用(Provider 所在应用)是两棵独立的 React 树。

注意:普通的 antd <Modal> / <Drawer> / <Popover> 内部用的是 ReactDOM.createPortal, 不会产生这个问题——Portal 只改变 DOM 挂载位置,不改变 React 组件树位置,Context 能正常穿透。 只有真正"另起一个 React 根"才会失效。

解决方式:这类场景请改用不依赖组件树位置的 全局 configureHikVideoPlayer(), 它写入的是模块级变量,不受渲染树位置限制,与 Provider 可以共存(分别覆盖"主树内"和"独立根节点"两类场景)。


可拖拽 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 裁剪。 一旦页面滚动或窗口变化,插件就会"飘"出容器、甚至覆盖到浏览器边框外。

这一版组件已通过以下机制把这个问题解决:

  1. 尺寸 / 滚动监听:组件内置
  • ResizeObserver 监听容器尺寸变化
  • window 上的 resize + 捕获阶段 scroll(捕获所有可滚动祖先的滚动事件)
  • width / height props 变化时主动校正(固定尺寸模式)
  1. autoFitParent 父容器适配(可选):
  • 开启后容器样式为 width/height: 100%,插件窗口尺寸由 DOM 实测值驱动
  • 父容器尺寸变化时,子元素随之变化,ResizeObserver 自动触发 JS_Resize
  • 要求父容器具有明确的宽高(或 flex 子项等可计算尺寸的布局)
  1. requestAnimationFrame 合帧:同一帧内的多次事件合并为 1 次 syncPluginWindow, 避免高频滚动引起的卡顿
  2. syncPluginWindow 完整流程(每次触发执行):
    ① JS_Resize(w, h)                       —— 对齐位置 & 大小
    ② 完全离开视口?                          —— JS_HideWnd 整体隐藏
    ③ 进入视口?                              —— JS_ShowWnd 恢复显示
    ④ JS_RepairPartWindow(0,0,w+1,h+1)      —— 先整体还原上次的扣除
    ⑤ 计算四方向溢出(left/right/top/bottom)—— JS_CuttingPartWindow 把溢出块挖掉
    这一套是参照海康官方 demo 的 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()。

Q4:明明用 HikVideoPlayerProvider 包了,appKey/secret/ip 也传全了,为什么还是提示"缺少平台信息"? 先确认 <HikVideoPlayer> 是否真的渲染在 Provider 的同一棵 React 树里。最常见的反例是地图 Marker / Overlay: 很多地图库的标记点内容是通过 ReactDOM.render() 手动挂载到地图自己的 DOM 节点上的,这会产生一棵独立于主应用的 React 根节点,Context 无法跨越这种边界传递(这不是 bug,是 React Context 的天然限制)。可以在 HikVideoPlayer.tsx 里临时打印 useHikVideoPlayerContext() 的返回值验证:如果是 null,说明确实没在同一棵树里,请改用 全局 configureHikVideoPlayer() 替代 Provider。