第一步:依赖引入
海康的H5插件使用需要配合海康的H5开发包,并把开发包引入到项目中。我是将开发包放入到了项目的public文件夹中。这里有个细节需要注意要将下载下来的开发包解压后将bin文件夹中的所有内容都放入项目中!
第二步:实例创建
其实这部分的操作比较简单,海康提供的Demo文件也已经将目前海康H5插件中的功能展示的比较完善了,当然也有需要注意的地方。如果你不是完全复制海康的代码的话自己进行开发的话一定要看清楚他的插件实例中的方法传参,他的demo文件中使用了很多{a,b}这种ES6的写法,所以传参时请务必看清楚对象的属性名。
第三步:二次开发
海康的H5插件目前很多功能还并不是非常完善,例如之前使用客户端插件的云台功能在H5插件上就有所缺失。作为公司项目的海康客户端插件的完整替换,在功能上应该与之前保持一致,至少用户体验不会降级。
写在后面 (20240715)
海康的H5插件有较为明显短板
- 直播延迟较高
- 占用客户端的CPU资源。不刷新页面,离开页面之后也得不到释放(确定不是我的代码的问题),重复进入H5页面会导致资源进一步占用(不知道是否是代码书写的问题,对接文档也没有说明)。
- 引入方式较为繁琐。
问题分析
以上种种的根源问题在于海康H5插件的实现原理。插件是通过加载了FFmpeg的wasm版本,通过webworker解码监控的二进制流再推送给页面。用这种方式就无法避免较高的CPU占用以及较高的延时。
问题优化
目前为了兼容大华的摄像头,我们公司内部升级了监控的推流方案:使用 webrtc-streamer,通过webrtc来直接预览rtsp视频流,解决了延时高以及CPU较高占用率得不到释放的问题。当然也并不是完美的,由于推流工作来到了服务器上,假如有较高的并发需求就要考虑服务器的承受能力了。
部分代码(旧版)
这里我会放出小弟自己封装的H5播放器的Vue组件,哪里写的不好请大佬多多指教
<template>
<div :id="id" class="player-h5"></div>
</template>
<script>
export default {
name: "H5Player",
components: {},
props: {
split: {
default: 1,
type: Number,
},
ws: {
default: "",
require: true,
},
mode: {
//0普通模式 1高级模式
default: 1,
type: Number,
},
//openlarge和openControl不能同时开启
openEnlarge: {
default: false,
type: Boolean,
},
openControl: {
default: false,
type: Boolean,
},
id: {
default: "videoDom",
type: String,
},
},
mounted() {
this.initPlayer();
if (this.openControl && !this.controlEventIsAdd) {
let dom = document.getElementById(this.id);
dom.addEventListener("mousemove", _.throttle(this.mouseMove, 100));
dom.addEventListener("mousedown", this.mousedown, false);
dom.addEventListener("mouseup", this.mouseup, false);
this.controlEventIsAdd = true;
}
},
watch: {
ws(val) {
if ((Array.isArray(val) && val.length > 0) || val !== "") {
if (Array.isArray(val)) {
for (const index in val) {
this.play(Number(index));
}
} else {
this.play();
}
}
},
openControl(val) {
if (val && !this.controlEventIsAdd) {
let dom = document.getElementById(this.id);
dom.addEventListener("mousemove", _.throttle(this.mouseMove, 100));
dom.addEventListener("mousedown", this.mousedown, false);
dom.addEventListener("mouseup", this.mouseup, false);
this.controlEventIsAdd = true;
}
},
split(val) {
this.arrangeWindow(val);
},
},
data() {
return {
timmer: null,
curIndex: 0, //窗口下标默认为0
playerPlugin: null, //播放器实例
playWindowIndex: [],
//cursorList: [require("@/assets/img/cursor-top.png")],
carmerIsMove: false, //当前摄像头是否处于移动状态,配合mouseup事件使用
cursorType: "empty",
controlEventIsAdd: false,
};
},
methods: {
initPlayer() {
this.playerPlugin = new window.JSPlugin({
szId: this.id, //需要英文字母开头 必填
szBasePath: "./hkplayer", // 必填,引用H5player.min.js的js相对路径
iCurrentSplit: Number(this.split),
iMaxSplit: 4,
openDebug: true,
oStyle: {
border: "#343434",
borderSelect: "#FFCC00",
background: "#000",
},
});
let _this = this;
this.playerPlugin.JS_SetWindowControlCallback({
windowEventSelect: function (iWndIndex) {
//插件选中窗口回调
console.log("windowSelect callback: ", iWndIndex);
},
pluginErrorHandler: function (iWndIndex, iErrorCode, oError) {
//插件错误回调
console.log("pluginError callback: ", iWndIndex, iErrorCode, oError);
if (_this.playWindowIndex.includes(Number(iWndIndex))) {
let i = _this.playWindowIndex.indexof(Number(iWndIndex));
_this.playWindowIndex.splice(i, 1);
}
},
windowEventOver: function (iWndIndex) {
//鼠标移过回调
},
windowEventOut: function (iWndIndex) {},
windowEventUp: function (iWndIndex) {
//鼠标mouseup事件回调
},
windowFullCcreenChange: function (bFull) {
//全屏切换回调
},
firstFrameDisplay: function (iWndIndex, iWidth, iHeight) {
//首帧显示回调
console.log(`窗口${iWndIndex}加载成功,分辨率为${iWidth}*${iHeight}`);
if (!_this.playWindowIndex.includes(Number(iWndIndex))) {
_this.playWindowIndex.push(Number(iWndIndex));
}
},
performanceLack: function () {
//性能不够回调
},
});
},
play(windwoIndex) {
if (this.playerPlugin === null) return;
let { ws, mode, playerPlugin } = this;
let index = windwoIndex || playerPlugin.currentWindowIndex;
let _this = this;
let playURL;
if (Array.isArray(ws)) {
if (!windwoIndex) {
playURL = ws[0];
} else {
playURL = ws[windwoIndex];
}
} else {
playURL = ws;
}
console.log(playURL, { playURL, mode }, index);
playerPlugin
.JS_Play(playURL, { playURL, mode }, index)
.then((res) => {
console.log("play", res);
if (_this.openEnlarge) {
_this.enlarge();
}
})
.catch((err) => {
console.log("play err", err);
});
},
//电子放大
enlarge() {
let player = this.playerPlugin,
index = player.currentWindowIndex;
player.JS_EnableZoom(index).then(
() => {
//电子放大启动成功回调
},
(e) => {
console.error(e);
}
);
},
enlargeClose() {
let player = this.playerPlugin,
index = player.currentWindowIndex;
player.JS_DisableZoom(index).then(
() => {
console.log("enlargeClose success");
},
(e) => {
console.error(e);
}
);
},
//分屏操作
arrangeWindow(splitNum) {
this.playerPlugin.JS_ArrangeWindow(splitNum).then(
() => {
console.log(`arrangeWindow to ${splitNum}x${splitNum} success`);
},
(e) => {
console.error(e);
}
);
},
mouseMove(ev) {
//console.log("move");
//if (!this.openControl) return;
if (ev.target.nodeName !== "CANVAS") return;
if (
!this.playWindowIndex.includes(
Number(ev.target.attributes.wid.value)
) ||
Number(ev.target.attributes.wid.value) !==
Number(this.playerPlugin.currentWindowIndex)
)
return;
let x = ev.offsetX,
y = ev.offsetY,
playerWidth = Number(ev.target.clientWidth),
playerHeight = Number(ev.target.clientHeight);
this.cursorType = this.getMousePosition({
x,
y,
playerWidth,
playerHeight,
target: ev.target,
});
},
getMousePosition(data) {
let { x, y, playerWidth, playerHeight, target } = data;
let x1 = parseInt(playerWidth / 3);
let x2 = x1 * 2;
let y1 = parseInt(playerHeight / 3);
let y2 = y1 * 2;
//let dom = document.getElementById(this.id);
if (x < x1) {
if (y < y1) {
target.style.cursor = `url(cursor-top_left.png),pointer`;
return "left_up";
} else if (y > y1 && y < y2) {
target.style.cursor = `url(cursor-left.png),pointer`;
return "left";
} else if (y > y2) {
target.style.cursor = `url(cursor-down_left.png),pointer`;
return "left_down";
}
} else if (x > x1 && x < x2) {
if (y < y1) {
target.style.cursor = `url(cursor-top.png),pointer`;
return "up";
} else if (y > y1 && y < y2) {
target.style.cursor = `default`;
return "empty";
} else if (y > y2) {
target.style.cursor = `url(cursor-down.png),pointer`;
return "down";
}
} else if (x > x2) {
if (y < y1) {
target.style.cursor = `url(cursor-top_right.png),pointer`;
return "right_up";
} else if (y > y1 && y < y2) {
target.style.cursor = `url(cursor-right.png),pointer`;
return "right";
} else if (y > y2) {
target.style.cursor = `url(cursor-down_right.png),pointer`;
return "right_down";
}
}
},
mousedown(ev) {
if (ev.target.nodeName !== "CANVAS"||ev.button!==0) return;
console.log(ev);
if (
!this.playWindowIndex.includes(
Number(ev.target.attributes.wid.value)
) ||
Number(ev.target.attributes.wid.value) !==
Number(this.playerPlugin.currentWindowIndex)
)
return;
this.changeCursor({ type: this.cursorType, target: ev.target });
this.carmerIsMove = true;
console.log("down");
this.$emit("moveCamera", {
index: this.playerPlugin.currentWindowIndex,
type: this.cursorType,
});
//ev.stopPropagation()
},
mouseup(ev) {
if (!this.carmerIsMove) return;
this.changeCursor({ type: this.cursorType, target: ev.target });
this.$emit("stopCamera", {
index: this.playerPlugin.currentWindowIndex,
type: this.cursorType,
});
console.log("up");
this.carmerIsMove = false;
ev.stopPropagation();
},
changeCursor({ type, target }) {
switch (type) {
case "left_up":
target.style.cursor = `url(cursor-top_left.png),pointer`;
break;
case "left":
target.style.cursor = `url(cursor-left.png),pointer`;
break;
case "left_down":
target.style.cursor = `url(cursor-down_left.png),pointer`;
break;
case "up":
target.style.cursor = `url(cursor-top.png),pointer`;
break;
case "empty":
target.style.cursor = `default`;
break;
case "down":
target.style.cursor = `url(cursor-down.png),pointer`;
break;
case "right_up":
target.style.cursor = `url(cursor-top_right.png),pointer`;
break;
case "right_down":
target.style.cursor = `url(cursor-down_right.png),pointer`;
break;
case "right":
target.style.cursor = `url(cursor-right.png),pointer`;
break;
default:
target.style.cursor = `default`;
break;
}
},
reset() {},
/* getSelectedWindowRange(data) {
let x1, x2, y1, y2;
let { playerWidth, playerHeight, index, split } = data;
//index = index + 1;
let column = index % split,
row = parseInt(index / split),
h = parseInt(playerHeight / split),
w = parseInt(playerWidth / split);
x1 = column * w;
x2 = column * w + w;
y1 = row * h;
y2 = row * h + h;
return { x1, x2, y1, y2 };
}, */
},
};
</script>
<style lang='scss' scoped>
.player-h5 {
width: 100%;
height: 100%;
/* .cursor-top{
cursor: url('~@/assets/img/up.svg') ;
} */
}
</style>