海康web视频插件h5网页账号密码预览监控

35 阅读4分钟
前言

简书应该不适合记录技术类文章换juej了

早前发现,有些记录涉及小程序带有微信,海康类似文案,疑似偏黑产风险词汇会概率被锁定,但实际是正常bug类记录,申诉过于浪费时间换个平台了,抓马吸眼球的奶头乐反门户大开 应该是情感类博文风华薛岳聚集地。

image.png web视频插件,是指windos下安装海康官网的exe在网页中通过中间js,实现网页上预览监控画面的形式。 这是iframe接入任意网页账号密码接入的形式 优点: 1 不需要后台参与,指转wss流协议在网页播放,官网对照是h5视频播放器开发包 2 只要有超脑账号密码ip 缺点: 1 浏览器出现多窗口都有监控画面显示时,会快速切换时,重复启用海康插件窗口,出现多个画面叠加 2 关闭a页面打开b页面,可能虽然关闭了a但未及时释放监控画面导致b页上出现了a页的监控画面。 open.hikvision.com/download/5c… image.png

本质是后台有个exe播放器,海康通过调整在电脑屏幕上的位置实现视觉上的引入网页,显示的不是video而是实打实的exe软件

<!DOCTYPE html>
<html>
  <head>
    <title>海康威视自动监控</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style>
      body,
      html {
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100%;
        overflow: hidden;
      }
      #divPlugin {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <div id="divPlugin"></div>
    <script src="../../../public/static/lib/jquery-3.3.1.min.js"></script>
    <!--  海康账号登录视频插件 -->
    <script src="../../../public/static/js/webVideoCtrl.js"></script>
    <script>
      function getQueryStringParam(paramName) {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get(paramName);
      }

      const CONFIG = {
        IP: getQueryStringParam("ip"),
        PORT: "80",
        PROTOCOL: 1,
        USER: getQueryStringParam("user"),
        PASSWORD: getQueryStringParam("pwd"),
        RTSP_PORT: "554",
        iWndowType: getQueryStringParam("split") * 1, // 必须数字
        // 1 单窗口;
        // 2 4 宫格,即 2×2 布局;
        // 3 6 宫格;
        // 4 8 宫格;
        // 5 9 宫格,即 3×3 布局;
        // 6 16 宫格。
        channels: getQueryStringParam("channel")
          ? getQueryStringParam("channel").split(",")
          : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
        STREAM_TYPE: 2,
      };

      let webVideoCtrl = null;
      let isPreviewing = false;
      let controller = null;
      let initInProgress = false; // 跟踪初始化状态

      class HikvisionController {
        constructor() {
          this.deviceId = `${CONFIG.IP}_${CONFIG.PORT}`;
          this.init();
        }

        async init() {
          if (initInProgress) return; // 防止重复初始化
          initInProgress = true;

          try {
            closeHk();

            await this.initPlugin();
            await this.loginDevice();
            await this.setupDisplay();
            await this.startPreviewAllChannels();
          } catch (error) {
            console.error(1, error);
          } finally {
            initInProgress = false;
          }
        }

        async initPlugin() {
          return new Promise((resolve, reject) => {
            if (!window.WebVideoCtrl) {
              reject(new Error("WebVideoCtrl插件未加载"));
              return;
            }

            WebVideoCtrl.I_InitPlugin({
              bWndFull: true,
              cbInitPluginComplete: () => {
                console.log("插件初始化完成");
                if (document.getElementById("divPlugin")) {
                  WebVideoCtrl.I_InsertOBJECTPlugin("divPlugin")
                    .then(() => {
                      // 调整插件位置
                      this.adjustPluginPosition();
                      resolve();
                    })
                    .catch((err) =>
                      reject(new Error(`插件插入失败: ${err.message}`))
                    );
                } else {
                  reject(new Error("divPlugin容器不存在"));
                }
              },
            });
          });
        }

        adjustPluginPosition() {
          // 获取iframe的DOM元素
          const iframe = window.frameElement;
          console.log("获取iframe的DOM元素");
          console.log(iframe);
          if (iframe) {
            // 获取iframe相对于窗口的偏移量
            console.log("获取iframe相对于窗口的偏移量");
            const iframeRect = iframe.getBoundingClientRect();
            const iframeOffsetTop = iframeRect.top;
            const iframeOffsetLeft = iframeRect.left;

            // 获取视频插件元素
            const pluginElement = document.getElementById("divPlugin");

            // 设置插件的定位方式
            pluginElement.style.position = "absolute";
            pluginElement.style.top = iframeOffsetTop + "px";
            pluginElement.style.left = iframeOffsetLeft + "px";

            // 调整插件大小
            if (WebVideoCtrl) {
              WebVideoCtrl.I_Resize(
                pluginElement.offsetWidth,
                pluginElement.offsetHeight
              );
            }
          }
        }

        loginDevice() {
          return new Promise((resolve, reject) => {
            if (!window.WebVideoCtrl || !WebVideoCtrl.I_Login) {
              reject(new Error("WebVideoCtrl.I_Login方法未定义"));
              return;
            }

            WebVideoCtrl.I_Login(
              CONFIG.IP,
              CONFIG.PROTOCOL,
              CONFIG.PORT,
              CONFIG.USER,
              CONFIG.PASSWORD,
              {
                success: (xmlDoc) => {
                  if ($(xmlDoc).find("statusString").text() === "OK") {
                    resolve();
                  } else {
                    reject(new Error("登录状态异常"));
                  }
                },
                error: (err) =>
                  reject(
                    new Error(`登录失败: ${err.errorCode} ${err.errorMsg}`)
                  ),
              }
            );
          });
        }

        setupDisplay() {
          return new Promise((resolve) => {
            // 设置为指定窗口布局
            if (WebVideoCtrl && WebVideoCtrl.I_ChangeWndNum) {
              WebVideoCtrl.I_ChangeWndNum(CONFIG.iWndowType);
            }
            if (WebVideoCtrl && WebVideoCtrl.I_Resize) {
              WebVideoCtrl.I_Resize(window.innerWidth, window.innerHeight);
            }
            window.addEventListener("resize", () => {
              if (WebVideoCtrl && WebVideoCtrl.I_Resize) {
                WebVideoCtrl.I_Resize(window.innerWidth, window.innerHeight);
              }
            });
            resolve();
          });
        }

        async startPreviewAllChannels() {
          if (isPreviewing) return;
          isPreviewing = true;

          try {
            // 强制停止所有预览
            if (WebVideoCtrl.I_StopRealPlayAll) {
              WebVideoCtrl.I_StopRealPlayAll(this.deviceId);
            }

            const previewPromises = CONFIG.channels.map((channel, index) =>
              this.playChannel(channel, index)
            );

            await Promise.allSettled(previewPromises);
          } catch (error) {
            console.error("预览启动失败:", error);
          } finally {
            isPreviewing = false;
          }
        }

        playChannel(iChannelID, wndIndex) {
          return new Promise((resolve, reject) => {
            if (!window.WebVideoCtrl || !WebVideoCtrl.I_StartRealPlay) {
              reject(new Error("WebVideoCtrl.I_StartRealPlay方法未定义"));
              return;
            }

            WebVideoCtrl.I_StartRealPlay(this.deviceId, {
              iWndIndex: wndIndex,
              iChannelID: iChannelID,
              iStreamType: CONFIG.STREAM_TYPE,
              bZeroChannel: false,
              iPort: CONFIG.RTSP_PORT,
              success: () => resolve(),
              error: (err) => reject(new Error(err.errorMsg)),
            });
          });
        }
      }

      function closeHk() {
        if (window.WebVideoCtrl.I_StopAllPlay) {
          window.WebVideoCtrl.I_DestroyPlugin();
          window.WebVideoCtrl.I_StopAllPlay();
        } else {
          console.log(0, window.WebVideoCtrl, "未检测到已有控件");
        }
      }

      document.addEventListener("DOMContentLoaded", () => {
        // 加载时关闭
        closeHk();
        // 确保DOM完全加载后再释放旧资源
        // setTimeout(() => {
        controller = new HikvisionController();
        // }, 100); // 延迟初始化确保DOM稳定
      });

      window.addEventListener("beforeunload", () => {
        closeHk();
      });

      // 监听iframe卸载
      window.addEventListener("unload", () => {
        closeHk();
      });
    </script>
  </body>
