Hikvison は NVR とドッキングして、フロントエンド ビデオ プレビュー (html、vue、nginx プロキシ) を実装するための WEB プラグイン不要の開発キットを実装します。

シーン

Vue で HIKVSION Hikvision の NVR (ネットワーク ハードディスク レコーダー) の複数のチャンネル (カメラ) のビデオをプレビューします。

HIKVSION で複数のチャンネル (カメラ) のビデオをプレビューする Hikvision の NVR (ネットワーク ハードディスク ビデオ レコーダー) を Vue でプレビューする_横暴なギャング気質のブログ - CSDN ブログ_Hikvision nvr Web ページのプレビュー

上記の NVR ビデオをプレビューする場合は、WEB コントロール開発パッケージが使用されます。これには、コンピューターにプラグインとブラウザーをインストールする必要があります。

互換モードでプレビューします。

さらに、プラグインを使用せずにパッケージを開発する別の方法もあります。

 

現在のところ、WEBプラグインフリーの開発キット V3.2です

WEB3.2 プラグインフリー バージョンの開発キットは、Google および Firefox ブラウザの上位バージョンをサポートしており、デバイスは Websocket ストリーミングをサポートする必要があります。プラグインなしのバージョンでは、nginx プロキシ サーバーを使用する必要があります。

説明書によると、NVR は WebSocket ストリーミングをサポートする必要があります。

NVR が WebSocket ストリーミングをサポートしているかどうかを確認する

ここでの NVR モデルは次のとおりです: DS-8664n-k16

 

NVR Web ページにログインし、[構成] - [システム設定] - [ネットワーク] - [詳細構成] - [WebSocket を有効にする]

 

WebSokcet を有効にするオプションがある場合は、問題がないことを意味します。さらに確認するには、上記の公式デモをダウンロードしてください。

 

公式の手順に従って nginx エージェントを実行します。

 

nginxディレクトリ内の設定ファイルを変更します。

 

ここでは、ポート番号を 8000 に変更するだけです。

次に、start.bat をクリックして nginx を起動し、アクセスします。

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

NVR に対応するアドレス、ユーザー名、パスワード、その他の情報を入力し、「ログインしてプレビューを開始」をクリックします。

 

公式デモが正常にプレビューできた場合は、プラグイン不要の開発パッケージを使用できます。

注記:

ブログ:
横暴な不正気質のブログ_CSDN ブログ - C#、アーキテクチャ ロード、SpringBoot ブロガー。
パブリック アカウント
Domineering Programmerをフォローする
と、プログラミング関連の電子書籍、チュートリアル、無料ダウンロードが入手できます。

成し遂げる

1. プレビューは、demo.html で実現できるため、必要に応じて変更するだけで済みます。

対応するデモとインターフェイスのドキュメントは非常に明確です。

そこで、フロントエンドとバックエンドを分離する SpringBoot+Vue プロジェクトなど、デモのサンプルを既存のプロジェクトと組み合わせる方法について説明します。

開発環境のセットアップと、フロントエンドとバックエンドの別々のバージョンに基づいたプロジェクトの実行に関するチュートリアル:

Ruoyi のフロントエンドとバックエンドの分離バージョンでは、環境を構築してローカルでプロジェクトを実行する方法をステップバイステップで教えます_横暴な不正気質のブログ - CSDN ブログ_フロントエンドとバックエンドの分離したプロジェクトのローカル操作

Vue ベースのプロジェクトでのコード変換

 

カメラ関連の情報はバックグラウンドで保存されますが、最も必要なのはチャンネル番号であり、プレビュー時に参照することができます。

 

カメラが選択されたら、プレビュー ボタンをクリックし、チャンネル番号パラメータをプレビュー ページに渡します。

前のプロセスを参照してください

SpringBoot+Vue+HIKVSION は、カメラのマルチ選択とマルチウィンドウ プレビューを実装します (プラグイン バージョン)。

SpringBoot+Vue+HIKVSIONでカメラのマルチ選択とマルチウィンドウプレビューを実装(プラグイン版)_横暴なローグ気質のブログ - CSDNブログ_websocketストリーミング

次に、公式デモに従って必要な js とその他のファイルを導入します

 

以下は主にプレビューページのコードです

