美文网首页
JSmpeg+ffmpeg+WebScoket实现视频监控(2)

JSmpeg+ffmpeg+WebScoket实现视频监控(2)

作者: 等级7 | 来源:发表于2022-06-29 10:47 被阅读0次

    技术需求请看上一篇文章,这篇使用vue实现视频监控(可直接复制代码运行)
    支持摄像头多开,点击全屏播放,窗口拖拽,操控摄像头上下左右旋转放大缩小等操作
    vue父组件页面

    <template>
      <div class="monitorPageBox">
        <div class="controlHide" v-if="controlShow">
          <i
            class="el-icon-d-arrow-right"
            @click="controlShow = false"
            title="展开控制台"
            style="cursor:pointer"
          ></i>
        </div>
        <div class="monitorList" v-else>
          <div class="monitorListTitleBox">
            <div class="monitorListTitle">
              当前摄像头
            </div>
            <div class="monitorListIcon">
              <i
                class="el-icon-d-arrow-left"
                @click="controlShow = true"
                title="隐藏控制台"
                style="cursor:pointer"
              ></i>
            </div>
          </div>
          <div class="displaysTypeBox">
            <div
              class="displaysNumber"
              v-for="(item, index) in displaysType"
              :key="index"
              :class="displaysTypeStyle(index)"
            >
              <div @click="displaysTypeClick(index)" style="cursor:pointer">
                {{ item }}
              </div>
            </div>
          </div>
          <div class="searchInputBox">
            <el-input placeholder="输入摄像头名称" v-model="monitorName"
              ><el-button
                slot="append"
                icon="el-icon-search"
                @click="queryEquipment"
              ></el-button
            ></el-input>
          </div>
          <div class="monitorEquipmentDataBox">
            <el-scrollbar style="height: 100%">
              <div
                class="monitorEquipmentBox"
                v-for="(item, index) in videoList"
                :key="index"
                @dblclick="monitorControl(item)"
                style="cursor:pointer"
                :title="
                  monitorPlay(item.id) ? `双击关闭${item.ip}` : `双击播放${item.ip}`
                "
              >
                <div class="monitorIPText" draggable="true" @dragstart="drag(item)">
                  <span>{{ item.ip }} </span>
                  <span v-if="monitorPlay(item.id)"
                    >--摄像头{{ playMonitor(item.id) }}</span
                  >
                </div>
                <div class="monitorControlBox">
                  <i
                    class="el-icon-circle-close"
                    v-if="monitorPlay(item.id)"
                    style="color:#ff3b30;"
                  ></i>
                  <i class="el-icon-video-play" style="color:#1c9eff;" v-else></i>
                </div>
              </div>
            </el-scrollbar>
          </div>
          <div class="controlBox" v-if="selectMonitorID">
            <div class="directionBox">
              <div
                class="directionButton"
                v-for="(item, index) in directionData"
                :key="index"
                @click="index == 4 ? rotatePreset('右') : ''"
                @mousedown="index != 4 ? move($event, index) : ''"
                @mouseup="end('direction')"
                @mouseout="end('direction')"
              >
                {{ item }}
              </div>
            </div>
            <div class="focusingBox">
              <div
                class="equipmentOperationBox"
                v-for="(item, index) in equipmentOperationData"
                :key="index"
                @mousedown="equipmentOperation($event, index)"
                @mouseup="index < 2 ? end('operation') : ''"
                @mouseout="index < 2 ? end('operation') : ''"
              >
                {{ item }}
              </div>
              <div class="rotateTimeoutBox">
                <el-input-number
                  v-model="timeout"
                  style="width: 100px"
                ></el-input-number>
              </div>
            </div>
          </div>
          <div class="speedSliderBox" v-if="selectMonitorID">
            <el-slider v-model="moveSpeed" :min="20"></el-slider>
          </div>
          <div class="presuppositionBox" v-if="selectMonitorID">
            <el-scrollbar style="height: 100%">
              <div
                class="presupposition"
                v-for="item in presuppositionData"
                :key="item.$.token"
              >
                <div
                  class="presuppositionText"
                  :style="existencePreset(item.PTZPosition) ? 'color:#bbbbbb' : ''"
                >
                  {{ item.Name }}
                </div>
                <div
                  class="presuppositionIcon"
                  v-if="presetSetUp(item.$.token)"
                  :style="existencePreset(item.PTZPosition) ? 'color:#bbbbbb' : ''"
                >
                  <div class="IconBox">
                    <i
                      class="el-icon-more"
                      title="功能"
                      @click="openPreset = item.$.token"
                    ></i>
                  </div>
                </div>
                <div class="presuppositionIcon" v-else>
                  <div class="IconBox">
                    <i
                      class="el-icon-s-tools"
                      title="设置"
                      @click="setPreset(item)"
                      :style="
                        existencePreset(item.PTZPosition) ? 'color:#bbbbbb' : ''
                      "
                    ></i>
                  </div>
                  <!-- <div class="IconBox">
                    <i
                      title="删除"
                      @click="removePreset(item)"
                      class="el-icon-error"
                    ></i>
                  </div> -->
                  <div class="IconBox">
                    <i
                      v-if="!existencePreset(item.PTZPosition)"
                      title="前往"
                      @click="gotoPreset(item)"
                      class="el-icon-s-promotion"
                    ></i>
                  </div>
                </div>
              </div>
            </el-scrollbar>
          </div>
        </div>
        <div class="monitorShowBox" id="monitorBox">
          <div
            v-for="item in displaysNumber"
            :key="item"
            :style="monitorNumberStyle(canvasStyle)"
            class="monitorRevealBox"
            :class="
              selectMonitor(videoData[item - 1] ? videoData[item - 1].id : false)
            "
            @dragover.prevent="allowDrop($event)"
            @drop="drop(item - 1)"
          >
            <div
              v-if="videoPlay(item - 1)"
              @click="selectVideo(videoData[item - 1].id)"
            >
              <canvasVideo
                :videoId="`video-canvas` + videoData[item - 1].id"
                :canvasData="videoData[item - 1]"
                :width="canvasStyle.canvasWidth"
                :height="canvasStyle.canvasHeight"
              ></canvasVideo>
            </div>
            <div v-else>请添加摄像头{{ item }}</div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    import canvasVideo from "../../components/videoPage/canvasVideo"; //视频测试页
    import elementResizeDetectorMaker from "element-resize-detector"; //element元素宽高变化
    export default {
      name: "monitorPage",
      components: {
        canvasVideo
      },
      data() {
        return {
          videoData: [""], //视频的数组
          monitorName: "", //查询的设备名称
          canvasStyle: {
            //摄像头样式
            canvasWidth: `0px`,
            canvasHeight: `0px`
          },
          //摄像头方向数据
          directionData: [
            "左上",
            "向上",
            "上右",
            "向左",
            "旋转",
            "向右",
            "左下",
            "向下",
            "下右"
          ],
          //设备功能数据
          equipmentOperationData: ["放大", "缩小", "左旋", "右旋"],
          controlShow: false, //控制台显影
          //摄像头列表
          videoList: [
            {
              id: 90,
              name: `admin`,
              password: `Szzgkon2016`,
              ip: `192.168.1.50`,
              flow: "ch1"
            },
            {
              id: 91,
              name: `admin`,
              password: `Szzgkon@2016`,
              ip: `192.168.1.51`,
              flow: "ch1"
            },
            {
              id: 92,
              name: `admin`,
              password: `Szzgkon2016`,
              ip: `192.168.1.50`,
              flow: "ch1"
            },
            {
              id: 93,
              name: `admin`,
              password: `Szzgkon@2016`,
              ip: `192.168.1.51`,
              flow: "ch1"
            },
            {
              id: 94,
              name: `admin`,
              password: `Szzgkon2016`,
              ip: `192.168.1.50`,
              flow: "ch1"
            },
            {
              id: 95,
              name: `admin`,
              password: `Szzgkon@2016`,
              ip: `192.168.1.51`,
              flow: "ch1"
            },
            {
              id: 96,
              name: `admin`,
              password: `Szzgkon2016`,
              ip: `192.168.1.50`,
              flow: "ch1"
            },
            {
              id: 97,
              name: `admin`,
              password: `Szzgkon@2016`,
              ip: `192.168.1.51`,
              flow: "ch1"
            },
            {
              id: 98,
              name: `admin`,
              password: `Szzgkon2016`,
              ip: `192.168.1.50`,
              flow: "ch1"
            },
            {
              id: 99,
              name: `admin`,
              password: `szzgkon@2016`,
              ip: `192.168.0.55`,
              flow: "ch33"
            }
          ],
          displaysType: ["1*1", "2*2", "3*2", "3*3"], //摄像头的展示数量
          displaysTypeIndex: 0, //选中的摄像头展示数量
          displaysNumber: 0, //展示摄像头的数量
          moveData: {}, //拖拽的数据
          moveTimer: null, //定时器
          moveSpeed: 75, //默认移动速度
          selectMonitorID: null, //选中的摄像头
          device: { xaddr: "", user: "", pass: "" }, //当前选中的摄像头数据
          presuppositionData: [], //预测点数据
          openPreset: null, //设置预置点
          timeout: 10 //摄像头旋转时间
        };
      },
      mounted() {
        //添加element动态改变摄像展示页大小
        this.erd = elementResizeDetectorMaker();
        let _this = this;
        _this.$nextTick(() => {
          _this.erd.listenTo(document.getElementById("monitorBox"), element => {
            let timer = setTimeout(function() {
              if (!_this.checkFull()) {
                _this.canvasStyleChange();
              }
            }, 100);
          });
        });
        _this.displaysNumber = 1;
        this.$socket.open();//局部引入socket
      },
      beforeDestroy() {
        this.$socket.close();//退出组件时关闭socket
      },
      sockets: {
        // 连接后台socket
        connect() {
          console.log("socket 连接成功");
        },
        //获取测试数据
        devicePresupposition(data) {
          this.presuppositionData = data.GetPresetsResponse.Preset;
        }
      },
      watch: {
        selectMonitorID(nval, oval) {
          let deviceData = this.videoList.filter(item => {
            return item.id == this.selectMonitorID;
          });
          this.device = {
            xaddr: `http://${deviceData[0].ip}/onvif/device_service`,
            user: deviceData[0].name,
            pass: deviceData[0].password
          };
        }
      },
      methods: {
        //摄像头旋转
        rotatePreset(type) {
          let speedX;
          if (type == "左") {
            speedX = -this.moveSpeed / 100;
          } else {
            speedX = this.moveSpeed / 100;
          }
          this.$socket.emit("rotatePreset", this.device, speedX, this.timeout);
        },
        //预置点是否存在
        existencePreset(item) {
          let x = item.PanTilt.$.x;
          let y = item.PanTilt.$.y;
          let z = item.Zoom.$.x;
          if (x == y && z == 0) {
            return true;
          } else {
            return false;
          }
        },
        //设置预设点
        setPreset(data) {
          this.openPreset = null;
          this.$socket.emit("setPreset", this.device, data);
        },
        //删除预置点
        removePreset(data) {
          this.openPreset = null;
          this.$socket.emit("removePreset", this.device, data);
        },
        //前往预设点
        gotoPreset(data) {
          this.openPreset = null;
          this.$socket.emit("gotoPreset", this.device, data);
        },
        //打开预置点功能区
        presetSetUp(token) {
          if (this.openPreset == token) {
            return false;
          } else {
            return true;
          }
        },
        //设备操作按钮
        equipmentOperation(e, index) {
          if (!this.selectMonitorID) {
            this.$message({
              message: "尚未选择摄像头",
              type: "warning"
            });
            return;
          }
          if (index == 0 || index == 1) {
            this.operatioDirection(index);
            this.moveTimer = setInterval(() => {
              this.operatioDirection(index);
            }, 500);
          } else {
            if (index == 2) {
              this.rotatePreset("左");
            } else if (index == 3) {
              this.rotatePreset("右");
            }
          }
        },
        //设备操作
        operatioDirection(index) {
          let z = 1;
          if (index == 0) {
            z = this.moveSpeed / 100;
          } else if (index == 1) {
            z = -this.moveSpeed / 100;
          }
          let speed = { x: 0, y: 0, z: z };
          this.$socket.emit("move", this.device, speed);
        },
        //按下移动摄像头
        move(e, index) {
          if (!this.selectMonitorID) {
            this.$message({
              message: "尚未选择摄像头",
              type: "warning"
            });
            return;
          }
          this.moveDirection(index);
          this.moveTimer = setInterval(() => {
            this.moveDirection(index);
          }, 500);
        },
        //释放停止移动摄像头
        end(type) {
          if (!this.selectMonitorID) {
            return;
          }
          if (!this.moveTimer) {
            return;
          }
          window.clearInterval(this.moveTimer);
          this.moveTimer = null;
          this.$socket.emit("stop", this.device);
        },
        //获取预设点
        getPresupposition() {
          this.$socket.emit("presupposition", this.device);
        },
        //摄像头移动的方向
        moveDirection(type) {
          let x = 0;
          let y = 0;
          if (type == 0) {
            x = -this.moveSpeed / 100;
            y = this.moveSpeed / 100;
          } else if (type == 1) {
            y = this.moveSpeed / 100;
          } else if (type == 2) {
            x = this.moveSpeed / 100;
            y = this.moveSpeed / 100;
          } else if (type == 3) {
            x = -this.moveSpeed / 100;
          } else if (type == 5) {
            x = this.moveSpeed / 100;
          } else if (type == 6) {
            x = -this.moveSpeed / 100;
            y = -this.moveSpeed / 100;
          } else if (type == 7) {
            y = -this.moveSpeed / 100;
          } else if (type == 8) {
            x = this.moveSpeed / 100;
            y = -this.moveSpeed / 100;
          }
          let speed = { x: x, y: y, z: 0 };
          this.$socket.emit("move", this.device, speed);
        },
        //当前选中的摄像头
        selectMonitor(id) {
          if (id == this.selectMonitorID) {
            return "selectMonitorClass";
          }
        },
        //点击选中的摄像头
        selectVideo(id) {
          this.selectMonitorID = id;
          this.getPresupposition();
        },
        //拖拽触发
        drag(item) {
          this.moveData = item;
        },
        //拖拽时触发
        allowDrop(e) {},
        //拖拽释放
        drop(index) {
          this.monitorModify(this.moveData, index);
        },
        //播放的摄像位置
        playMonitor(id) {
          let num;
          this.videoData.forEach((item, index) => {
            if (item.id == id) {
              num = index;
            }
          });
          return num + 1;
        },
        // 判断全屏
        checkFull() {
          //判断浏览器是否处于全屏状态 (需要考虑兼容问题)
          //火狐浏览器
          let isFull =
            document.mozFullScreen ||
            document.fullScreen ||
            //谷歌浏览器及Webkit内核浏览器
            document.webkitIsFullScreen ||
            document.webkitRequestFullScreen ||
            document.mozRequestFullScreen ||
            document.msFullscreenEnabled;
          if (isFull === undefined) {
            isFull = false;
          }
          return isFull;
        },
        //该视频是否正在播放
        monitorPlay(id) {
          let play = false;
          this.videoData.forEach(item => {
            if (item.id == id) {
              play = true;
            }
          });
          return play;
        },
        //当前播放的视频
        videoPlay(index) {
          let play = false;
          if (this.videoData[index]) {
            play = true;
          }
          if (this.videoData[index] == "") {
            play = false;
          }
          return play;
        },
        //操作摄像头
        monitorControl(item) {
          let _this = this;
          if (_this.monitorPlay(item.id)) {
            _this.monitorClose(item.id);
          } else {
            _this.monitorData(item);
          }
        },
        //查询摄像头
        queryEquipment() {
          console.log("查询" + this.monitorName);
        },
        //摄像头切换
        pageChange(video) {
          let params = [];
          video.forEach(item => {
            params.push({
              id: item.id,
              ip: item.ip,
              rtsp: `rtsp://${item.name}:${item.password}@${item.ip}:554/h264/${item.flow}/sub/av_stream`
            });
          });
          this.axios
            .post("http://localhost:3120/open", params)
            .then(req => {
              let data = req.data;
              this.videoData = [];
              data.forEach(item => {
                this.videoData.push({ id: item.id, port: item.port });
              });
            })
            .catch(err => {
              console.log(err);
            });
        },
        //传递摄像头数据
        monitorData(video) {
          let play = true;
          for (let i = 0; i < this.videoData.length; i++) {
            if (this.videoData[i] == "") {
              play = false;
              break;
            }
          }
          if (play && this.videoData.length == this.displaysNumber) {
            this.$message({
              message: "页面摄像头数已满,请切换摄像头或关闭摄像头后再添加",
              type: "warning"
            });
            return;
          }
          let params = [];
          params.push({
            id: video.id,
            ip: video.ip,
            rtsp: `rtsp://${video.name}:${video.password}@${video.ip}:554/h264/${video.flow}/sub/av_stream`
          });
          console.log("params", params);
          this.selectMonitorID = video.id;
          this.axios
            .post("http://localhost:3120/open", params)
            .then(req => {
              let data = req.data;
              let video = true;
              for (let i = 0; i < this.videoData.length; i++) {
                if (this.videoData[i] == "") {
                  this.videoData.splice(i, 1, data[0]);
                  video = false;
                  break;
                }
              }
              if (video) {
                this.videoData = this.videoData.concat(data);
              }
              this.getPresupposition();
            })
            .catch(err => {
              console.log(err);
            });
        },
        //关闭视频
        monitorClose(id) {
          for (let i = 0; i < this.videoData.length; i++) {
            if (this.videoData[i].id == id) {
              this.videoData.splice(i, 1, "");
              break;
            }
          }
          this.selectMonitorID = null;
          this.presuppositionData = [];
        },
        //切换视频
        monitorModify(item, index) {
          let rtsp = `rtsp://${item.name}:${item.password}@${item.ip}:554/h264/${item.flow}/sub/av_stream`;
          let params = { id: item.id, ip: item.ip, rtsp: rtsp };
          this.selectMonitorID = item.id;
          this.axios
            .post("http://localhost:3120/modify", params)
            .then(req => {
              let data = req.data;
              this.videoData.splice(index, 1, "");
              this.videoData.forEach((item, index) => {
                if (item.id == data.id) {
                  this.videoData.splice(index, 1, "");
                }
              });
              this.$nextTick(() => {
                this.videoData.splice(index, 1, {
                  id: data.id,
                  port: data.port
                });
                this.getPresupposition();
              });
            })
            .catch(err => {
              console.log(err);
            });
        },
        //选中的摄像头展示类型
        displaysTypeStyle(index) {
          if (this.displaysTypeIndex == index) {
            return "selectDisplaysType";
          }
        },
        //摄像头的数量类型
        displaysTypeClick(index) {
          this.displaysTypeIndex = index;
          if (this.displaysTypeIndex == 0) {
            this.displaysNumber = 1;
          } else if (this.displaysTypeIndex == 1) {
            this.displaysNumber = 4;
          } else if (this.displaysTypeIndex == 2) {
            this.displaysNumber = 6;
          } else {
            this.displaysNumber = 9;
          }
          this.videoPlayNum(this.displaysNumber);
          this.canvasStyleChange();
        },
        //切换摄像头数量
        videoPlayNum(num) {
          let _this = this;
          let video = JSON.parse(JSON.stringify(_this.videoData));
          _this.videoData = [];
          this.$nextTick(() => {
            for (let i = 0; i < video.length; i++) {
              if (video[i] != "") {
                this.videoData.push(video[i]);
                if (_this.videoData.length == num) {
                  break;
                }
              }
            }
            for (let i = 0; i < num; i++) {
              if (_this.videoData.length == num) {
                break;
              } else {
                _this.videoData.push("");
              }
            }
          });
        },
        //可展示的摄像组盒子
        monitorNumberStyle(canvasStyle) {
          let style;
          const monitorBox = document.getElementById("monitorBox");
          let width = monitorBox.getBoundingClientRect().width - 2;
          let height = monitorBox.getBoundingClientRect().height - 2;
          if (this.displaysTypeIndex == 0) {
            style = `width:${width}px; height:${height}px;`;
          } else if (this.displaysTypeIndex == 1) {
            style = `width:${width / 2}px; height:${height / 2}px;`;
          } else if (this.displaysTypeIndex == 2) {
            style = `width:${width / 3}px; height:${height / 2}px;`;
          } else {
            style = `width:${width / 3}px; height:${height / 3}px;`;
          }
          return style;
        },
        //摄像头展示数量
        monitorBoxStyle(canvasStyle) {
          return `width:${canvasStyle.canvasWidth}px; height:${canvasStyle.canvasHeight}px;float:left`;
        },
        //摄像页面宽高变化
        canvasStyleChange() {
          const monitorBox = document.getElementById("monitorBox");
          let width = monitorBox.getBoundingClientRect().width;
          let height = monitorBox.getBoundingClientRect().height;
          if (this.displaysTypeIndex == 0) {
            this.canvasStyle = {
              canvasWidth: width - 4 + `px`,
              canvasHeight: height - 4 + `px`
            };
          } else if (this.displaysTypeIndex == 1) {
            this.canvasStyle = {
              canvasWidth: (width - 6) / 2 + `px`,
              canvasHeight: (height - 6) / 2 + `px`
            };
          } else if (this.displaysTypeIndex == 2) {
            this.canvasStyle = {
              canvasWidth: (width - 8) / 3 + `px`,
              canvasHeight: (height - 6) / 2 + `px`
            };
          } else {
            this.canvasStyle = {
              canvasWidth: (width - 8) / 3 + `px`,
              canvasHeight: (height - 8) / 3 + `px`
            };
          }
        }
      }
    };
    </script>
    
    <style lang="less" scoped>
    .box(@width:"100%",@height:"100%",@size:"14px") {
      width: @width;
      height: @height;
      font-size: @size;
    }
    .textBox(@width,@height,@align:center) {
      width: @width;
      height: @height;
      line-height: @height;
      text-align: @align;
    }
    .monitorPageBox {
      display: flex;
      width: 100%;
      height: 87vh;
      overflow: auto;
      min-width: 1500px;
      background-color: #edf0ef;
      .controlHide {
        flex: 0.05;
        background-color: #fff;
        margin-right: 15px;
        border-radius: 5px;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      .monitorList {
        flex: 0.8;
        background-color: #fff;
        margin-right: 15px;
        border-radius: 5px;
        padding: 20px 10px;
        .monitorListTitleBox {
          margin-bottom: 0.677rem;
          display: flex;
          .monitorListTitle {
            font-size: 16px;
            user-select: none;
            flex: 9;
          }
          .monitorListIcon {
            flex: 1;
          }
        }
    
        .displaysTypeBox {
          .box(280px, 32px);
          margin-bottom: 0.677rem;
          display: flex;
          .displaysNumber {
            border: 1px solid #efefef;
            font-size: 0.73rem;
            flex: 1;
            text-align: center;
            line-height: 1.67rem;
          }
          .selectDisplaysType {
            background-color: #05c399;
            color: #fff;
          }
        }
        .searchInputBox {
          .box(280px, 32px);
          margin-bottom: 0.677rem;
        }
        .monitorEquipmentDataBox {
          height: calc(100% - 450px);
          .monitorEquipmentBox {
            font-size: 0.731rem;
            color: rgba(78, 82, 79, 1);
            line-height: 1.562rem;
            padding-left: 15px;
            .box(100%, 32px);
            display: flex;
            .monitorIPText {
              flex: 9;
            }
            .monitorControlBox {
              flex: 1;
            }
          }
        }
        .controlBox {
          display: flex;
          .directionBox {
            flex: 1.5;
            .box(150px, 150px);
            .directionButton {
              .textBox(50px, 50px);
              background-color: #edf0ef;
              cursor: pointer;
              border-radius: 50%;
              float: left;
              border: 1px #05c399 solid;
              -webkit-user-select: none;
              -moz-user-select: none;
              -ms-user-select: none;
              user-select: none;
            }
          }
          .focusingBox {
            flex: 1;
            .equipmentOperationBox {
              .textBox(50px, 50px);
              background-color: #edf0ef;
              cursor: pointer;
              border-radius: 50%;
              float: left;
              border: 1px #05c399 solid;
              -webkit-user-select: none;
              -moz-user-select: none;
              -ms-user-select: none;
              user-select: none;
            }
            .rotateTimeoutBox {
              .box(100px, 50px);
            }
            /deep/ .el-input-number__decrease {
              width: 20px;
            }
            /deep/ .el-input-number__increase {
              width: 20px;
            }
            /deep/ .el-input__inner {
              width: 100px;
              padding: 0;
            }
          }
        }
        .speedSliderBox {
        }
        .presuppositionBox {
          height: 150px;
          overflow: auto;
          .presupposition {
            padding: 0 15px;
            display: flex;
            .presuppositionText {
              flex: 1;
              font-size: 0.731rem;
              color: rgba(78, 82, 79, 1);
              line-height: 1.562rem;
            }
            .presuppositionIcon {
              flex: 1;
              .IconBox {
                .box(30px, 30px);
                padding: 7px;
                cursor: pointer;
                float: right;
              }
            }
          }
        }
      }
      .monitorShowBox {
        flex: 4;
        background-color: #fff;
        border-radius: 5px;
        overflow: hidden;
        border: 1px #333 solid;
        .monitorRevealBox {
          float: left;
          border: 1px solid #333;
        }
        .selectMonitorClass {
          border: 1px solid red;
        }
      }
    }
    </style>
    <style>
    .searchInputBox .el-input__inner {
      background: #fff;
      border-radius: 0.206rem;
      height: 1.67rem;
    }
    </style>
    <style>
    .el-slider__runway.disabled .el-slider__bar {
      background-color: #05c399;
    }
    .el-slider__button {
      background: #fff;
      border: #05c399 2px solid;
    }
    .el-slider__bar {
      background-color: #05c399;
    }
    .el-slider__runway {
      background-color: #edf0ef;
    }
    </style>
    

    vue子组件页面

    <template>
      <div class="video" ref="vcontainer" @dblclick="toggleFullscreen()">
        <canvas
          class="video__player"
          :id="videoId"
          :style="`width:${width};height:${height}`"
          >您的浏览器暂不支持Canvas,请更换浏览器后再试</canvas
        >
      </div>
    </template>
    
    <script>
    import maskBox from "../layout/maskBox"; //弹窗层
    export default {
      name: "canvasVideo",
      components: { maskBox },
      props: ["canvasData", "videoId", "width", "height"], //传入的视频连接
      data() {
        return {
          players: null //视频播放器
        };
      },
      mounted() {
        this.start();
      },
      destroyed() {
        this.players.destroy();
      },
      methods: {
        //加载视频
        start() {
          const canvas = document.getElementById(this.videoId);
          let urls = `ws://172.16.10.81:` + this.canvasData.port;
          this.players = new JSMpeg.Player(urls, {
            canvas: canvas,
            autoplay: true
          });
        },
        FontChart(res) {
          //获取到屏幕的宽度
          let clientWidth =
            window.innerWidth ||
            document.documentElement.clientWidth ||
            document.body.clientWidth;
          if (!clientWidth) return; //报错拦截:
          let fontSize = 1;
          if (clientWidth > 1920) fontSize = clientWidth / 1920;
          return res * fontSize;
        },
        //全屏播放
        toggleFullscreen() {
          const isFullscreen = document.webkitIsFullScreen || document.fullscreen;
          const canvas = document.getElementById(this.videoId);
          if (isFullscreen) {
            const exitFunc =
              document.exitFullscreen || document.webkitExitFullscreen;
            exitFunc.call(document);
            canvas.style = `width:${this.width};height:${this.height}`;
            window.onresize = "";
          } else {
            const element = this.$refs.vcontainer;
            const fullscreenFunc =
              element.requestFullscreen || element.webkitRequestFullScreen;
            fullscreenFunc.call(element);
            canvas.style = "";
            this.windowOnresize();
          }
        },
        //添加全屏监控事件
        windowOnresize() {
          const canvas = document.getElementById(this.videoId);
          let _this = this;
          window.onresize = () => {
            if (!_this.checkFull()) {
              canvas.style = `width:${_this.width};height:${_this.height}`;
              window.onresize = "";
            }
          };
        },
        // 判断全屏
        checkFull() {
          //判断浏览器是否处于全屏状态 (需要考虑兼容问题)
          //火狐浏览器
          let isFull =
            document.mozFullScreen ||
            document.fullScreen ||
            //谷歌浏览器及Webkit内核浏览器
            document.webkitIsFullScreen ||
            document.webkitRequestFullScreen ||
            document.mozRequestFullScreen ||
            document.msFullscreenEnabled;
          if (isFull === undefined) {
            isFull = false;
          }
          return isFull;
        }
      }
    };
    </script>
    <style scoped>
    .video {
      position: relative;
      width: 100%;
      height: 100%;
    }
    .video__player {
      width: 100%;
      height: 100%;
      display: flex;
    }
    </style>
    

    Node页

    //导入子进程模块
    const child_process = require('child_process');
    const exec = child_process.exec;
    const cors = require('cors')
    const bodyParser = require('body-parser');
    const express = require('express');
    const onvif = require('node-onvif');
    const app = express();
    // 开启socket服务
    let server = app.listen(3120, () => {
        console.log("服务器3120启动");
    })
    // socket 初始化
    const io = require("socket.io")(server, { cors: true })
    app.use(cors());
    app.use(bodyParser.urlencoded({ extended: false }))
    app.use(bodyParser.json())
    var portId = 9000//起始的端口号
    var openArr = []//正在工作的摄像机组
    var onLinePort = []//在线的端口
    //新增摄像机
    app.post('/open', function (req, res) {
        var videoArr = []
        req.body.forEach((item) => {
            let portNum = null
            for (var i = 0; i < openArr.length; i++) {
                if (openArr[i].ip == item.ip) {
                    portNum = openArr[i].portId
                    break
                }
            }
            let port = portNum ? portNum : gainPortNum(item.id, item.ip, item.rtsp)
            videoArr.push({ id: item.id, port: port + 1 })
        })
        res.json(videoArr);
    });
    //切换摄像机
    app.post('/modify', function (req, res) {
        let portNum = null
        for (var i = 0; i < openArr.length; i++) {
            if (openArr[i].ip == req.body.ip) {
                portNum = openArr[i].portId
                break
            }
        }
        let port = portNum ? portNum : gainPortNum(req.body.id, req.body.ip, req.body.rtsp)
        res.json({ id: req.body.id, port: port + 1 });
    });
    
    //connection为自带的方法,类似生命周期里面的创建,连接后就会触发
    io.on("connection", function (socket) {
        console.log('一个用户与服务器建立连接', socket.handshake.query.id.toString())
        socket.join(socket.handshake.query.id.toString());
        // 接收到移动摄像头指令
        socket.on("move", function (deviceData, speed) {
            deviceMove(deviceData, speed)
        })
        // 接收到停止移动摄像头指令
        socket.on("stop", function (deviceData) {
            deviceStop(deviceData)
        })
        // 旋转摄像头指令
        socket.on("rotatePreset", function (deviceData, speedX, timeout) {
            deviceRotate(deviceData, speedX, timeout)
        })
        //摄像头预置点设置
        socket.on("setPreset", function (deviceData, data) {
            let device = new onvif.OnvifDevice({
                xaddr: deviceData.xaddr,
                user: deviceData.user,
                pass: deviceData.pass
            });
            device.init().then(() => {
                let ptz = device.services.ptz;
                if (!ptz) {
                    throw new Error('当前ONVIF网络摄像机不支持云台服务');
                }
                let profile = device.getCurrentProfile();
                let params = {
                    'ProfileToken': profile['token'],
                    'PresetToken': data.$.token,
                };
                device.services.ptz.setPreset(params).then((result) => {
                    let params2 = {
                        'ProfileToken': profile['token']
                    };
                    device.services.ptz.getPresets(params2).then((result) => {
                        socket.emit('devicePresupposition', result['data'])
                    }).catch((error) => {
                        console.error(error);
                    });
                }).catch((error) => {
                    console.error(error);
                });
            }).catch((error) => {
                console.error(error);
            });
        })
        //摄像头删除预置点
        socket.on("removePreset", function (deviceData, data) {
            removeDevicePreset(deviceData, data)
        })
        //摄像头前往预置点
        socket.on("gotoPreset", function (deviceData, data) {
            gotoDevicePreset(deviceData, data)
        })
        // 接收到获取预设点指令
        socket.on("presupposition", function (deviceData) {
            let device = new onvif.OnvifDevice({
                xaddr: deviceData.xaddr,
                user: deviceData.user,
                pass: deviceData.pass
            });
            device.init().then(() => {
                let ptz = device.services.ptz;
                if (!ptz) {
                    throw new Error('当前ONVIF网络摄像机不支持云台服务');
                }
                let profile = device.getCurrentProfile();
                let params = {
                    'ProfileToken': profile['token']
                };
                device.services.ptz.getPresets(params).then((result) => {
                    socket.emit('devicePresupposition', result['data'])
                }).catch((error) => {
                    console.error(error);
                });
            }).catch((error) => {
                console.error("大错误", error);
            });
        })
        // 当关闭连接后触发 disconnect 事件
        socket.on('disconnect', function () {
            console.log(socket.handshake.query.id.toString(), '与服务器断开连接');
        });
    })
    
    //新增视频流
    function videoAdd(id, ip, rtsp, portNumber) {
        var websocket = `node websocket-relay.js supersecret ${portNumber} ${portNumber + 1}`
        var ffmpeg = `ffmpeg -rtsp_transport tcp -i ${rtsp} -s 1280x720 -c copy -q 0 -map 0:0 -f mpegts -codec:v mpeg1video http://127.0.0.1:${portNumber}/supersecret`
        var websocket = execute('websocket', websocket, portNumber + 1);
        var ffmpeg = execute('ffmpeg', ffmpeg, portNumber)
        openArr.push({ ip: ip, id: id, portId: portNumber, ffmpeg: ffmpeg })
        onLinePort.push(portNumber + 1)
        onLinePort = onLinePort.sort((n1, n2) => { return n1 - n2; })
    }
    //获取使用的端口
    function gainPortNum(id, ip, rtsp) {
        var port = (portId - 9000) / 2
        var portNum
        if (onLinePort.length != port) {
            var startNum = 8999
            for (var i = 0; i < onLinePort.length; i++) {
                if (startNum == onLinePort[i] - 2) {
                    startNum = onLinePort[i]
                    portNum = startNum + 1
                } else {
                    portNum = startNum + 1
                    break
                }
            }
        } else {
            portNum = portId
            portId = portId + 2
        }
        videoAdd(id, ip, rtsp, portNum)
        return portNum
    }
    /**
     * 执行cmd命令
     * @param {*} cmd 传入的cmd
     */
    function execute(type, cmd, port) {
        var last = exec(cmd);
        last.stdout.on('data', function (data) {
            console.log(type + port + ' : ' + data);
            if (data.length == 6) {
                openArr.forEach((item, index) => {
                    if (item.portId == (port - 1)) {
                        item.ffmpeg.stdin.write('q');
                        openArr.splice(index, 1);
                    }
                })
            }
        });
        last.on('exit', function (code) {
            console.log(type + port + '已关闭,代码:' + code);
            if (type == 'websocket') {
                onLinePort.forEach((item, index) => {
                    if (item == port) {
                        onLinePort.splice(index, 1);
                    }
                })
                console.log("在线端口", onLinePort)
                if (onLinePort.length == 0) {
                    portId = 9000
                }
            }
        });
        return last
    }
    //移动和缩放摄像头
    function deviceMove(deviceData, speed) {
        let device = new onvif.OnvifDevice({
            xaddr: deviceData.xaddr,
            user: deviceData.user,
            pass: deviceData.pass
        });
    
        device.init().then(() => {
            let ptz = device.services.ptz;
            if (!ptz) {
                throw new Error('当前ONVIF网络摄像机不支持云台服务');
            }
            return device.ptzMove({
                'speed': {
                    x: speed.x, // Speed of pan (in the range of -1.0 to 1.0)
                    y: speed.y, // Speed of tilt (in the range of -1.0 to 1.0)
                    z: speed.z  // Speed of zoom (in the range of -1.0 to 1.0)
                },
                'timeout': 1 // seconds
            });
        }).catch((error) => {
            console.error(error);
        });
    }
    //停止摄像头移动
    function deviceStop(deviceData) {
        let device = new onvif.OnvifDevice({
            xaddr: deviceData.xaddr,
            user: deviceData.user,
            pass: deviceData.pass
        });
        device.init().then(() => {
            let ptz = device.services.ptz;
            if (!ptz) {
                throw new Error('当前ONVIF网络摄像机不支持云台服务');
            }
            device.ptzStop().catch((error) => {
                console.error(error);
            });
        }).catch((error) => {
            console.error(error);
        });
    }
    //摄像头旋转
    function deviceRotate(deviceData, speedX, timeout) {
        let device = new onvif.OnvifDevice({
            xaddr: deviceData.xaddr,
            user: deviceData.user,
            pass: deviceData.pass
        });
        device.init().then(() => {
            let ptz = device.services.ptz;
            if (!ptz) {
                throw new Error('当前ONVIF网络摄像机不支持云台服务');
            }
            return device.ptzMove({
                'speed': { x: speedX, y: 0, z: 0 },
                'timeout': timeout
            });
        }).catch((error) => {
            console.error(error);
        });
    }
    //删除预置点
    function removeDevicePreset(deviceData, data) {
        let device = new onvif.OnvifDevice({
            xaddr: deviceData.xaddr,
            user: deviceData.user,
            pass: deviceData.pass
        });
        device.init().then(() => {
            let ptz = device.services.ptz;
            if (!ptz) {
                throw new Error('当前ONVIF网络摄像机不支持云台服务');
            }
            let profile = device.getCurrentProfile();
            let params = {
                'ProfileToken': profile['token'],
                'PresetToken': data.$.token,
            };
            device.services.ptz.removePreset(params).catch((error) => {
                console.error(error);
            });
        }).catch((error) => {
            console.error(error);
        });
    }
    //前往预置点
    function gotoDevicePreset(deviceData, data) {
        let device = new onvif.OnvifDevice({
            xaddr: deviceData.xaddr,
            user: deviceData.user,
            pass: deviceData.pass
        });
        device.init().then(() => {
            let ptz = device.services.ptz;
            if (!ptz) {
                throw new Error('当前ONVIF网络摄像机不支持云台服务');
            }
            let profile = device.getCurrentProfile();
            let params = {
                'ProfileToken': profile['token'],
                'PresetToken': data.$.token,
                'Speed': { 'x': 1, 'y': 1, 'z': 1 }
            };
            device.services.ptz.gotoPreset(params).catch((error) => {
                console.error(error);
            });
        }).catch((error) => {
            console.error(error);
        });
    }
    

    websocket-relay页

    // Use the websocket-relay to serve a raw MPEG-TS over WebSockets. You can use
    // ffmpeg to feed the relay. ffmpeg -> websocket-relay -> browser
    // Example:
    // node websocket-relay yoursecret 9081 9082
    // ffmpeg -i <some input> -f mpegts http://localhost:8081/yoursecret
    
    var fs = require('fs'),
        http = require('http'),
        WebSocket = require('ws');
    //判断输入格式是否正确
    if (process.argv.length < 3) {
        console.log(
            '输入格式: \n' +
            'node websocket-relay.js <secret> [<stream-port> <websocket-port>]'
        );
        process.exit();
    }
    var STREAM_SECRET = process.argv[2],//密码
        STREAM_PORT = process.argv[3] || 8081,//输入地址
        WEBSOCKET_PORT = process.argv[4] || 8082,//输出地址
        RECORD_STREAM = false;//是否录像
    // Websocket Server
    var socketServer = new WebSocket.Server({ port: WEBSOCKET_PORT, perMessageDeflate: false });
    socketServer.connectionCount = 0;
    var timer = null
    socketServer.on('connection', function (socket, upgradeReq) {
        //一个新的socketServer加入
        socketServer.connectionCount++;
        if (timer != null) {
            clearInterval(timer);
        }
        console.log(
            '接入一个新WebSocket',
            // (upgradeReq || socket.upgradeReq).socket.remoteAddress,
            // (upgradeReq || socket.upgradeReq).headers['user-agent'],
            '现连接数:' + socketServer.connectionCount
        );
        //一个socketServer链接断开
        socket.on('close', function (code, message) {
            socketServer.connectionCount--;
            console.log(
                '一个WebSocket断开 现连接数:' + socketServer.connectionCount
            );
            if (socketServer.connectionCount == 0) {
                timer = setInterval(() =>
                    socketClose(), 3000);
            }
        });
    });
    //是否关闭socket
    function socketClose() {
        if (socketServer.connectionCount == 0) {
            console.log('关闭流传输')
        }
    }
    //socketServer广播
    socketServer.broadcast = function (data) {
        socketServer.clients.forEach(function each(client) {
            if (client.readyState === WebSocket.OPEN) {
                client.send(data);
            }
        });
    };
    //HTTP服务器接受来自ffmpeg的输入MPEG-TS流
    var streamServer = http.createServer(function (request, response) {
        var params = request.url.substr(1).split('/');
        if (params[0] !== STREAM_SECRET) {//判断密码是否正确
            console.log(
                '流连接失败: ' + request.socket.remoteAddress + ':' +
                request.socket.remotePort + ' - 密码错误'
            );
            response.end();
        }
        //连接流成功
        response.connection.setTimeout(0);
        console.log(
            '传输的流: ' +
            request.socket.remoteAddress + ':' +
            request.socket.remotePort
        );
        //传输流数据
        request.on('data', function (data) {
            socketServer.broadcast(data);
            if (request.socket.recording) {
                request.socket.recording.write(data);
            }
        });
        //传输流结束
        request.on('end', function () {
            console.log('传输流关闭');
            if (request.socket.recording) {
                request.socket.recording.close();
            }
            process.exit();
        });
        //将流记录到本地文件
        if (RECORD_STREAM) {
            var path = 'recordings/' + Date.now() + '.ts';
            request.socket.recording = fs.createWriteStream(path);
        }
    })
    //保持套接字打开以进行流式处理
    streamServer.headersTimeout = 0;//不等待请求头
    streamServer.listen(STREAM_PORT);//创建输出流服务器
    // console.log('监听MPEG-TS流 http://127.0.0.1:' + STREAM_PORT + '/<secret>');
    // console.log('正在等待上的WebSocket连接 ws://127.0.0.1:' + WEBSOCKET_PORT + '/');
    

    相关文章

      网友评论

          本文标题:JSmpeg+ffmpeg+WebScoket实现视频监控(2)

          本文链接:https://www.haomeiwen.com/subject/wjytbrtx.html