• Hikvison对接NVR实现WEB无插件开发包实现前端视频预览(html、vue、nginx代理)


    场景

    Vue中预览HIKVSION海康威视的NVR(网络硬盘录像机)中多个通道(摄像机)的视频:

    Vue中预览HIKVSION海康威视的NVR(网络硬盘录像机)中多个通道(摄像机)的视频_霸道流氓气质的博客-CSDN博客_海康nvr网页预览

    在上面进行NVR视频预览时采用的是WEB控件开发包,需要电脑安装插件,并且需要浏览器在

    兼容模式下预览。

    除此之外,还有另一种无插件开发包的方式

     

    截止目前是WEB无插件开发包V3.2

    WEB3.2无插件版本开发包,支持高版本谷歌、火狐浏览器,同时需要设备支持Websocket取流。无插件版本需要使用nginx代理服务器。

    按照说明可知,需要NVR支持websocket取流。

    验证NVR是否支持websocket取流

    这里的NVR型号为:DS-8664n-k16

     

    登录到NVR的web页面-配置-系统设置-网络-高级配置-启用websocket

     

    如果有启用WebSokcet选项,则代表可以,为了进一步验证,下载上面的官方demo。

     

    按照官方提供的说明,运行nginx的代理

     

    修改nginx目录下的配置文件

     

    这里只是简单修改了端口号为8000

    然后点击start.bat启动nginx,访问

    http://127.0.0.1:8000/cn/demo.html

    输入NVR对应的地址、用户名、密码等信息,然后点击登录和开始预览

     

    如果官方demo能预览成功,那么就可以使用无插件开发包了。

    注:

    博客:
    霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
    关注公众号
    霸道的程序猿
    获取编程相关电子书、教程推送与免费下载。

    实现

    1、在demo.html中能实现预览的基础上,只需要根据自己的需要改造即可,

    对应的demo以及接口文档说的很清楚。

    那么怎么将demo的示例与现有项目进行结合,比如前后端分离的SpringBoot+Vue的项目。

    若依前后端分离版本地搭建开发环境并运行项目的教程:

    若依前后端分离版手把手教你本地搭建环境并运行项目_霸道流氓气质的博客-CSDN博客_前后端分离项目本地运行

    在基于Vue的项目上进行代码改造

     

    后台存储摄像头的相关信息,最需要的就是通道号,拿其来进行预览时传参用

     

    选中摄像头时点击预览按钮,传递通道号参数至预览页面。

    前面流程可参考

    SpringBoot+Vue+HIKVSION实现摄像头多选并多窗口预览(插件版):

    SpringBoot+Vue+HIKVSION实现摄像头多选并多窗口预览(插件版)_霸道流氓气质的博客-CSDN博客_websocket取流

    然后根据官方demo引入需要的js等文件

     

    下面主要是预览页面的代码

    1. <template>
    2.   <el-dialog
    3.     title="视频监控"
    4.     :visible.sync="videoOpen"
    5.     width="800px"
    6.     :append-to-body=false
    7.     @close="videoClose"
    8.     class="video_box"
    9.     :modal=false
    10.   >
    11.     <!-- 摄像头 -->
    12.     <!--视频窗口展示-->
    13.     <div id="playWnd" class="playWnd" ref="playWnd"></div>
    14.   </el-dialog>
    15. </template>
    16.  
    17. <script>
    18. const g_iWndIndex = 0; //可以不用设置这个变量,有窗口参数的接口中,不用传值,开发包会默认使用当前选择窗口
    19. export default {
    20.   name: "HkVideo1",
    21.   components: {},
    22.   props: {
    23.     channelId: "",
    24.   },
    25.   watch: {},
    26.   data() {
    27.     return {
    28.       isLogin: false,
    29.       videoOpen: false,
    30.       szDeviceIdentify: "", // 设备标识(IP_Port)
    31.       ip: "NVR的ip",
    32.       port: "80",
    33.       username: "NVR的用户名",
    34.       password: "NVR的密码",
    35.     };
    36.   },
    37.   created() {},
    38.   mounted() {},
    39.   destroyed() {},
    40.   methods: {
    41.     // 创建播放实例
    42.     async initPlugin() {
    43.       let iRet = window.WebVideoCtrl.I_CheckPluginInstall();
    44.       if (-1 == iRet) {
    45.         alert("您还未安装过插件,请安装WebComponentsKit.exe!");
    46.         this.$confirm("是否下载WebComponentsKit.exe插件?", "提示", {
    47.           confirmButtonText: "确定",
    48.           cancelButtonText: "取消",
    49.           type: "warning",
    50.         }).then(() => {
    51.           window.location.href = "/static/HK_3.2/WebComponentsKit.exe";
    52.         });
    53.         return;
    54.       }
    55.       // 初始化插件参数及插入插件
    56.       window.WebVideoCtrl.I_InitPlugin(800, 600, {
    57.         bWndFull: true, //是否支持单窗口双击全屏,默认支持 true:支持 false:不支持
    58.         iPackageType: 2,
    59.         //szColorProperty:"plugin-background:0000ff; sub-background:0000ff; sub-border:00ffff; sub-border-select:0000ff",   //2:PS 11:MP4
    60.         iWndowType: 1,
    61.         bNoPlugin: true,
    62.         // 窗口选中事件回调
    63.         cbSelWnd: (xmlDoc) => {
    64.           var szInfo = "当前选择的窗口编号:" + g_iWndIndex;
    65.           console.log(szInfo);
    66.         },
    67.         // 窗口双击事件回调
    68.         cbDoubleClickWnd: (iWndIndex, bFullScreen) => {
    69.           var szInfo = "当前放大的窗口编号:" + iWndIndex;
    70.           if (!bFullScreen) {
    71.             szInfo = "当前还原的窗口编号:" + iWndIndex;
    72.           }
    73.           console.log(szInfo);
    74.         },
    75.         // 插件事件回调
    76.         cbEvent: (iEventType, iParam1, iParam2) => {
    77.           if (2 == iEventType) {
    78.             // 回放正常结束
    79.             console.log("窗口" + iParam1 + "回放结束!");
    80.           } else if (-1 == iEventType) {
    81.             console.log("设备" + iParam1 + "网络错误!");
    82.           } else if (3001 == iEventType) {
    83.             this.clickStopRecord(g_szRecordType, iParam1);
    84.           }
    85.         },
    86.         cbRemoteConfig: () => {
    87.           console.log("关闭远程配置库!");
    88.         },
    89.         // 插件初始化完成回调
    90.         cbInitPluginComplete: () => {
    91.           this.$nextTick(() => {
    92.             console.log('窗口', this.$refs.playWnd)
    93.             let isInit = window.WebVideoCtrl.I_InsertOBJECTPlugin('playWnd');
    94.             console.log('isInit', isInit)
    95.  
    96.             // 检查插件是否最新
    97.             if (-1 == window.WebVideoCtrl.I_CheckPluginVersion()) {
    98.               alert("检测到新的插件版本,请对WebComponentsKit.exe进行升级!");
    99.               return;
    100.             } else this.clickLogin();
    101.           })
    102.         },
    103.       });
    104.     },
    105.     // 登录
    106.     clickLogin() {
    107.       let { ip, port, username, password } = this;
    108.       if ("" == ip || "" == port) {
    109.         return;
    110.       }
    111.       this.szDeviceIdentify = ip + "_" + port;
    112.       let iRet = window.WebVideoCtrl.I_Login(ip, 1, port, username, password, {
    113.         success: (xmlDoc) => {
    114.           setTimeout(() => {
    115.             this.getChannelInfo();
    116.             this.getDevicePort();
    117.           }, 10);
    118.         },
    119.         error: (status, xmlDoc) => {
    120.           console.log(" 登录失败!", status, xmlDoc);
    121.         },
    122.       });
    123.       if (-1 == iRet) {
    124.         this.clickStartRealPlay();
    125.       }
    126.     },
    127.     // 获取通道
    128.     getChannelInfo() {
    129.       if (null == this.szDeviceIdentify) {
    130.         return;
    131.       }
    132.       // 模拟通道
    133.       window.WebVideoCtrl.I_GetAnalogChannelInfo(this.szDeviceIdentify, {
    134.         async: false,
    135.         success: (xmlDoc) => {
    136.         },
    137.         error: (status, xmlDoc) => {
    138.           console.log(" 获取模拟通道失败!");
    139.         },
    140.       });
    141.       // 数字通道
    142.       window.WebVideoCtrl.I_GetDigitalChannelInfo(this.szDeviceIdentify, {
    143.         async: false,
    144.         success: (xmlDoc) => {
    145.         },
    146.         error: (status, xmlDoc) => {
    147.           console.log(" 获取数字通道失败!");
    148.         },
    149.       });
    150.       // 零通道
    151.       window.WebVideoCtrl.I_GetZeroChannelInfo(this.szDeviceIdentify, {
    152.         async: false,
    153.         success: (xmlDoc) => {
    154.         },
    155.         error: (status, xmlDoc) => {
    156.           console.log(" 获取零通道失败!");
    157.         },
    158.       });
    159.     },
    160.     // 获取端口
    161.     getDevicePort() {
    162.       if (null == this.szDeviceIdentify) {
    163.         return;
    164.       }
    165.       this.port = window.WebVideoCtrl.I_GetDevicePort(this.szDeviceIdentify);
    166.       if (this.port != null) {
    167.         this.clickStartRealPlay();
    168.         return true
    169.       } else {
    170.         console.log(" 获取端口失败!");
    171.         return false
    172.       }
    173.     },
    174.     // 开始预览
    175.     clickStartRealPlay(iStreamType) {
    176.       let wndInfo = window.WebVideoCtrl.I_GetWindowStatus(g_iWndIndex);
    177.       let iChannelID = this.channelId; // 通道列表
    178.       let bZeroChannel = false; // 是否播放零通道(下拉框)
    179.       let szInfo = "";
    180.       if ("undefined" === typeof iStreamType) {
    181.         iStreamType = 2; // 1主码流 2子码流 3第三码流 4转码码流
    182.       }
    183.       if (null == this.szDeviceIdentify) {
    184.         return;
    185.       }
    186.       let startRealPlay = () => {
    187.         window.WebVideoCtrl.I_StartRealPlay(this.szDeviceIdentify, {
    188.           iRtspPort: 554,
    189.           iStreamType: iStreamType,
    190.           iChannelID: iChannelID,
    191.           bZeroChannel: bZeroChannel,
    192.           success: () => {
    193.             szInfo = "开始预览成功!";
    194.             console.log(szInfo);
    195.           },
    196.           error: (status, xmlDoc) => {
    197.             if (403 === status) {
    198.               szInfo = "设备不支持Websocket取流!";
    199.             } else {
    200.               szInfo = "开始预览失败!";
    201.             }
    202.             this.$message.error(szInfo);
    203.           },
    204.         });
    205.       };
    206.       if (wndInfo != null) {
    207.         // 已经在播放了,先停止
    208.         window.WebVideoCtrl.I_Stop({
    209.           success: () => {
    210.             startRealPlay();
    211.           },
    212.         });
    213.       } else {
    214.         startRealPlay();
    215.       }
    216.     },
    217.     // 停止预览
    218.     clickStopRealPlay() {
    219.       let oWndInfo = window.WebVideoCtrl.I_GetWindowStatus(g_iWndIndex),
    220.         szInfo = "";
    221.       if (oWndInfo != null) {
    222.         window.WebVideoCtrl.I_Stop({
    223.           success: () => {
    224.             szInfo = "停止预览成功!";
    225.             console.log(szInfo);
    226.           },
    227.           error: () => {
    228.             szInfo = "停止预览失败!";
    229.             console.log(szInfo);
    230.           },
    231.         });
    232.       }
    233.     },
    234.     // 全屏
    235.     clickFullScreen() {
    236.       window.WebVideoCtrl.I_FullScreen(true);
    237.     },
    238.     // 停止录像
    239.     clickStopRecord(szType, iWndIndex) {
    240.       if ("undefined" === typeof iWndIndex) {
    241.         iWndIndex = g_iWndIndex;
    242.       }
    243.       var oWndInfo = window.WebVideoCtrl.I_GetWindowStatus(iWndIndex),
    244.         szInfo = "";
    245.       if (oWndInfo != null) {
    246.         window.WebVideoCtrl.I_StopRecord({
    247.           success: () => {
    248.             if ("realplay" === szType) {
    249.               szInfo = "停止录像成功!";
    250.             } else if ("playback" === szType) {
    251.               szInfo = "停止剪辑成功!";
    252.             }
    253.             showOPInfo(oWndInfo.szDeviceIdentify + " " + szInfo);
    254.           },
    255.           error: () => {
    256.             if ("realplay" === szType) {
    257.               szInfo = "停止录像失败!";
    258.             } else if ("playback" === szType) {
    259.               szInfo = "停止剪辑失败!";
    260.             }
    261.             showOPInfo(oWndInfo.szDeviceIdentify + " " + szInfo);
    262.           },
    263.         });
    264.       }
    265.     },
    266.     // 查看摄像
    267.     videoChange() {
    268.       this.videoOpen = true;
    269.       this.$nextTick(() => {
    270.         if(!this.isLogin) {
    271.           this.isLogin = true
    272.           this.initPlugin()
    273.         } else {
    274.           this.clickStartRealPlay();
    275.         }
    276.       });
    277.     },
    278.     // 关闭摄像头弹窗
    279.     videoClose() {
    280.       this.videoOpen = false;
    281.       console.log(this.isLogin)
    282.       this.clickStopRealPlay();
    283.     },
    284.   },
    285. };
    286. </script>
    287.   <style scoped lang="scss">
    288. .video_box {
    289.   width: 100%;
    290.   height: 100%;
    291. }
    292. .plugin {
    293.   width: 100%;
    294.   height: 100%;
    295. }
    296. .playWnd {
    297.   width: 800px;
    298.   height: 600px;
    299.   margin: 0;
    300. }
    301. .video_box {
    302.   ::v-deep .el-dialog__body {
    303.     padding: 0 !important;
    304.   }
    305. }
    306. </style>

    2、关于nginx代理

    查看官方示例nginx配置文件中

     

    需要做两部分的代理。最前面的

    1.         location / {
    2.             root   "../webs";
    3.             index  index.html index.htm;
    4.         }

    是其示例demo的静态页面的代理,对应自己的Vue的dist包的代理

    对应的线上要改成

    1.     location / {
    2.       root C:\dist;
    3.       try_files $uri $uri/ /index.html;
    4.       index index.html index.htm;
    5.     }

    剩下两个代理一个是接口代理,比如调用登录接口时进行反向代理

    1.  location ~ /ISAPI|SDK/ {
    2.      if ($http_cookie ~ "webVideoCtrlProxy=(.+)") {
    3.   proxy_pass http://$cookie_webVideoCtrlProxy;
    4.   break;
    5.      }
    6.  }

    这个意思大概是如果是ISAPI或者SDK开头的请求,波浪线代表不忽略大小写,就被被代理到下面的地址。

    但是这里会疑问为什么代理配置文件中没有配置任何关于nvr的ip地址的配置,那我代理时是怎么请求接口的。

    这里是在中间加一个nginx进行转发,nginx通过请求中的Cookie找到NVR的Ip地址进行转发,包括预览时获取视频流

    也是通过这种方式进行websocket代理并获取视频流。

    类似如下这张流程图。

     

    所以只要按照官方的nginx的配置文件将自己项目的nginx配置文件进行修改即可

    下面提供一个改造之后项目的nginx的配置文件示例

    1. worker_processes 1;
    2. events {
    3.   worker_connections 1024;
    4. }
    5. http {
    6.   include mime.types;
    7.   default_type application/octet-stream;
    8.   sendfile on;
    9.   keepalive_timeout 65;
    10.   map $http_upgrade $connection_upgrade {
    11.     default upgrade;
    12.     '' close;
    13.   }
    14.   server {
    15.     listen 90;
    16.     server_name localhost;
    17.     client_max_body_size 300M;
    18.     #websocket相关配置
    19.     proxy_http_version 1.1;
    20.     proxy_set_header Upgrade $http_upgrade;
    21.     proxy_set_header Connection "upgrade";
    22.     proxy_set_header X-real-ip $remote_addr;
    23.     proxy_set_header X-Forwarded-For $remote_addr;
    24.    
    25.     location / {
    26.       root D:\font\dist;
    27.       try_files $uri $uri/ /index.html;
    28.       index index.html index.htm;
    29.     }
    30.     location /prod-api/ {
    31.       proxy_set_header Host $http_host;
    32.       proxy_set_header X-Real-IP $remote_addr;
    33.       proxy_set_header REMOTE-HOST $remote_addr;
    34.       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    35.       proxy_pass http://服务器ip:8888/;
    36.     }
    37.     location ~ /ISAPI|SDK/ {
    38.       if ($http_cookie ~ "webVideoCtrlProxy=(.+)") {
    39.  proxy_pass http://$cookie_webVideoCtrlProxy;
    40.  break;
    41.       }
    42.     }
    43.     location ^~ /webSocketVideoCtrlProxy {
    44.      #web socket
    45.      proxy_http_version 1.1;
    46.      proxy_set_header Upgrade $http_upgrade;
    47.      proxy_set_header Connection "upgrade";
    48.      proxy_set_header Host $host;
    49.      if ($http_cookie ~ "webVideoCtrlProxyWs=(.+)") {
    50.   proxy_pass http://$cookie_webVideoCtrlProxyWs/$cookie_webVideoCtrlProxyWsChannel?$args;
    51.   break;
    52.      }
    53.      if ($http_cookie ~ "webVideoCtrlProxyWss=(.+)") {
    54.   proxy_pass http://$cookie_webVideoCtrlProxyWss/$cookie_webVideoCtrlProxyWsChannel?$args;
    55.   break;
    56.      }
    57.     }
    58.     error_page 500 502 503 504 /50x.html;
    59.     location = /50x.html {
    60.       root html;
    61.     }
    62.   }
    63. }

    前后端项目使用Nginx代理可以参考

    若依前后端分离版本,Windows下使用Nginx代理的方式进行部署(全流程,图文教程):

    若依前后端分离版本,Windows下使用Nginx代理的方式进行部署(全流程,图文教程)_霸道流氓气质的博客-CSDN博客

    预览效果

     

  • 相关阅读:
    网页大作业代码自取【HTML+CSS制作美味糖果网站】
    为什么要做LiveVideoStack课程?
    web网页大作业:基于html设计与实现的茶文化网站12页(带psd)
    业务架构·应用架构·数据架构实战~业务驱动的应用架构设计
    【人脸识别】face_recognition 库的使用
    SpringBoot vue云办公系统
    C++语法——右值引用、移动构造和赋值、万能引用和转发、move和forward底层实现
    js返回上一页和刷新页面
    神经网络的三种训练方法,神经网络训练全过程
    【JAVA UI】abilitySlice或ability以Action方式跳转
  • 原文地址:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/127717532