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

公司业务迭代, 需要前端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项目中的跳转和传参:

    showMonitor() {
      // 创建一个包含参数的对象
      const params = {
        szIP: 'xxxxxxxx',
        szPort: 'xxxxxxx',
        szUsername: 'admin',
        szPassword: 'xxxxxxxx'
      }

      // 使用URLSearchParams将参数转换为查询字符串
      const queryParams = new URLSearchParams(params).toString()

      // 将查询字符串添加到外部页面的URL中
      const externalURL = `http://xxxxxxx:xxxxx/cn/demo.html?${queryParams}`

      // 使用window.open()打开新标签页并跳转到带参数的外部页面
      window.open(externalURL, '_blank')
    },

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

  // 显示加载提示信息的函数
  function showLoadingMessage(message) {
    // 创建一个 DOM 元素来显示提示信息
    var loadingDiv = document.createElement("div");
    loadingDiv.setAttribute("id", "loadingMessage");
    loadingDiv.style.position = "fixed";
    loadingDiv.style.top = "8%";
    loadingDiv.style.left = "50%";
    loadingDiv.style.transform = "translate(-50%, -50%)";
    loadingDiv.style.backgroundColor = "rgba(0, 0, 0, 0.7)";
    loadingDiv.style.color = "white";
    loadingDiv.style.padding = "10px";
    loadingDiv.style.zIndex = "999";
    loadingDiv.style.borderRadius = "5px";
    loadingDiv.innerHTML = message;

    // 将提示信息添加到页面中
    document.body.appendChild(loadingDiv);
  }

  // 关闭加载提示信息的函数
  function hideLoadingMessage() {
    var loadingDiv = document.getElementById("loadingMessage");
    if (loadingDiv) {
      // 从页面中移除提示信息
      loadingDiv.parentNode.removeChild(loadingDiv);
    }
  }

  // 定义登录函数
  function loginWithParameters() {
    // 从URL中获取参数
    function getParameterByName(name) {
      name = name.replace(/[\[\]]/g, "\\$&");
      var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
        results = regex.exec(window.location.href);
      if (!results) return null;
      if (!results[2]) return "";
      return decodeURIComponent(results[2].replace(/\+/g, " "));
    }

    // 获取URL中的参数值
    var szIP = getParameterByName("szIP");
    var szPort = getParameterByName("szPort");
    var szUsername = getParameterByName("szUsername");
    var szPassword = getParameterByName("szPassword");

    // 替换属性值
    $("#loginip").val(szIP);
    $("#port").val(szPort);
    $("#username").val(szUsername);
    $("#password").val(szPassword);

    // 调用登录函数,稍后执行以确保页面加载完成
    setTimeout(function () {
      clickLogin();
    }, 1000); // 可以根据需要调整等待时间
  }

  // 在页面加载完成后自动运行登录函数
  $(document).ready(function () {
    loginWithParameters();
  });

  // 登录
  function clickLogin() {
    var loadingMessage = "正在登录设备...";
    showLoadingMessage(loadingMessage);

    var urlParams = new URLSearchParams(window.location.search);

    // 从URL参数中获取登录信息
    var szIP = urlParams.get("szIP");
    var szPort = urlParams.get("szPort");
    var szUsername = urlParams.get("szUsername");
    var szPassword = urlParams.get("szPassword");

    if (!szIP || !szPort) {
      return;
    }

    var szDeviceIdentify = szIP + "_" + szPort;

    WebVideoCtrl.I_Login(szIP, 1, szPort, szUsername, szPassword, {
      timeout: 3000,
      success: function (xmlDoc) {
        // 登录成功后关闭加载提示信息
        hideLoadingMessage();
        showOPInfo(szDeviceIdentify + " 登录成功!");
        $("#ip").prepend(
          "<option value='" +
            szDeviceIdentify +
            "'>" +
            szDeviceIdentify +
            "</option>"
        );
        setTimeout(function () {
          $("#ip").val(szDeviceIdentify);
          setTimeout(function () {
            getChannelInfo();
          }, 1000);
          getDevicePort();
        }, 10);
      },
      error: function (oError) {
        if (ERROR_CODE_LOGIN_REPEATLOGIN === status) {
          showOPInfo(szDeviceIdentify + " 已登录过!");
        } else {
          alert("登录设备失败, 请刷新页面重试");
          showOPInfo(
            szDeviceIdentify + " 登录失败!",
            oError.errorCode,
            oError.errorMsg
          );
        }
      },
    });
  }

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