<template>
  <el-dialog
    title="视频监控"
    :visible.sync="videoOpen"
    width="800px"
    :append-to-body=false
    @close="videoClose"
    class="video_box"
    :modal=false
  >
    <!-- 摄像头 -->
    <!--视频窗口展示-->
    <div id="playWnd" class="playWnd" ref="playWnd"></div>
  </el-dialog>
</template>
 
<script>
const g_iWndIndex = 0; //可以不用设置这个变量,有窗口参数的接口中,不用传值,开发包会默认使用当前选择窗口
export default {
  name: "HkVideo1",
  components: {},
  props: {
    channelId: "",
  },
  watch: {},
  data() {
    return {
      isLogin: false,
      videoOpen: false,
      szDeviceIdentify: "", // 设备标识(IP_Port)
      ip: "NVR的ip",
      port: "80",
      username: "NVR的用户名",
      password: "NVR的密码",
    };
  },
  created() {},
  mounted() {},
  destroyed() {},
  methods: {
    // 创建播放实例
    async initPlugin() {

      let iRet = window.WebVideoCtrl.I_CheckPluginInstall();

      if (-1 == iRet) {
        alert("您还未安装过插件,请安装WebComponentsKit.exe!");
        this.$confirm("是否下载WebComponentsKit.exe插件?", "提示", {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        }).then(() => {
          window.location.href = "/static/HK_3.2/WebComponentsKit.exe";
        });

        return;
      }
      // 初始化插件参数及插入插件
      window.WebVideoCtrl.I_InitPlugin(800, 600, {
        bWndFull: true, //是否支持单窗口双击全屏,默认支持 true:支持 false:不支持
        iPackageType: 2,
        //szColorProperty:"plugin-background:0000ff; sub-background:0000ff; sub-border:00ffff; sub-border-select:0000ff",   //2:PS 11:MP4
        iWndowType: 1,
        bNoPlugin: true,
        // 窗口选中事件回调
        cbSelWnd: (xmlDoc) => {
          var szInfo = "当前选择的窗口编号:" + g_iWndIndex;
          console.log(szInfo);
        },
        // 窗口双击事件回调
        cbDoubleClickWnd: (iWndIndex, bFullScreen) => {
          var szInfo = "当前放大的窗口编号:" + iWndIndex;
          if (!bFullScreen) {
            szInfo = "当前还原的窗口编号:" + iWndIndex;
          }
          console.log(szInfo);
        },
        // 插件事件回调
        cbEvent: (iEventType, iParam1, iParam2) => {
          if (2 == iEventType) {
            // 回放正常结束
            console.log("窗口" + iParam1 + "回放结束!");
          } else if (-1 == iEventType) {
            console.log("设备" + iParam1 + "网络错误!");
          } else if (3001 == iEventType) {
            this.clickStopRecord(g_szRecordType, iParam1);
          }
        },
        cbRemoteConfig: () => {
          console.log("关闭远程配置库!");
        },
        // 插件初始化完成回调
        cbInitPluginComplete: () => {
          this.$nextTick(() => {
            console.log('窗口', this.$refs.playWnd)
            let isInit = window.WebVideoCtrl.I_InsertOBJECTPlugin('playWnd');
            console.log('isInit', isInit)
 
            // 检查插件是否最新
            if (-1 == window.WebVideoCtrl.I_CheckPluginVersion()) {
              alert("检测到新的插件版本,请对WebComponentsKit.exe进行升级!");
              return;
            } else this.clickLogin();
          })
        },
      });
    },

    // 登录
    clickLogin() {
      let { ip, port, username, password } = this;

      if ("" == ip || "" == port) {
        return;
      }

      this.szDeviceIdentify = ip + "_" + port;

      let iRet = window.WebVideoCtrl.I_Login(ip, 1, port, username, password, {
        success: (xmlDoc) => {
          setTimeout(() => {
            this.getChannelInfo();
            this.getDevicePort();
          }, 10);
        },
        error: (status, xmlDoc) => {
          console.log(" 登录失败!", status, xmlDoc);
        },
      });

      if (-1 == iRet) {
        this.clickStartRealPlay();
      }
    },
    // 获取通道
    getChannelInfo() {
      if (null == this.szDeviceIdentify) {
        return;
      }

      // 模拟通道
      window.WebVideoCtrl.I_GetAnalogChannelInfo(this.szDeviceIdentify, {
        async: false,
        success: (xmlDoc) => {
        },
        error: (status, xmlDoc) => {
          console.log(" 获取模拟通道失败!");
        },
      });
      // 数字通道
      window.WebVideoCtrl.I_GetDigitalChannelInfo(this.szDeviceIdentify, {
        async: false,
        success: (xmlDoc) => {
        },
        error: (status, xmlDoc) => {
          console.log(" 获取数字通道失败!");
        },
      });
      // 零通道
      window.WebVideoCtrl.I_GetZeroChannelInfo(this.szDeviceIdentify, {
        async: false,
        success: (xmlDoc) => {
        },
        error: (status, xmlDoc) => {
          console.log(" 获取零通道失败!");
        },
      });
    },
    // 获取端口
    getDevicePort() {
      if (null == this.szDeviceIdentify) {
        return;
      }

      this.port = window.WebVideoCtrl.I_GetDevicePort(this.szDeviceIdentify);
      if (this.port != null) {
        this.clickStartRealPlay();
        return true
      } else {
        console.log(" 获取端口失败!");
        return false
      }
    },
    // 开始预览
    clickStartRealPlay(iStreamType) {
      let wndInfo = window.WebVideoCtrl.I_GetWindowStatus(g_iWndIndex);

      let iChannelID = this.channelId; // 通道列表
      let bZeroChannel = false; // 是否播放零通道(下拉框)
      let szInfo = "";

      if ("undefined" === typeof iStreamType) {
        iStreamType = 2; // 1主码流 2子码流 3第三码流 4转码码流
      }

      if (null == this.szDeviceIdentify) {
        return;
      }
      let startRealPlay = () => {
        window.WebVideoCtrl.I_StartRealPlay(this.szDeviceIdentify, {
          iRtspPort: 554,
          iStreamType: iStreamType,
          iChannelID: iChannelID,
          bZeroChannel: bZeroChannel,
          success: () => {
            szInfo = "开始预览成功!";
            console.log(szInfo);
          },
          error: (status, xmlDoc) => {
            if (403 === status) {
              szInfo = "设备不支持Websocket取流!";
            } else {
              szInfo = "开始预览失败!";
            }
            this.$message.error(szInfo);
          },
        });
      };

      if (wndInfo != null) {
        // 已经在播放了,先停止
        window.WebVideoCtrl.I_Stop({
          success: () => {
            startRealPlay();
          },
        });
      } else {
        startRealPlay();
      }
    },
    // 停止预览
    clickStopRealPlay() {
      let oWndInfo = window.WebVideoCtrl.I_GetWindowStatus(g_iWndIndex),
        szInfo = "";

      if (oWndInfo != null) {
        window.WebVideoCtrl.I_Stop({
          success: () => {
            szInfo = "停止预览成功!";
            console.log(szInfo);
          },
          error: () => {
            szInfo = "停止预览失败!";
            console.log(szInfo);
          },
        });
      }
    },
    // 全屏
    clickFullScreen() {
      window.WebVideoCtrl.I_FullScreen(true);
    },
    // 停止录像
    clickStopRecord(szType, iWndIndex) {
      if ("undefined" === typeof iWndIndex) {
        iWndIndex = g_iWndIndex;
      }
      var oWndInfo = window.WebVideoCtrl.I_GetWindowStatus(iWndIndex),
        szInfo = "";

      if (oWndInfo != null) {
        window.WebVideoCtrl.I_StopRecord({
          success: () => {
            if ("realplay" === szType) {
              szInfo = "停止录像成功!";
            } else if ("playback" === szType) {
              szInfo = "停止剪辑成功!";
            }
            showOPInfo(oWndInfo.szDeviceIdentify + " " + szInfo);
          },
          error: () => {
            if ("realplay" === szType) {
              szInfo = "停止录像失败!";
            } else if ("playback" === szType) {
              szInfo = "停止剪辑失败!";
            }
            showOPInfo(oWndInfo.szDeviceIdentify + " " + szInfo);
          },
        });
      }
    },
    // 查看摄像
    videoChange() {
      this.videoOpen = true;
      this.$nextTick(() => {
        if(!this.isLogin) {
          this.isLogin = true
          this.initPlugin()
        } else {
          this.clickStartRealPlay();
        }
      });
    },
    // 关闭摄像头弹窗
    videoClose() {
      this.videoOpen = false;
      console.log(this.isLogin)
      this.clickStopRealPlay();
    },
  },
};
</script>
  <style scoped lang="scss">
