Vuejs集成海康威视web端视频插件问题。
目前公司有一个视频播放的需求:利用海康威视web端插件对摄像头视频进行播放(单播、多播)。
。目前问题:
1、该web插件支持直接播放rtsp格式是视频流吗?(接口返回的视频流格式是rtsp格式的)。
基于海康web端视频插件的代码封住的VueJs组件代码如下:
<template>
<div class="play_wnd_box">
<div
id="playWnd"
class="play_wnd"
></div>
<span
v-if="videoPlayStatus === 1"
class="prompt_text"
>安装插件后刷新此页面。</span>
<span
v-else-if="videoPlayStatus === 2"
class="prompt_text"
>插件未启动,正在尝试启动,请稍候...</span>
</div>
</template>
<script>
import dayjs from 'dayjs'
import { mapState } from "vuex";
import { Modal } from 'ant-design-vue';
export default {
name: "HkVideo",
expose: ['startMultiPreview', 'stopVidePreview', 'snapPic', 'hiddenVidoe', 'showVideo', 'startPreview'],
data () {
return {
oWebControl: null,
initCount: 0,
pubKey: '',
divSizeWidth: 700,
divSizeHeight: 400,
oDiv: null,
iLastCoverLeft: 0,
iLastCoverTop: 0,
iLastCoverRight: 0,
iLastCoverBottom: 0,
setTimeoutClock: null,
videoPlayStatus: 0
}
},
props: {
pointData: {
required: true,
type: Array,
default: function () {
return [{
code: "11011500581314000911",
type: '1'
}]
}
},
initData: {
required: true,
type: Object
}
},
computed: {
...mapState({
inHomeSever: state => state.app.inHomeSever
})
},
mounted () {
this.oDiv = document.getElementById("playWnd")
this.divSizeWidth = this.oDiv.clientWidth || 700
this.divSizeHeight = this.oDiv.clientHeight || 400
//根据窗口和滚动设置插件大小
window.addEventListener('resize', this.handleScroll)
window.addEventListener('scroll', this.handleScroll)
//判断是否在主应用打开,在主应用打开时不执行 initScript()
if (this.inHomeSever) {
this.initPlugin()
} else {
this.initScript()
}
},
beforeUnmount () {
window.removeEventListener('resize', this.handleScroll)
window.removeEventListener('scroll', this.handleScroll)
this.destructionVideo()
if (this.setTimeoutClock) {
clearTimeout(this.setTimeoutClock)
}
},
methods: {
//初始化加载Script标签
initScript () {
const head = document.head || document.getElementsByTagName('head')[0];
const promise1 = new Promise((resolve, reject) => {
const script1 = document.createElement('script'); //创建一个script标签
script1.src = 'https://www.xxx.com/jsencrypt.min.js'
head.appendChild(script1)
script1.onload = script1.onreadystatechange = function () {
if (!this.readyState || this.readyState === "loaded" || this.readyState === "complete") {
resolve()
}
};
});
const promise2 = new Promise((resolve, reject) => {
const script2 = document.createElement('script'); //创建一个script标签
script2.src = 'https://www.xxx.com/jsWebControl-1.0.0.min.js'
head.appendChild(script2)
script2.onload = script2.onreadystatechange = function () {
if (!this.readyState || this.readyState === "loaded" || this.readyState === "complete") {
resolve()
}
};
});
Promise.all([
promise1,
promise2
]).then(res => {
/*初始化播放器插件*/
this.initPlugin()
})
},
//初始化播放器插件
initPlugin () {
const that = this
this.oWebControl = new WebControl({
szPluginContainer: "playWnd", // 指定容器id
iServicePortStart: 15900, // 指定起止端口号,建议使用该值
iServicePortEnd: 15909,
szClassId: "23BF3B0A-2C56-4D97-9C03-0CB103AA8F11", // 用于IE10使用ActiveX的clsid
cbConnectSuccess: function () {
console.warn('初始化播放器插件成功')
that.setCallbacks()
that.oWebControl.JS_StartService("window", { // WebControl实例创建成功后需要启动服务
dllPath: "./VideoPluginConnect.dll" // 值"./VideoPluginConnect.dll"写死
}).then(function () {
that.videoPlayStatus = 0 // 启动插件服务成功
that.oWebControl.JS_CreateWnd("playWnd", 700, 400).then(
function () { //JS_CreateWnd创建视频播放窗口,宽高可设定
that.initParameter(); // 创建播放实例成功后初始化
});
}, function () {
console.log('启动插件服务失败!')
});
},
cbConnectError: function () {
that.oWebControl = null
that.videoPlayStatus = 2
console.log('插件未启动,正在尝试启动,请稍候...')
WebControl.JS_WakeUp("VideoWebPlugin://"); // 程序未启动时执行error函数,采用wakeup来启动程序
that.initCount++;
if (that.initCount < 3) {
that.setTimeoutClock = setTimeout(function () {
that.initPlugin()
}, 3000)
} else {
that.videoPlayStatus = 1
console.log(that.initData.version, '插件版本')
Modal.confirm({
title: '下载提示',
content: '未检测到视频插件V' + that.initData.version + ',是否下载安装?',
okText: '确认',
cancelText: '取消',
wrapClassName: 'hkVideo-box',
onOk () {
window.open(that.initData.pluginUrl)
},
onCancel () {
//console.log('Cancel');
},
})
}
},
cbConnectClose: function (bNormalClose) {
// 异常断开:bNormalClose = false
// JS_Disconnect正常断开:bNormalClose = true
console.log("cbConnectClose", bNormalClose)
that.oWebControl = null
}
});
},
// 反初始化
uninit () {
this.oWebControl.JS_RequestInterface({
funcName: "uninit"
}).then(function () { });
},
// 初始化参数
initParameter () {
const that = this
this.getPubKey(function () {
const buttonIDs = "0,16,256,257,258,259,260,512,513,514,515,516,517,768,769"; //自定义工具条按钮
that.oWebControl.JS_RequestInterface({
funcName: "init",
argument: JSON.stringify({
appkey: that.initData.appkey, //API网关提供的appkey
secret: that.setEncrypt(that.initData.secret), //API网关提供的secret
ip: that.initData.ip, //API网关IP地址
playMode: that.initData.playMode || 0, //初始播放模式:0-预览,1-回放
port: that.initData.port, //端口
snapDir: "D:\\SnapDir", //抓图存储路径
videoDir: "D:\\VideoDir", //紧急录像或录像剪辑存储路径
layout: that.initData.layout, //布局
enableHTTPS: 1, //是否启用HTTPS协议
encryptedFields: 'secret', //加密字段
showToolbar: 1, //是否显示工具栏
showSmart: 1, //是否显示智能信息
buttonIDs: buttonIDs //自定义工具条按钮
})
}).then(function (oData) {
// 初始化后resize一次,规避firefox下首次显示窗口后插件窗口未与DIV窗口重合问题
that.oWebControl.JS_Resize(that.divSizeWidth, that.divSizeHeight)
// that.startMultiPreview(),这里先注释不调用看看
});
});
},
//窗口布局
setLayout (num = '1x1') {
const pram = {
funcName: "setLayout",
argument: {
layout: num
}
}
this.requestInterface(pram)
},
//单个预览
startPreview () {
const pram = {
funcName: "startPreview",
argument: JSON.stringify({
cameraIndexCode: this.pointData[0].code, //监控点编号
streamMode: 1, //主子码流标识:0-主码流,1-子码流
transMode: 0, //传输协议:0-UDP,1-TCP
gpuMode: 0, //是否启用GPU硬解,0-不启用,1-启用
wndId: -1, //播放窗口序号(在2x2以上布局下可指定播放窗口)
ezvizDirect: this.pointData[0].type // 未指定或者0 非直连,其他为直连
})
}
this.requestInterface(pram)
},
//单个回放
startPlayback (startTime, endTime, wndId) {
const pram = {
funcName: "startPlayback",
argument: JSON.stringify({
cameraIndexCode: this.pointData[0].code, //监控点编号
startTimeStamp: startTime, // 录像查询开始时间戳,单位:秒
endTimeStamp: endTime, // 录像查询结束时间戳,单位:秒
recordLocation: Number(this.initData.storageStrategy), // 录像存储类型 0-中心存储 1-设备存储
transMode: 0, // 传输协议 ,0-UDP 1-TCP
gpuMode: 0, // 是否开启 GPU 硬解,0-不开启 1-开启
wndId: wndId, //播放窗口序号(在2x2以上布局下可指定播放窗口)
})
}
this.requestInterface(pram)
},
//多个窗口播放
startMultiPreview () {
const listArr = [];
this.pointData.forEach((val, index, arr) => {
listArr.push({
cameraIndexCode: val.code,
ezvizDirect: val.type,
gpuMode: 0,
streamMode: 1,
transMode: 0,
wndId: index + 1
})
})
const pram = {
funcName: "startMultiPreviewByCameraIndexCode",
argument: {
list: listArr
}
}
this.requestInterface(pram)
},
requestInterface (value) {
this.oWebControl.JS_RequestInterface(value).then(function (oData) {
console.log('HkVideo请求成功:', oData)
}).catch(err => {
console.error('HkVideo网络请求失败:', err)
// 添加更详细的错误信息
if (err.message) {
console.error('错误信息:', err.message)
}
if (err.code) {
console.error('错误代码:', err.code)
}
// 向父组件发送错误事件
this.$emit('videoPlayEvent', {
type: 'error',
error: err,
message: '网络请求失败'
})
});
},
//窗口发生变化调用
handleScroll () {
this.divSizeWidth = this.oDiv.clientWidth
this.divSizeHeight = this.oDiv.clientHeight
if (this.oWebControl != null) {
setTimeout(() => {
this.oWebControl.JS_Resize(this.divSizeWidth, this.divSizeHeight)
this.setWndCover()
}, 200)
}
},
// 设置窗口遮挡
setWndCover () {
const iWidth = document.body.offsetWidth
const iHeight = document.body.offsetHeight
const oDivRect = document.getElementById("playWnd").getBoundingClientRect()
let iCoverLeft = (oDivRect.left < 0) ? Math.abs(oDivRect.left) : 0
let iCoverTop = (oDivRect.top < 0) ? Math.abs(oDivRect.top) : 0
let iCoverRight = (oDivRect.right - iWidth > 0) ? Math.round(oDivRect.right - iWidth) : 0
let iCoverBottom = (oDivRect.bottom - iHeight > 0) ? Math.round(oDivRect.bottom - iHeight) : 0
iCoverLeft = (iCoverLeft > this.divSizeWidth) ? this.divSizeWidth : iCoverLeft
iCoverTop = (iCoverTop > this.divSizeHeight) ? this.divSizeHeight : iCoverTop
iCoverRight = (iCoverRight > this.divSizeWidth) ? this.divSizeWidth : iCoverRight
iCoverBottom = (iCoverBottom > this.divSizeHeight) ? this.divSizeHeight : iCoverBottom
if (this.iLastCoverLeft != iCoverLeft) {
this.iLastCoverLeft = iCoverLeft
this.oWebControl.JS_SetWndCover("left", iCoverLeft)
}
if (this.iLastCoverTop != iCoverTop) {
this.iLastCoverTop = iCoverTop
this.oWebControl.JS_SetWndCover("top", iCoverTop)
}
if (this.iLastCoverRight != iCoverRight) {
this.iLastCoverRight = iCoverRight
this.oWebControl.JS_SetWndCover("right", iCoverRight)
}
if (this.iLastCoverBottom != iCoverBottom) {
this.iLastCoverBottom = iCoverBottom
this.oWebControl.JS_SetWndCover("bottom", iCoverBottom)
}
},
// 设置窗口控制回调
setCallbacks () {
const that = this
this.oWebControl.JS_SetWindowControlCallback({
cbIntegrationCallBack: that.cbIntegrationCallBack
});
},
cbIntegrationCallBack (oData) {
const data = oData.responseMsg
this.$emit('videoPlayEvent', data)
},
//截图
snapPic (num = 0, item = false) {
const that = this;
let snapName;
const wndId = parseInt(num, 10); // 0 选中窗口截图 1,、2、3...等数字为指定窗口
if (item) {
snapName = "D:\/SnapDir\/" + that.dateFormat(new Date(), "HH:mm") + item.name + ".jpg";
} else {
snapName = "D:\/SnapDir\/"
}
const code = new Promise((resolve, reject) => {
that.oWebControl.JS_RequestInterface({
funcName: "snapShot",
argument: JSON.stringify({
name: snapName,
wndId: wndId
})
}).then(function (oData) {
resolve(oData ? oData.responseMsg.code : '-1');
});
}).then(r => {
return r;
}).catch(e => {
console.log(e)
})
code.then((val) => {
if (val === 512) {
this.$emit('videoPlayEvent', {
type: 3,
msg: {
result: 512,
text: '截图失败'
}
})
}
})
},
//获取公钥
getPubKey (callback) {
const that = this
this.oWebControl.JS_RequestInterface({
funcName: "getRSAPubKey",
argument: JSON.stringify({
keyLength: 1024
})
}).then(function (oData) {
if (oData.responseMsg.data) {
that.pubKey = oData.responseMsg.data
callback()
}
})
},
//加密
setEncrypt (value) {
const that = this
const encrypt = new JSEncrypt()
encrypt.setPublicKey(that.pubKey)
return encrypt.encrypt(value)
},
//关闭,销毁标签
destructionVideo () {
if (this.oWebControl != null) {
this.uninit()
this.oWebControl.JS_HideWnd() // 先让窗口隐藏,规避可能的插件窗口滞后于浏览器消失问题
this.oWebControl.JS_Disconnect().then(function () { }, function () { })
}
},
//隐藏
hiddenVidoe () {
if (this.oWebControl != null) {
this.oWebControl.JS_HideWnd() // 让窗口隐藏
}
},
//显示
showVideo () {
if (this.oWebControl != null) {
this.oWebControl.JS_ShowWnd() // 让窗口显示
}
},
//停止全部预览
stopVidePreview () {
const that = this;
that.oWebControl.JS_RequestInterface({
funcName: "stopAllPreview"
}).then(function (oData) {
//展示回调信息
//that.showCBInfo('停止预览',JSON.stringify(oData ? oData.responseMsg : ''));
});
},
// 格式化时间
dateFormat (oDate, fmt) {
const time = dayjs(oDate).format(fmt)
return time;
},
}
}
</script>
<style lang="less" scoped>
.play_wnd_box {
width: 100%;
height: 100%;
position: relative;
background: #000;
}
.play_wnd {
width: 100%;
height: 100%;
}
.prompt_text {
color: #fff;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
<style lang="less">
.hkVideo-box {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 100px;
.ant-modal {
top: 0px;
}
}
</style>
通过集成海康web端视频插件,直接播放摄像头rtsp视频流播放不了。
低版本处理
// 通过动态加载指定版本
const script = document.createElement('script');
script.src = 'https://path/to/h5player1.5.5.min.js';
document.body.appendChild(script);
浏览器兼容性处理
1、基础配置
const player = new JSPlugin({
szId: 'player',
szBasePath: "/static/h5player/",
iMaxSplit: 4,
iCurrentSplit: 1,
openDebug: true
});
@ 必须设置szBasePath指向插件文件路径
2、跨域配置
服务器需添加响应头
nginx
add_header Cross-Origin-Embedder-Policy require-corp;
add_header Cross-Origin-Opener-Policy same-origin;
3、编码格式支持
低版本不支持H265时,需后端转码为H264
音频建议使用AAC格式
特殊场景解决方案
1、IE浏览器兼容
if(navigator.userAgent.indexOf('Trident') > -1) {
// 使用ActiveX控件模式
const axPlayer = new ActiveXObject('H5Player.ActiveX');
axPlayer.JS_StartPlay(url);
}
2、移动端适配
/* 响应式布局处理 */
#player {
width: 100vw;
height: calc(100vh - 60px);
}
性能优化建议
1、使用硬件解码时需注意:
高级模式仅支持Chrome 80+/iOS Safari
普通模式建议限制分辨率至1080p以下
2、多画面播放时:
// 动态调整解码路数
player.JS_SetDecodeRoute(routeId, 2); // 2表示双路解码
💻面试岗位:前端日常实习-一面 ❓面试问题: 1.自我介绍 2.js的基础数据类型 3.判别数据类型的方法 4.数组常用API 5.遍历循环的API 6.深浅拷贝 7.实现深拷贝-解决循环引用的情况 8.实现深拷贝还要注意哪些细节 9.闭包 10.setTimeout如何解决内存泄漏 11.拷打实习 12.promise 13.说一个promise代码询问输出情况 14.vue2/vue3生命周
技术支持工程师-医疗方向-杭州 时间线: 9.04:测评 9.06:笔试 9.21:一面 1、一面(钉钉)(第一个面试) 1.01 自我介绍 1.02 问项目 1.03 项目中的主要工作 1.04 最有成就感的事情,详细说下 1.05 当班长的体会 1.06 对这个岗位的了解 1.07 课余爱好 1.08 长期坚持的爱好 1.09 为什么选这个城市 1.10 反问
刚刚面的海康威视。找的是Java开发。一进是线下面的。然后他开始也不问,我技术啊,他就问我一些七七八八的学校做什么,你怎么自学的?你觉得你可算了多少嗯什么你最精通的什么框架?笑死了,我说我就会一个是公布的,其他不是很熟练。他说他这边要求必须精通三个以上的框架。真**笑死我了。然后说什么妮蔻,你这才刷至少得刷200道题啊?然后就是说你技术上不行。然后问我练了多少家公司了,怎么有没有考虑,为什么没有通
下载示例链接:
整体来说问的还是很简单的,感觉不符合想象 1.自我介绍 2.问了半天公司的事 3.数组方法 4.找数组最多出现次数 5.vue双向绑定 6.git解决冲突 7.微前端 8.webpack 9.工厂模式 10.反问 还有记不得了,问的奇少无比,而且都贼简单,不过听面试官的意思,招进来好像就是干外包的,原话叫"负责定制" 面试官声音挺好听的#我的实习求职记录#
1、自我介绍 2、项目 3、项目功能上还可以有哪些优化 4、页面dom太多如何优化 5、图片懒加载 6、http与socket的区别 7、闭包、原型链 8、vue响应式原理,如何给一个普通变量加上响应式 9、说说es6 10、项目部署
全程大概25min 1.自我介绍 2.围绕项目询问 3.vue的响应式原理 4.说一下flex布局 5.js事件循环 6.自己在前端方面还有什么不足 #前端面经##海康威视#
1、内容:16道单选,4道多选,两道简答题,两道编程题 2、时长:90min 选择 前端基础知识:html、css考察居多,因为我才开始学js,所以也不清楚考察js哪些内容,但考察的不深。 数据结构知识:碰到的两道好像都是排序的 简答 第二道是让我实现一个圣杯布局,只能用txt写,很无语 编程 因为我第一次笔试,结果一进去没有代码编辑器,就只有txt写,还以为系统出问题了,搞得我都有点懵了。 第一