// 获取通道
function getChannelInfo() {
  var loadingMessage = "获取设备模拟通道...";
  showLoadingMessage(loadingMessage);
  var szDeviceIdentify = $("#ip").val(),
    oSel = $("#channels").empty();

  if (null == szDeviceIdentify) {
    return;
  }

  // 模拟通道
  WebVideoCtrl.I_GetAnalogChannelInfo(szDeviceIdentify, {
    success: function (xmlDoc) {
      var oChannels = $(xmlDoc).find("VideoInputChannel");

      $.each(oChannels, function (i) {
        var id = $(this).find("id").eq(0).text(),
          name = $(this).find("name").eq(0).text();
        if ("" == name) {
          name = "Camera " + (i < 9 ? "0" + (i + 1) : i + 1);
        }
        oSel.append(
          "<option value='" + id + "' bZero='false'>" + name + "</option>"
        );
      });
      hideLoadingMessage();
      showOPInfo(szDeviceIdentify + " 获取模拟通道成功!");
    },
    error: function (oError) {
      alert("获取监控模拟通道失败, 请刷新页面重试");
      showOPInfo(
        szDeviceIdentify + " 获取模拟通道失败!",
        oError.errorCode,
        oError.errorMsg
      );
    },
  });

// 显示加载提示信息的函数
function showLoadingMessage(message) {
  // 创建一个 DOM 元素来显示提示信息
  var loadingDiv = document.createElement("div");
  loadingDiv.setAttribute("id", "loadingMessage");
  loadingDiv.style.position = "fixed";
  loadingDiv.style.top = "8%";
  loadingDiv.style.left = "50%";
  loadingDiv.style.transform = "translate(-50%, -50%)";
  loadingDiv.style.backgroundColor = "rgba(0, 0, 0, 0.7)";
  loadingDiv.style.color = "white";
  loadingDiv.style.padding = "10px";
  loadingDiv.style.zIndex = "999";
  loadingDiv.style.borderRadius = "5px";
  loadingDiv.innerHTML = message;

  // 将提示信息添加到页面中
  document.body.appendChild(loadingDiv);
}

// 关闭加载提示信息的函数
function hideLoadingMessage() {
  var loadingDiv = document.getElementById("loadingMessage");
  if (loadingDiv) {
    // 从页面中移除提示信息
    loadingDiv.parentNode.removeChild(loadingDiv);
  }
}
// 开始预览
function clickStartRealPlay(iStreamType) {
  var loadingMessage = "正在打开监控画面...";
  showLoadingMessage(loadingMessage);
  var oWndInfo = WebVideoCtrl.I_GetWindowStatus(g_iWndIndex),
    szDeviceIdentify = $("#ip").val(),
    iRtspPort = parseInt($("#rtspport").val(), 10),
    iChannelID = parseInt($("#channels").val(), 10),
    bZeroChannel =
      $("#channels option")
        .eq($("#channels").get(0).selectedIndex)
        .attr("bZero") == "true"
        ? true
        : false,
    szInfo = "";

  if ("undefined" === typeof iStreamType) {
    iStreamType = parseInt($("#streamtype").val(), 10);
  }

  if (null == szDeviceIdentify) {
    return;
  }
  var startRealPlay = function () {
    WebVideoCtrl.I_StartRealPlay(szDeviceIdentify, {
      iStreamType: iStreamType,
      iChannelID: iChannelID,
      bZeroChannel: bZeroChannel,
      success: function () {
        hideLoadingMessage();
        szInfo = "开始预览成功!";
        showOPInfo(szDeviceIdentify + " " + szInfo);
      },
      error: function (oError) {
        alert("预览实时画面失败, 请刷新页面重试");
        showOPInfo(
          szDeviceIdentify + " 开始预览失败!",
          oError.errorCode,
          oError.errorMsg
        );
      },
    });
  };

  if (oWndInfo != null) {
    // 已经在播放了,先停止
    WebVideoCtrl.I_Stop({
      success: function () {
        startRealPlay();
      },
    });
  } else {
    startRealPlay();
  }
}

// 获取端口
function getDevicePort() {
  var szDeviceIdentify = $("#ip").val();

  if (null == szDeviceIdentify) {
    return;
  }

  WebVideoCtrl.I_GetDevicePort(szDeviceIdentify).then(
    (oPort) => {
      $("#deviceport").val(oPort.iDevicePort);
      $("#rtspport").val(oPort.iRtspPort);

      showOPInfo(szDeviceIdentify + " 获取端口成功!");

      // 在成功回调中调用开始预览函数
      // 这里直接使用1=主码流, 其他选项可查看html文件中的steamtype
      clickStartRealPlay(1);
    },
    (oError) => {
      var szInfo = "获取端口失败!";
      showOPInfo(szDeviceIdentify + szInfo, oError.errorCode, oError.errorMsg);
    }
  );
  // 登录成功后调用开始预览函数
}

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

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

以下是最终效果:

1

2

3

五、总结

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/588373.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Jenkins邮件发送失败问题解决

如下提示为 Extended E-mail Notification开启Debug模式下显示的错误信息&#xff0c; (Debug模式设置方法&#xff1a;Dashboard-> manage Jenkins->configure System)DEBUG SMTP: Attempt to authenticate using mechanisms: LOGIN PLAIN DIGEST-MD5 NTLM XOAUTH2 DEB…

Unity3d 学习之按钮绑定事件

创建测试脚本 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI;public class myTest : MonoBehaviour {// Start is called before the first frame updatepublic Button _codeBindBtn null;void Start(){if (_codeBi…

LeetCode 213 —— 打家劫舍 II

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 此题是 LeetCode 198—— 打家劫舍 的升级版&#xff0c;多了一个首尾相连的设定。 因为首尾相连&#xff0c;所以第一个房屋和最后一个房屋只能偷窃其中一个。 所以&#xff0c;第一种方案就是不偷窃最后一个房…

每日OJ题_DFS爆搜深搜回溯剪枝⑧_力扣980. 不同路径 III

目录 力扣980. 不同路径 III 解析代码 力扣980. 不同路径 III 980. 不同路径 III 难度 困难 在二维网格 grid 上&#xff0c;有 4 种类型的方格&#xff1a; 1 表示起始方格。且只有一个起始方格。2 表示结束方格&#xff0c;且只有一个结束方格。0 表示我们可以走过的空…

HTML5实用大全(Part.1)

引言&#xff1a; 哈喽&#xff0c;各位小伙伴们&#xff0c;在本篇博客我将带领大家走进前端中的HTML5,利用HTML我们将可以在网页上自我创作内容&#xff0c;现在学起来&#xff0c;不久后自己也能制作一个花哨的项目了呢&#xff0c;那么&#xff0c;我们开始吧&#xff01; …

【ROS2学习记录】—— 参考鱼香ROS

1 回顾Linux基础 &#xff08;1&#xff09;打开终端&#xff1a;Ctrl Alt T &#xff08;2&#xff09;ls &#xff08;3&#xff09;cd cd ~ cd /&#xff08;4&#xff09;pwd &#xff08;5&#xff09;mkdir -p catkin_ws/src &#xff08;6&#xff09;rm -rf &#…

LeetCode 198—— 打家劫舍

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 此题使用动态规划求解&#xff0c;假设 d p [ i ] [ 0 ] dp[i][0] dp[i][0] 代表不偷窃第 i i i 个房屋可以获得的最高金额&#xff0c;而 d p [ i ] [ 1 ] dp[i][1] dp[i][1] 代表偷窃第 i i i 个房屋可以获…

【右一的开发日记】全导航,持续更新...

文章目录 &#x1f4da;前端【跟课笔记】&#x1f407;核心技术&#x1f407;高级技术 &#x1f4da;捣鼓捣鼓&#x1f407;小小案例&#x1f407;喵喵大王立大功&#x1f407;TED自用学习辅助网站&#x1f407;世界top2000计算机科学家可视化大屏&#x1f407;基于CBDB的唐代历…

GitHub Copilot 简单使用

因为公司安全原因&#xff0c;并不允许在工作中使用GitHub Copilot&#xff0c;所以&#xff0c;一直没怎么使用。最近因为有一些其它任务&#xff0c;所以&#xff0c;试用了一下&#xff0c;感觉还是很不错的。&#xff08;主要是C和Python编程&#xff09; 一&#xff1a;常…

python中的进程线程和协程

目录 进程&#xff08;Process&#xff09;多进程代码实例 线程&#xff08;Thread&#xff09;多线程存在原因及其缺点多线程代码实例 协程&#xff08;Coroutine&#xff09;协程的优点协程代码实例 进程、线程和协程适合的任务性质和环境多进程更适合的场景多线程更适合的场…

LeetCode 11—— 盛最多水的容器

阅读目录 1. 题目2. 解题思路一3. 代码实现一4. 解题思路二5. 代码实现二 1. 题目 2. 解题思路一 暴力法&#xff0c;遍历所有可能的垂线对 ( i , j ) (i, j) (i,j)&#xff0c;求取最大面积&#xff1a; a r e a m i n ( h [ i ] , h [ j ] ) ∗ ( j − i ) area min(h[i]…

Node.js -- MongoDB

文章目录 1. 相关介绍2. 核心概念3. 命令行交互3.1数据库命令3.2 集合命令3.3 文档命令 4. 数据库应用场景4.1 新增4.2 删除4.3 更新4.4 查询 1. 相关介绍 一、简介 Mongodb是什么 MongoDB是一个基于分布式文件存储的数据库&#xff0c;官方地址https://www.mongodb.com/try/d…

一个C++小程序调试过程记录

Top 20 C Projects With Source Code [2024 Update]https://www.interviewbit.com/blog/cpp-projects/ 这个网页有一些简单的C程序的源码&#xff0c;闲来无事&#xff0c;把第一个程序&#xff08;Bookshop Management System Using C&#xff09;的源码下载了下来。 源文件…

第N1周:one-hot独热编码

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、OneHot独热编码原理 独热编码&#xff08;One-Hot Encoding&#xff09;是一种将分类数据转换为二进制向量的方法&#xff0c;其中每个类别对应一个唯一的…

如何下载AndroidStudio旧版本

文章目录 1. Android官方网站2. 往下滑找到历史版本归档3. 同意软件下载条款协议4. 下载旧版本Androidstudio1. Android官方网站 点击 Android官网AS下载页面 https://developer.android.google.cn/studio 进入AndroidStuido最新版下载页面,如下图: 2. 往下滑找到历史版本归…

Delta lake with Java--利用spark sql操作数据2

上一篇文章尝试了建库&#xff0c;建表&#xff0c;插入数据&#xff0c;还差删除和更新&#xff0c;所以在这篇文章补充一下&#xff0c;代码很简单&#xff0c;具体如下&#xff1a; import org.apache.spark.sql.SaveMode; import org.apache.spark.sql.SparkSession;publi…

谈一谈电影《飞驰人生》

文章目录 1.概述2.电影情节3.观后感 1.概述 电影《飞驰人生》是一部关于赛车的电影&#xff0c;主要演员是沈腾&#xff0c;因此电影中有不少的喜剧片段&#xff0c;不过电影整体偏向于剧情类电影。该电影的导演是韩寒&#xff0c;就是哪个曾经写出高分高考作文的考生&#xf…

【跟我学RISC-V】(一)认识RISC-V指令集并搭建实验环境

写在前面 现在计算机的体系架构正是发展得如火如荼的时候&#xff0c;占领桌面端市场的x86架构、占领移动端市场的arm架构、在服务器市场仍有一定地位的mips架构、国产自研的指令集loongarch架构、还有我现在要讲到的新型开源开放的RISC-V指令集架构。 我先说一说我的学习经历…

十二、视觉内容生成模型

1 判别式模型和生成式模型 1. 判别式模型 学习策略函数 Y f ( X ) Yf(X) Yf(X)或者条件概率 P ( Y ∣ X ) P(Y|X) P(Y∣X)不能反映训练数据本身的特性学习成本低&#xff0c;需要的训练样本少无法转为生成式 2. 生成式模型 学习联合概率密度分布 P ( X ∣ Y ) P(X|Y) P(X∣…

C++ 矩阵

目录 了解矩阵的数学原理&#xff08;大学线性代数&#xff09; 矩阵及转置矩阵 矩阵乘法 矩阵快速幂 相伴矩阵模板 [相伴矩阵,快速矩阵幂]CSES1722 Fibonacci Numbers 了解矩阵的数学原理&#xff08;大学线性代数&#xff09; 矩阵及转置矩阵 这里A就是一个矩阵&…
最新文章