.video_box {
  width: 100%;
  height: 100%;
}

.plugin {
  width: 100%;
  height: 100%;
}

.playWnd {
  width: 800px;
  height: 600px;
  margin: 0;
}

.video_box {
  ::v-deep .el-dialog__body {
    padding: 0 !important;
  }
}
</style>

2. nginxプロキシについて

公式の nginx 設定ファイルのサンプルを表示する

 

代理店には 2 つの部分が必要です。正面

        location / {
            root   "../webs";
            index  index.html index.htm;
        }

これはサンプルデモの静的ページのプロキシであり、独自の Vue dist パッケージのプロキシに対応します。

対応するオンラインを次のように変更する必要があります。

    location / {
      root C:\dist;
      try_files $uri $uri/ /index.html;
      index index.html index.htm;
    }

残りの 2 つのプロキシのうちの 1 つは、ログイン インターフェイスを呼び出すときのリバース プロキシなどのインターフェイス プロキシです。


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

これはおそらく、リクエストが ISAPI または SDK で始まる場合、波線は大文字と小文字が無視されず、次のアドレスにプロキシされることを意味します。

しかし、ここでは、プロキシ設定ファイルに nvr の IP アドレスに関する設定がない理由を尋ねます。プロキシの場合、インターフェイスをどのようにリクエストすればよいでしょうか。

