vue项目使用海康视频H5palyer

上篇文章中分享了vue项目中集成海康视频web插件,但在功能上线后发现存在的一些局限性,如海康视频web插件仅适用于Windows桌面操作系统,并不能在麒麟系统上使用,并且跳转界面后视频插件的界面有时还在,影响用户体验,因此又研究了H5视频播放器的应用,H5player适用于跨平台浏览器,包括window、android、ios、银河麒麟等,并且根据自己需求调用h5player功能,样式调整简单,适用性强。

一、引入h5player@2.0开发包

首先登录海康平台下载H5视频播放器开发包 V2.5.1,其中包含demo、bin、doc,可根据demo中的使用说明进行测试,doc中有开发指南,bin中包含H5视频播放器的第三方库文件。

 接着在public文件夹下新建h5player文件夹,并将上面bin下所有文件复制到h5player文件夹下,即:(注意:一定要放在vue中的public文件夹中,否则会报错)

 最后public文件夹下的index.html中引入h5player.min.js文件 

<script type="text/javascript" src="./h5player/h5player.min.js"></script>

二、h5player应用

项目需求中存在两个应用视频的场景,第一个场景点击视频icon弹出视频界面,可实现实时预览、回放、抓图、录像、云台操作、全屏功能;第二个场景是多个视频点分页显示,每个视频点可实时预览、回放、抓图、录像、云台操作、全屏。

单个视频点功能

<template>
  <el-dialog
    :title="title"
    :visible.sync="visible"
    width="65%"
    :modal="true"
    :append-to-body="true"
    :modal-append-to-body="true"
    :close-on-click-modal="false"
    @close="$emit('update:hkPlayerDialog', false)"
  >
    <div style="display: flex; width: 100%; height: 100%">
      <div style="width: 70%; height: 100%">
	  <!-- H5播放器容器 -->
        <div style="height: 650px" id="player"></div>
		<!-- 视频操作按钮 -->
        <div
          style="
            height: 5%;
            background-color: #303133;
            justify-content: flex-end;
            display: flex;
            gap: 10px;
            padding-right: 10px;
            align-items: center;
          "
        >
          <svg
            style="width: 25px; height: 30px; color: #c0c4cc"
            v-show="!isPlaying"
            @click="play"
          >
            <svg-icon icon-class="start" />
            <title>开始</title>
          </svg>
          <svg
            style="width: 25px; height: 30px; color: #c0c4cc"
            v-show="isPlaying"
            @click="stopPlay"
          >
            <svg-icon icon-class="stop" />
            <title>停止播放</title>
          </svg>
          <svg
            style="width: 25px; height: 30px; color: #c0c4cc"
            @click="recordStart('MP4')"
            v-show="!isRecording"
          >
            <svg-icon icon-class="video11" />
            <title>开始录像</title>
          </svg>
          <svg
            style="width: 25px; height: 30px; color: red"
            @click="recordStop()"
            v-show="isRecording"
          >
            <svg-icon icon-class="video11" />
            <title>停止录像</title>
          </svg>
          <svg
            style="width: 20px; height: 25px; color: #c0c4cc"
            @click="capture('JPEG')"
          >
            <svg-icon icon-class="capture11" />
            <title>抓图</title>
          </svg>
          <svg
            style="width: 20px; height: 20px; color: #c0c4cc"
            @click="fullScreen"
          >
            <svg-icon icon-class="fullscreen11" />
            <title>全屏</title>
          </svg>
        </div>
      </div>
	  <!-- 云台操作功能 -->
      <div class="rightVideoWatvh">
        <p>云台控制</p>
        <div class="roadvideo">

          <div>
            <div></div
            ><div
              ><img
                @click="
                  move('up',0);
                  direction = 1;
                  centImg = 0;
                  beforeDire = 0;
                "
                :class="{ active: direction == 1 }"
                src="../../../assets/images/video/triangle.png" /></div
            ><div></div>
          </div>
          <div>
            <div class="leftImg"
              ><img
                @click="
                  move('left',0);
                  direction = 2;
                  centImg = 0;
                  beforeDire = 0;
                "
                :class="{ active: direction == 2 }"
                src="../../../assets/images/video/triangle.png"
            /></div>
            <div class="centImg">
              <img
                style="transform: none; width: 9px"
                @click="changeCentImg"
                v-if="centImg == 0 && direction != 0"
                src="../../../assets/images/video/puss.png"
              />
              <img
                v-else
                @click="changeCentImg"
                src="../../../assets/images/video/triangle.png"
              />
            </div>
            <div class="rightImg"
              ><img
                @click="
                  move('right',0);
                  direction = 3;
                  centImg = 0;
                  beforeDire = 0;
                "
                :class="{ active: direction == 3 }"
                src="../../../assets/images/video/triangle.png"
            /></div>
          </div>
          <div>
            <diV></diV
            ><diV class="bottomImg"
              ><img
                @click="
                  move('down',0);
                  direction = 4;
                  centImg = 0;
                  beforeDire = 0;
                "
                :class="{ active: direction == 4 }"
                src="../../../assets/images/video/triangle.png" /></diV
            ><diV></diV>
          </div>
        </div>
        <div>
          <ul class="lanren">
            <span id="count">0</span>
            <li>
              <span>速度</span>
              <div class="scale_panel">
                <div class="scale" id="bar">
                  <div class="leftWidth"></div>
                  <span id="btn"></span>
                </div>
                <div><span>0</span><span>100</span></div>
              </div>
            </li>
          </ul>
        </div>
        <div class="buttonset">
          <div>
            <span
              @mousedown="toIN = true"
              @mouseup="toIN = false"
              :class="{ active: toIN }"
              @click="move('zoom_in',0)"
              ><b>+</b>焦距变大</span
            >
            <span
              @mousedown="toOUT = true"
              @mouseup="toOUT = false"
              :class="{ active: toOUT }"
              @click="move('zoom_out',0)"
              ><b>-</b>焦距变小</span
            >
          </div>
          <br />
          <!-- 预置位按钮 -->
          <div>
            <span @click="changeCamera(1)">预置位1</span>
            <span @click="changeCamera(2)">预置位2</span>
            <span @click="changeCamera(3)">预置位3</span>
          </div>
        </div>
        <p>回放</p>
        <div style="padding: 15px 10px">
          <el-date-picker
            size="mini"
            style="width: 100%"
            v-model="value1"
            type="datetimerange"
            range-separator="-"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
          >
          </el-date-picker>
          <div style="margin-top: 20px">
            <el-button type="primary" size="mini">开始回放</el-button>
            <el-button type="primary" size="mini">暂停</el-button>
            <el-button type="primary" size="mini">恢复</el-button>
            <el-button type="primary" size="mini">停止回放</el-button>
          </div>
        </div>
      </div>
    </div>
  </el-dialog>
