上篇文章中分享了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播放问题集,若问题集中无法解决实际遇到的问题,可邮箱咨询。
如果对小伙伴儿有帮助,请点个小赞赞哈