当前位置:   article > 正文

海康WEB3.3控件开发包 V3.3 前端vue项目调用实时监控画面

海康web3.3

公司业务迭代, 需要前端vue项目里增加一个查看实时监控模块, 这个需求是之前离职的前端小哥没有研究明白的, 现在落在了我的肩上, 压力还是有的. 但是压力归压力, 问题还是要解决的.

一、调研设备和方案

第一步: 调研大佬们已经实现的方案, 找设备对接. 公司后端大佬提出用官方SDK稍加修改即可, 难度并不大, 那么首先看海康官方平台:

海康开放平台

可以看到有三个版本, 而且网上搜了一圈, 实现过的都是3.0和3.2的, 其中3.0不支持新版浏览器, 3.2对设备又有需求(需要支持websocket), 这个一定要注意; 我手上只有一个最基本的枪机摄像头, 咨询过客服, 也不支持websocket; 于是乎, 只能自己踩坑最新版的3.3.

摸索了几天, 过程比较痛苦, SDK是原生JS和HTML, 移植到vue中, 调用webVideoCtrl.js这个库时有一个报错无法解决, 求助大佬调试了很久, 甚至研究到了js代码模块化, 依然无法解决.

于是转换思路, 决定将不再将代码改造集成到vue中,而是直接部署demo页面,与vue项目相互独立,在vue中跳转到demo页面,传入登录设备所需的四个参数即可(设备的ip,端口号,用户名,密码)。

二、设备对接和后台支持

实现这个需求当然离不开对接设备测试,这里有两种方式,第一个是直接连设备,网线一头插摄像头,一头插自己的电脑,将摄像头的ip设置为和电脑在一个网段中,在浏览器输入摄像头的ip和端口,即可进入海康平台,查看摄像头画面和相关功能;第二个需要后台支持,由后端大佬将某个摄像头映射到公网,在浏览器输入该公网ip和端口即可查看,道理和直连是一样的:

1. 输入摄像头ip和端口,打开海康平台页面,输入设备用户名和密码登录:

2. 登录后的页面:

三、海康WEB3.3控件开发包demo对接摄像头

解压3.3版本的SDK,开发文档、插件安装、nginx配置等, 开发包里都有明确说明,不再赘述,本方案用到的核心是demo文件夹,有以下内容:

打开index.html, 即可看到demo的内容:

输入IP, 端口号, 用户名, 密码, 点击登录, 登录完毕后, 点击开始预览:

这里使用的是后端映射到公网的摄像头,这个当然更方便,而且开发完成后演示需求也可以直接调用公网的画面,测试各按钮功能也是OK的,右侧的信息框都可以展示出操作信息。

四、部署demo页面以及代码实现

我这里前端部署是之前配置好的,是nginx一个总的.conf配置文件, 运行各子系统在conf_d的配置文件, 当然事先也要准备好服务器, 打开端口号进行部署, 我用的是termius, 很方便, 可以直接本地拖拽到服务器.

可能部署页面是由运维或者后端来负责, 我这里都自己搞了.

我们需要修改的是demo.js, demo.html以及对应的demo.css, JS实现自动登录并预览画面, 加入一些信息提示和报错的alert弹窗, 只展示视频插件的画面即可, 改完代码后, 部署demo页面到服务器; 部署完成后, 在原有vue项目设置一个跳转, 并传入参数即可:

1. vue项目中的跳转和传参:

  1. showMonitor() {
  2. // 创建一个包含参数的对象
  3. const params = {
  4. szIP: 'xxxxxxxx',
  5. szPort: 'xxxxxxx',
  6. szUsername: 'admin',
  7. szPassword: 'xxxxxxxx'
  8. }
  9. // 使用URLSearchParams将参数转换为查询字符串
  10. const queryParams = new URLSearchParams(params).toString()
  11. // 将查询字符串添加到外部页面的URL中
  12. const externalURL = `http://xxxxxxx:xxxxx/cn/demo.html?${queryParams}`
  13. // 使用window.open()打开新标签页并跳转到带参数的外部页面
  14. window.open(externalURL, '_blank')
  15. },