ここでは、転送のために中間に nginx が追加されており、nginx はリクエスト内の Cookie を通じて NVR の IP アドレスを見つけて、プレビュー中のビデオ ストリームの取得を含めて転送します。

これは、WebSocket プロキシを実行してビデオ ストリームを取得する方法でもあります。

以下のフローチャートと同様です。

 

したがって、公式の nginx 設定ファイルに従ってプロジェクトの nginx 設定ファイルを変更するだけです。

以下は、変換後のプロジェクトの nginx 設定ファイルの例です。


worker_processes 1;
events {
  worker_connections 1024;
}
http {
  include mime.types;
  default_type application/octet-stream;
  sendfile on;
  keepalive_timeout 65;
  map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
  }
  server {
    listen 90;
    server_name localhost;

    client_max_body_size 300M;

    #websocket相关配置
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-real-ip $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr;

   
    location / {
      root D:\font\dist;
      try_files $uri $uri/ /index.html;
      index index.html index.htm;
    }
    location /prod-api/ {
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header REMOTE-HOST $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_pass http://服务器ip:8888/;
    }
    location ~ /ISAPI|SDK/ {
      if ($http_cookie ~ "webVideoCtrlProxy=(.+)") {
 proxy_pass http://$cookie_webVideoCtrlProxy;
 break;
      }
    }

    location ^~ /webSocketVideoCtrlProxy {
     #web socket
     proxy_http_version 1.1;
     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection "upgrade";
     proxy_set_header Host $host;

     if ($http_cookie ~ "webVideoCtrlProxyWs=(.+)") {
  proxy_pass http://$cookie_webVideoCtrlProxyWs/$cookie_webVideoCtrlProxyWsChannel?$args;
  break;
     }
     if ($http_cookie ~ "webVideoCtrlProxyWss=(.+)") {
  proxy_pass http://$cookie_webVideoCtrlProxyWss/$cookie_webVideoCtrlProxyWsChannel?$args;
  break;
     }
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
      root html;
    }
  }
}

Nginx プロキシを使用するフロントエンドおよびバックエンド プロジェクトについては、以下を参照してください。

フロントエンドとバックエンドのバージョンが分離されている場合は、Windows で Nginx プロキシを使用してデプロイします (完全なプロセス、グラフィック チュートリアル)。

フロントエンドとバックエンドの分離バージョンに依存する場合は、Nginx プロキシ方式を使用して Windows に展開します (完全なプロセス、グラフィック チュートリアル)_Overbearing Rogue Temperament のブログ - CSDN ブログ

プレビュー効果

 

おすすめ

転載: blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/127717532
おすすめ