</template>

<script>
import { wsvideostream,hkptzoperate } from "@/api/api.js";
import moment from "moment";
export default {
  props: {
    dataInfo: {
      type: Object,
      default: null,
    },
    hkPlayerDialog: {
      type: Boolean,
    },
  },
  data() {
    return {
      direction: 0,
      centImg: 1,
      toOUT: false,
      toIN: false,
      beforeDire: 0,
      transmode: 40, //速度
      lastAvtion: "", //旋转方向
      visible: this.hkPlayerDialog,
      value1: "",
      previewUrl: "",
      playbackUrl: "",
      player: null,
      isPlaying: false,
      isRecording: false,
      command: '',
      title: ''
    };
  },
  methods: {
//获取ws视频流
    getwsUrl() {
      wsvideostream({
        cameraIndexCode: this.dataInfo.cameraIndexCode,
      }).then((res) => {
        if (200 == res.code) {
          this.previewUrl = res.data;
          this.play();
        }
      });
    },
//初始化播放器
    createPlayer(option) {
      window.addEventListener("resize", () => {
        setTimeout(() => {
          this.player.JS_Resize();
        }, 100);
      });
      this.player = new window.JSPlugin({
        szId: "player",
        szBasePath: "./h5player/",
        iMaxSplit: 1,
        iCurrentSplit: 2, //2
        oStyle: {
          // border: '#0bc4db',
          // borderSelect: '#FFCC00',
          // background: '#fff'
        },
      });
      // 事件回调绑定
      this.initPlugin();
    },

    /**
     * 事件初始化
     */
    initPlugin() {
      this.player.JS_SetWindowControlCallback({
        windowEventSelect: function (iWndIndex) {
          //插件选中窗口回调
          console.log("windowSelect callback: ", iWndIndex);
        },
        pluginErrorHandler: function (iWndIndex, iErrorCode, oError) {
          //插件错误回调
          console.log("pluginError callback: ", iWndIndex, iErrorCode, oError);
        },
        windowEventOver: function (iWndIndex) {
          //鼠标移过回调
          //console.log(iWndIndex);
        },
        windowEventOut: function (iWndIndex) {
          //鼠标移出回调
          //console.log(iWndIndex);
        },
        windowEventUp: function (iWndIndex) {
          //鼠标mouseup事件回调
          //console.log(iWndIndex);
        },
        windowFullCcreenChange: function (bFull) {
          //全屏切换回调
          console.log("fullScreen callback: ", bFull);
        },
        firstFrameDisplay: function (iWndIndex, iWidth, iHeight) {
          //首帧显示回调
          console.log(
            "firstFrame loaded callback: ",
            iWndIndex,
            iWidth,
            iHeight
          );
        },
        performanceLack: function (iWndIndex) {
          //性能不足回调
          console.log("performanceLack callback: ", iWndIndex);
        },
        StreamEnd: function (iWndIndex) {
          //性能不足回调
          console.log("recv StreamEnd: ", iWndIndex);
        },
        StreamHeadChanged: function (iWndIndex) {
          console.log("recv StreamHeadChanged: ", iWndIndex);
        },
        ThumbnailsEvent: (iWndIndex, eventType, eventCode) => {
          console.log(
            "recv ThumbnailsEvent: " +
              iWndIndex +
              ", eventType:" +
              eventType +
              ", eventCode:" +
              eventCode
          );
        },
        InterruptStream: (iWndIndex, iTime) => {
          console.log(
            "recv InterruptStream: " + iWndIndex + ", iTime:" + iTime
          );
        },
        ElementChanged: (iWndIndex, szElementType) => {
          //回调采用的是video还是canvas
          console.log(
            "recv ElementChanged: " +
              iWndIndex +
              ", szElementType:" +
              szElementType
          );
        },
      });
      this.getwsUrl();
    },
    /**
     * 开始播放
     */
    play(data, callback) {
      this.isPlaying = true;
      const param = {
        playURL: this.previewUrl,
        mode: 1,
      };
      let index = 0;
      this.player.JS_Play(this.previewUrl, param, index).then(
        () => {
          // this.isPlaying = true;
          console.log("播放成功");
        },
        (err) => {
          // this.isPlaying = true;
          console.log("播放失败", err);
          console.log("play fail");
        }
      );
    },
    // 停止播放
    stopPlay() {
      this.isPlaying = false;
      this.player.JS_Stop().then(
        () => {

          console.log("stop realplay success");
        },
        (e) => {
          console.error(e);
        }
      );
    },
    //开始回放
    async startPlayback() {
      //首先获取回放ws:url
      const res = await getVideoList({ factId: this.queryParams.factId });
    },
    //开始回放
    playbackStart() {
      const config = {
        playURL: this.playbackUrl,
        mode: 1,
      };
      let index = 0;
      let startTime = "2025-03-21T00:00:00" + ".000+08:00";
      let endTime = "2025-03-21T21:00:00" + ".000+08:00";
      this.player
        .JS_Play(this.playbackUrl, config, index, startTime, endTime)
        .then(
          () => {
            console.log("playbackStart success");
            // this.playback.rate = 1
          },
          (e) => {
            console.log("playbackStart fail");
            console.log(e);
          }
        );
    },
    //抓图
    capture(imageType) {
      let player = this.player,
        index = player.currentWindowIndex;

      player.JS_CapturePicture(index, "img", imageType).then(
        () => {
          console.log("capture success", imageType);
        },
        (e) => {
          console.error(e);
        }
      );
    },
    //开始录像
    recordStart(type) {
      const codeMap = { MP4: 5, PS: 2 };
      //let options = {irecordType: 1, cbStreamCB: streamcb}
      let index = this.player.currentWindowIndex;
      let fileName =
        this.dataInfo.name + moment().format("YYYY-MM-DD HH:mm:ss") + ".mp4";
      let typeCode = codeMap[type];

      this.player.JS_StartSaveEx(index, fileName, typeCode).then(
        () => {
          this.isRecording = true;
          console.log("record start ...");
        },
        (e) => {
          console.error(e);
        }
      );
    },
    recordStop() {
      let index = this.player.currentWindowIndex;

      this.player.JS_StopSave(index).then(
        () => {
          this.isRecording = false;
          console.log("record stoped, saving ...");
        },
        (e) => {
          console.error(e);
        }
      );
    },
    changeCentImg() {
      if (this.direction || this.beforeDire) {
        //非0数为真
        // 0开始 1暂停
        if (this.centImg == 0) {
          // 开始变暂停
          this.centImg = 1;
          this.beforeDire = this.direction; //暂存,记录的是上一次按钮的方向
          this.direction = 0;
        } else {
          // 暂停变开始
          this.centImg = 0;
          if (this.beforeDire) {
            this.direction = this.beforeDire; //暂存,记录的是上一次按钮的方向
          }
        }
        let dirTemp = null;
        if (this.direction === 1) {
          dirTemp = "up";
        }
        if (this.direction === 2) {
          dirTemp = "left";
        }
        if (this.direction === 3) {
          dirTemp = "right";
        }
        if (this.direction === 4) {
          dirTemp = "down";
        }
        this.centImgMove(this.centImg, dirTemp);
      }
    },
    handleClose(done) {
      if (this.v1 != undefined) {
        this.v1.disconnect();
      }
      this.hkPlayerDialog = false;
      this.$emit("cancel", {
        hkPlayerDialog: this.hkPlayerDialog,
      });
    },
    //暂停开始图标的移动行为
    centImgMove(tag, beforeDire) {
      //根据tag判断;如果tag==1表示想要暂停,直接发送暂停命令,如果是tag==0标识想要开始,则把上一次行为记录的方向发送开始命令
      if (tag === 1) {
        //暂停命令
        this.move(this.command,1);
      }
      if (tag === 0) {
        //开始命令
        console.log("beforeDire", beforeDire);
        this.move(this.command,0);
      }
    },
    // 移动
    move(command,action) {
      this.command = command;
        if (command === "zoom_in" || command === "zoom_out") {

          this.centImg = action == 0 ? 0 :1; // 使用赋值操作符来设置属性的值
          this.direction = -1; // 使用赋值操作符来设置属性的值
        }

        let params = {
          action: action, //0 开始 1暂停
          cameraIndexCode: this.dataInfo.cameraIndexCode, //h5s平台设备的token,非系统登录的token
          speed: this.transmode, //旋转速度
          command: command
        };
        hkptzoperate(params).then((res) => {
          if (res.code === 200) {
            console.log(res.data);
          }
        });
      // }
    },

    async changeCamera(presetIndex) {
    },
    fullScreen() {
      this.player.JS_FullScreenDisplay(true).then(
        () => {
          console.log(`wholeFullScreen success`);
        },
        (e) => {
          console.error(e);
        }
      );
    },
    initScale(){
      var _that = this;
    var scale = function (btn,bar,count){
        this.btn=document.getElementById(btn);
        this.bar=document.getElementById(bar);
        this.count=document.getElementById(count);
        this.step=document.getElementsByClassName("leftWidth")[0];//leftWidth的div节点元素
        console.log("this.step",this.step);
        this.init();
        this.count.innerHTML = 40
        this.step.style.width='76px';
        this.count.style.left='50%';
        this.btn.style.left=76+'px'
    };
    scale.prototype={
        init:function (){
            var f=this,g=document,b=window,m=Math;
            f.btn.onmousedown=function (e){
                var x=(e||b.event).clientX;
                var l=this.offsetLeft;
                var max=f.bar.offsetWidth-this.offsetWidth;
                g.onmousemove=function (e){
                    var thisX=(e||b.event).clientX;
                    var to=m.min(max,m.max(-2,l+(thisX-x)));
                    f.btn.style.left=to+'px';
                    f.ondrag(m.round(m.max(0,to/max)*100),to);
                    b.getSelection ? b.getSelection().removeAllRanges() : g.selection.empty();
                };
                g.onmouseup=function(e){
                  g.onmousemove=null
                  //_that.move(_that.lastAvtion)
                }
            };
        },
        ondrag:function (pos,x){
          this.step.style.width=Math.max(0,x)+'px';
          this.count.innerHTML=pos; //标记
          _that.transmode = pos
          this.count.style.left=Math.max(0,x)+25+'px';
        }
    }
    setTimeout(function(){
      //count表示当前速度是多少;bar表示整个横向滚动的滑动条;btn是滑动条的可拖动的点
      new scale('btn','bar','count');
    },200)

    }
  },
  watch: {
    //监听开启关闭状态,并改变相应值
    hkPlayerDialog() {
      this.title = this.dataInfo.name;
      this.visible = this.hkPlayerDialog;
      if (this.hkPlayerDialog == true) {
        this.initScale();
        this.$nextTick(() => {
          this.createPlayer();
        });
      } else {
        this.player = null;
      }
    },
  },
};
</script>