2. demo.js相关代码, 我这里在顶部添加了提示信息, 优化用户体验, 在初始化插件, 登录, 获取通道, 打开预览过程中均有不同提示, 否则的话只能看着一个黑屏干等着, 还是比较尴尬的:

  1. // 显示加载提示信息的函数
  2. function showLoadingMessage(message) {
  3. // 创建一个 DOM 元素来显示提示信息
  4. var loadingDiv = document.createElement("div");
  5. loadingDiv.setAttribute("id", "loadingMessage");
  6. loadingDiv.style.position = "fixed";
  7. loadingDiv.style.top = "8%";
  8. loadingDiv.style.left = "50%";
  9. loadingDiv.style.transform = "translate(-50%, -50%)";
  10. loadingDiv.style.backgroundColor = "rgba(0, 0, 0, 0.7)";
  11. loadingDiv.style.color = "white";
  12. loadingDiv.style.padding = "10px";
  13. loadingDiv.style.zIndex = "999";
  14. loadingDiv.style.borderRadius = "5px";
  15. loadingDiv.innerHTML = message;
  16. // 将提示信息添加到页面中
  17. document.body.appendChild(loadingDiv);
  18. }
  19. // 关闭加载提示信息的函数
  20. function hideLoadingMessage() {
  21. var loadingDiv = document.getElementById("loadingMessage");
  22. if (loadingDiv) {
  23. // 从页面中移除提示信息
  24. loadingDiv.parentNode.removeChild(loadingDiv);
  25. }
  26. }
  27. // 定义登录函数
  28. function loginWithParameters() {
  29. // 从URL中获取参数
  30. function getParameterByName(name) {
  31. name = name.replace(/[\[\]]/g, "\\$&");
  32. var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
  33. results = regex.exec(window.location.href);
  34. if (!results) return null;
  35. if (!results[2]) return "";
  36. return decodeURIComponent(results[2].replace(/\+/g, " "));
  37. }
  38. // 获取URL中的参数值
  39. var szIP = getParameterByName("szIP");
  40. var szPort = getParameterByName("szPort");
  41. var szUsername = getParameterByName("szUsername");
  42. var szPassword = getParameterByName("szPassword");
  43. // 替换属性值
  44. $("#loginip").val(szIP);
  45. $("#port").val(szPort);
  46. $("#username").val(szUsername);
  47. $("#password").val(szPassword);
  48. // 调用登录函数,稍后执行以确保页面加载完成
  49. setTimeout(function () {
  50. clickLogin();
  51. }, 1000); // 可以根据需要调整等待时间
  52. }
  53. // 在页面加载完成后自动运行登录函数
  54. $(document).ready(function () {
  55. loginWithParameters();
  56. });
  57. // 登录
  58. function clickLogin() {
  59. var loadingMessage = "正在登录设备...";
  60. showLoadingMessage(loadingMessage);
  61. var urlParams = new URLSearchParams(window.location.search);
  62. // 从URL参数中获取登录信息
  63. var szIP = urlParams.get("szIP");
  64. var szPort = urlParams.get("szPort");
  65. var szUsername = urlParams.get("szUsername");
  66. var szPassword = urlParams.get("szPassword");
  67. if (!szIP || !szPort) {
  68. return;
  69. }
  70. var szDeviceIdentify = szIP + "_" + szPort;
  71. WebVideoCtrl.I_Login(szIP, 1, szPort, szUsername, szPassword, {
  72. timeout: 3000,
  73. success: function (xmlDoc) {
  74. // 登录成功后关闭加载提示信息
  75. hideLoadingMessage();
  76. showOPInfo(szDeviceIdentify + " 登录成功!");
  77. $("#ip").prepend(
  78. "<option value='" +
  79. szDeviceIdentify +
  80. "'>" +
  81. szDeviceIdentify +
  82. "</option>"
  83. );
  84. setTimeout(function () {
  85. $("#ip").val(szDeviceIdentify);
  86. setTimeout(function () {
  87. getChannelInfo();
  88. }, 1000);
  89. getDevicePort();
  90. }, 10);
  91. },
  92. error: function (oError) {
  93. if (ERROR_CODE_LOGIN_REPEATLOGIN === status) {
  94. showOPInfo(szDeviceIdentify + " 已登录过!");
  95. } else {
  96. alert("登录设备失败, 请刷新页面重试");
  97. showOPInfo(
  98. szDeviceIdentify + " 登录失败!",
  99. oError.errorCode,
  100. oError.errorMsg
  101. );
  102. }
  103. },
  104. });
  105. }