</html>

使用方式

https://xx.com/index.html?ip=192.168.0.2&user=admin&pwd=xx&split=2&channel=3,2,6,7

超脑的ip账号密码 注意下载官网插件后的js,要两个放入对应,js是动态加载的会自行加载另外一个虽然html未引入 image.png

vue版未验证: juejin.cn/post/733393…

推荐个另外的vue版,同样public下index.html需要引入jq和官方的webVideo.js

<template>
  <div ref="divPlugin" id="divPlugin" class="plugin-container"></div>
</template>

<script>
export default {
  name: 'HikvisionMonitor',
  props: {
    config: {
      type: Object,
      required: true,
      default: () => ({})
    }
  },
  data() {
    return {
      webVideoCtrl: null,
      isPreviewing: false,
      controller: null,
      initInProgress: false,
      deviceId: '',
      channels: []
    }
  },
  watch: {
    config: {
      handler(newVal) {
        // this.handleConfigChange(newVal)
      },
      deep: true
    }
  },
  mounted() {
    // 初始化插件容器
    this.initPlugin()
  },
  beforeDestroy() {
    // 组件销毁前清理插件资源
    console.log('组件销毁前清理插件资源')
    this.closeHk()
  },
  methods: {
    // 获取查询参数(此处可根据实际需求调整)
    getQueryStringParam(paramName) {
      const urlParams = new URLSearchParams(window.location.search)
      return urlParams.get(paramName)
    },
    handleConfigChange(newConfig) {
      // 当配置改变时,重新初始化或更新预览
      this.closeHk() // 先关闭当前预览
      this.initPlugin() // 重新初始化
    },
    initPlugin() {
      if (this.initInProgress) return
      this.initInProgress = true
      this.deviceId = `${this.config.ip}_${this.config.port || 80}`
      this.channels = this.config.channel || [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // 根据传入配置解析通道

      if (!window.WebVideoCtrl) {
        console.error('WebVideoCtrl 插件未加载')
        this.initInProgress = false
        return
      }

      // 初始化插件
      WebVideoCtrl.I_InitPlugin({
        bWndFull: true,
        cbInitPluginComplete: () => {
          console.log('插件初始化完成')
          this.$nextTick(() => {
            if (this.$refs.divPlugin) {
              // 插入插件到容器中
              WebVideoCtrl.I_InsertOBJECTPlugin('divPlugin')
                .then(() => {
                  this.loginDeviceAndSetupDisplay()
                })
                .catch(err => {
                  console.error(1, err)
                })
            } else {
              console.error('未找到插件容器')
            }
          })
        }
      })
    },
    loginDeviceAndSetupDisplay() {
      // 登录设备并设置显示
      if (window.WebVideoCtrl && WebVideoCtrl.I_Login) {
        WebVideoCtrl.I_Login(
          this.config.ip,
          1, // 假设协议为 1(可根据实际情况调整)
          this.config.port || 80,
          this.config.user,
          this.config.pwd,
          {
            success: xmlDoc => {
              if ($(xmlDoc).find('statusString').text() === 'OK') {
                this.setupDisplay()
              } else {
                console.error('登录状态异常')
              }
            },
            error: err => {
              console.error('登录失败:', err)
            }
          }
        )
      } else {
        console.error('WebVideoCtrl.I_Login 方法未定义')
      }
    },
    setupDisplay() {
      // 设置显示布局
      if (window.WebVideoCtrl && WebVideoCtrl.I_ChangeWndNum) {
        // 切割画面要求数字
        WebVideoCtrl.I_ChangeWndNum(this.config.split || 2)
      }
      //   window.addEventListener('resize', () => {
      //     if (window.WebVideoCtrl && WebVideoCtrl.I_Resize) {
      //       WebVideoCtrl.I_Resize(window.innerWidth, window.innerHeight)
      //     }
      //   })
      this.startPreviewAllChannels() // 启动所有通道预览
    },
    startPreviewAllChannels() {
      if (this.isPreviewing) return
      this.isPreviewing = true

      try {
        if (window.WebVideoCtrl && WebVideoCtrl.I_StopRealPlayAll) {
          WebVideoCtrl.I_StopRealPlayAll(this.deviceId)
        }

        this.channels.forEach((channel, index) => {
          this.playChannel(channel, index)
        })
      } catch (error) {
        console.error('预览启动失败:', error)
      } finally {
        this.isPreviewing = false
      }
    },
    playChannel(iChannelID, wndIndex) {
      if (!window.WebVideoCtrl || !WebVideoCtrl.I_StartRealPlay) {
        console.error('WebVideoCtrl.I_StartRealPlay 方法未定义')
        return
      }

      WebVideoCtrl.I_StartRealPlay(this.deviceId, {
        iWndIndex: wndIndex,
        iChannelID: parseInt(iChannelID), // 确保通道 ID 为数字类型
        iStreamType: 2, // 假设流类型为 2(可根据实际情况调整)
        bZeroChannel: false,
        iPort: 554, // RTSP 端口(可根据实际情况调整)
        success: () => {
          console.log(`通道 ${iChannelID} 预览启动成功`)
        },
        error: err => {
          console.error(`通道 ${iChannelID} 预览启动失败:`, err)
        }
      })
    },
    closeHk() {
      if (window.WebVideoCtrl) {
        console.log('关闭预览')
        window.WebVideoCtrl.I_StopAllPlay()
        console.log('销毁插件')
        window.WebVideoCtrl.I_DestroyPlugin()
        window.WebVideoCtrl.I_Logout(this.deviceId)
      }
    }
  }
}
</script>

<style scoped>
.plugin-container {
  width: 100%;
  height: 100%;
}
</style>