<style lang="scss" scoped>
/deep/.el-dialog {
  height: 740px;
  background: url(../../../assets/images/dialog.png) no-repeat center;
  background-size: 100% 100%;
  margin-left: 20%;
  //  height: 70%;
}

/deep/.el-dialog__body {
  padding: 0px 10px 10px 10px;
}

/deep/.el-dialog__header {
  padding: 10px 20px;
  text-align: left;
}
/deep/ .el-dialog__title {
  color: #fff;
}
/* // 右侧视频监测 */
.rightVideoWatvh {
  width: 30%;
  background: #344f6b;
  box-sizing: border-box;
  padding: 5px 6px;
  > .buttonset {
    margin-top: 15px;
    > div {
      display: flex;
      padding: 0 10px;
      justify-content: space-between;
      > span {
        text-align: center;
        font-size: 14px;
        font-weight: 400;
        color: #ffffff;
        width: 104px;
        height: 40px;
        background: url(../../../assets/images/video/rect.png) no-repeat center;
        background-size: 100% 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        > b {
          font-size: 20px;
          margin-top: -3px;
          margin-right: 5px;
        }
      }
      .active {
        color: #00b3fe;
      }
    }
  }
  > p {
    height: 28px;
    background: #102944;
    line-height: 28px;
    font-size: 14px;
    font-family: Microsoft YaHei;
    font-weight: bold;
    color: #ffffff;
    position: relative;
    text-indent: 20px;
  }
  > p::before {
    content: "";
    position: absolute;
    left: 13px;
    width: 3px;
    height: 15px;
    background: #ffffff;
    border-radius: 2px;
    top: 0;
    bottom: 0;
    margin: auto;
  }
}
.roadvideo {
  width: 165px;
  height: 165px;
  margin: 5px auto 5px;
  background: url(../../../assets/images/video/road.png) no-repeat;
  background-size: 100% 100%;
  > div {
    height: 55px;
    display: flex;
    > div {
      width: 55px;
      height: 55px;
      display: flex;
      align-items: center;
      justify-content: center;
      > img {
        width: 13px;
        filter: grayscale(100%) brightness(200%);
        cursor: pointer;
      }
      .active {
        filter: grayscale(0);
      }
    }
    .bottomImg {
      > img {
        transform: rotateX(180deg);
      }
    }
    .leftImg {
      > img {
        transform: rotateY(180deg) rotateZ(90deg);
      }
    }
    .centImg > img {
      margin-left: 5px;
    }
    .rightImg,
    .centImg {
      > img {
        transform: rotateY(180deg) rotateZ(-90deg);
      }
    }
  }
}
.lanren > li > span {
  color: #fff;
  margin-right: 10px;
  font-size: 14px;
}
.scale_panel > div:last-child {
  display: flex;
  justify-content: space-between;
  color: #fff;
  margin-top: 5px;
}
.scale_panel {
  color: #999;
  width: 88%;
  line-height: 18px;
}
.scale_panel .r {
  float: right;
}
.scale span {
  background: url(../../../assets/images/video/scroll.png) no-repeat;
  width: 16px;
  height: 16px;
  position: absolute;
  left: -5px;
  top: -3px;
  cursor: pointer;
  background-size: 100% 100%;
}
.scale {
  border-radius: 5px;
  background-repeat: repeat-x;
  background-position: 0 100%;
  height: 10px;
  background: #07070733;
  width: 85%;
  position: relative;
  font-size: 0px;
  border-radius: 3px;
}
.scale div {
  border-top-left-radius: 5px;
  border-bottom-left-radius: 5px;
  background-repeat: repeat-x;
  height: 10px;
  background: #626060;
  width: 0px;
  position: absolute;
  width: 0;
  left: 0;
  bottom: 0;
}
.lanren li {
  font-size: 12px;
  line-height: 50px;
  position: relative;
  height: 50px;
  list-style: none;
  display: flex;
  align-items: flex-end;
}
.lanren {
  // padding-top: 310px;
  position: relative;
  padding-left:10px
  // padding:10px;
  // margin: 10px;
}
#count {
  background: #454545;
  border-radius: 6px;
  padding: 3px 6px;
  border-radius: 6px;
  position: absolute;
  top: -15px;
  left: 41px;
  color: #fff;
}
.el-input__inner {
  background-color: transparent;
}
/deep/ .el-range-editor .el-range-input {
  background-color: transparent;
}
/deep/ .el-dialog__headerbtn .el-dialog__close{
  color: #fff;
}
/deep/ .sub-wnd{
  border-color: #596b87!important;
}
</style>

三、总结

开发过程中遇到问题,可以先去海康官网下的软件平台咨询,选择“智能问答”,其中包含H5播放问题集,若问题集中无法解决实际遇到的问题,可邮箱咨询。

如果对小伙伴儿有帮助,请点个小赞赞哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值