第二段代码,与原始demo的区别主要是使用传入的参数,登录、获取通道、打开预览等相互的回调, 以实现打开页面后从登录到预览视频的一条龙服务(无法调用上边写好的展示信息的函数, 于是在这里又摆了一套...):

  1. // 获取通道
  2. function getChannelInfo() {
  3. var loadingMessage = "获取设备模拟通道...";
  4. showLoadingMessage(loadingMessage);
  5. var szDeviceIdentify = $("#ip").val(),
  6. oSel = $("#channels").empty();
  7. if (null == szDeviceIdentify) {
  8. return;
  9. }
  10. // 模拟通道
  11. WebVideoCtrl.I_GetAnalogChannelInfo(szDeviceIdentify, {
  12. success: function (xmlDoc) {
  13. var oChannels = $(xmlDoc).find("VideoInputChannel");
  14. $.each(oChannels, function (i) {
  15. var id = $(this).find("id").eq(0).text(),
  16. name = $(this).find("name").eq(0).text();
  17. if ("" == name) {
  18. name = "Camera " + (i < 9 ? "0" + (i + 1) : i + 1);
  19. }
  20. oSel.append(
  21. "<option value='" + id + "' bZero='false'>" + name + "</option>"
  22. );
  23. });
  24. hideLoadingMessage();
  25. showOPInfo(szDeviceIdentify + " 获取模拟通道成功!");
  26. },
  27. error: function (oError) {
  28. alert("获取监控模拟通道失败, 请刷新页面重试");
  29. showOPInfo(
  30. szDeviceIdentify + " 获取模拟通道失败!",
  31. oError.errorCode,
  32. oError.errorMsg
  33. );
  34. },
  35. });
  36. // 显示加载提示信息的函数
  37. function showLoadingMessage(message) {
  38. // 创建一个 DOM 元素来显示提示信息
  39. var loadingDiv = document.createElement("div");
  40. loadingDiv.setAttribute("id", "loadingMessage");
  41. loadingDiv.style.position = "fixed";
  42. loadingDiv.style.top = "8%";
  43. loadingDiv.style.left = "50%";
  44. loadingDiv.style.transform = "translate(-50%, -50%)";
  45. loadingDiv.style.backgroundColor = "rgba(0, 0, 0, 0.7)";
  46. loadingDiv.style.color = "white";
  47. loadingDiv.style.padding = "10px";
  48. loadingDiv.style.zIndex = "999";
  49. loadingDiv.style.borderRadius = "5px";
  50. loadingDiv.innerHTML = message;
  51. // 将提示信息添加到页面中
  52. document.body.appendChild(loadingDiv);
  53. }
  54. // 关闭加载提示信息的函数
  55. function hideLoadingMessage() {
  56. var loadingDiv = document.getElementById("loadingMessage");
  57. if (loadingDiv) {
  58. // 从页面中移除提示信息
  59. loadingDiv.parentNode.removeChild(loadingDiv);
  60. }
  61. }
  62. // 开始预览
  63. function clickStartRealPlay(iStreamType) {
  64. var loadingMessage = "正在打开监控画面...";
  65. showLoadingMessage(loadingMessage);
  66. var oWndInfo = WebVideoCtrl.I_GetWindowStatus(g_iWndIndex),
  67. szDeviceIdentify = $("#ip").val(),
  68. iRtspPort = parseInt($("#rtspport").val(), 10),
  69. iChannelID = parseInt($("#channels").val(), 10),
  70. bZeroChannel =
  71. $("#channels option")
  72. .eq($("#channels").get(0).selectedIndex)
  73. .attr("bZero") == "true"
  74. ? true
  75. : false,
  76. szInfo = "";
  77. if ("undefined" === typeof iStreamType) {
  78. iStreamType = parseInt($("#streamtype").val(), 10);
  79. }
  80. if (null == szDeviceIdentify) {
  81. return;
  82. }
  83. var startRealPlay = function () {
  84. WebVideoCtrl.I_StartRealPlay(szDeviceIdentify, {
  85. iStreamType: iStreamType,
  86. iChannelID: iChannelID,
  87. bZeroChannel: bZeroChannel,
  88. success: function () {
  89. hideLoadingMessage();
  90. szInfo = "开始预览成功!";
  91. showOPInfo(szDeviceIdentify + " " + szInfo);
  92. },
  93. error: function (oError) {
  94. alert("预览实时画面失败, 请刷新页面重试");
  95. showOPInfo(
  96. szDeviceIdentify + " 开始预览失败!",
  97. oError.errorCode,
  98. oError.errorMsg
  99. );
  100. },
  101. });
  102. };
  103. if (oWndInfo != null) {
  104. // 已经在播放了,先停止
  105. WebVideoCtrl.I_Stop({
  106. success: function () {
  107. startRealPlay();
  108. },
  109. });
  110. } else {
  111. startRealPlay();
  112. }
  113. }
  114. // 获取端口
  115. function getDevicePort() {
  116. var szDeviceIdentify = $("#ip").val();
  117. if (null == szDeviceIdentify) {
  118. return;
  119. }
  120. WebVideoCtrl.I_GetDevicePort(szDeviceIdentify).then(
  121. (oPort) => {
  122. $("#deviceport").val(oPort.iDevicePort);
  123. $("#rtspport").val(oPort.iRtspPort);
  124. showOPInfo(szDeviceIdentify + " 获取端口成功!");
  125. // 在成功回调中调用开始预览函数
  126. // 这里直接使用1=主码流, 其他选项可查看html文件中的steamtype
  127. clickStartRealPlay(1);
  128. },
  129. (oError) => {
  130. var szInfo = "获取端口失败!";
  131. showOPInfo(szDeviceIdentify + szInfo, oError.errorCode, oError.errorMsg);
  132. }
  133. );
  134. // 登录成功后调用开始预览函数
  135. }

初始化插件的代码就不放了, 只是改动了提示信息而已.

html和css按需求自行调整, 因为这个插件比较逆天, 覆盖了所有其他的样式, 我就在四周留了空隙, 用于展示提示信息, 否则用户体验会比较差.

以下是最终效果:

1

2

3

五、总结

本需求的完成得到了前端大佬、后端大佬、设备大佬的帮助,特别是后端将设备映射到公网, 以及前端单独部署demo页面这个思路的转变。前期的调研和方案确认占了大部分时间,代码落地耗时较少,用到了GPT辅助,后期有内网无法映射到公网的项目,可能还要调整方案。

调整HTML我也有点犯傻,因为js逻辑会调用html页面的信息,隐藏这些模块和按钮需要解耦,我还挨个去测试并注释掉,其实在标签添加一个样式隐藏掉就可以了,根本不用那么麻烦,哈哈

感谢您的耐心观看,希望对您的需求实现有所帮助。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/362206
推荐阅读
相关标签
  

闽ICP备14008679号