一、需求
一开始用的海康服务插件做的视频监控,但是后来需要付费了,于是不得不寻找新的解决方案。
需要实现的几个功能:
1、初始按顺序加载所有监控(默认)
2、窗口可选择,可手动控制监控点和监控窗口,同一窗口可切换监控画面
3、监控视频(摄像头)可控制方向、缩放
4、各监控窗口左上角显示当前摄像头的名称及位置信息
5、切换窗口规格(1x1,2x2,3x3等)
6、切换规格时,已有的监控画面不更新,直接缩小到对应尺寸(未实现)
7、全屏
8、浏览器长连接(浏览器同域名并发限制-我的chrome浏览器限制个数是6;意思就是,如果一个页面是3x3的9个监控,但是只能加载6个,剩下的3个都一直处于等待状态中)2021.12.02 更新:浏览器对ws没有并发限制
9、监控延时(未实现)
10、监控画面出现前展示加载动画,黑屏用户体验不友好(未实现)
11、画面铺满整个播放器
二、效果
三、实现
法1:
首先index.html中引入插件,
<script src="./video/EasyWasmPlayer.js"></script>
这里有个坑,create react app中new WasmPlayer时会报’WasmPlayer is not defined‘的错,这里提醒一下大家,如果有这个错,改成 new window.WasmPlayer就好了,或者自己修改webpack配置。
或
法2
npm 安装
npm install @easydarwin/easywasmplayer --save
引入
import WasmPlayer from '@easydarwin/easywasmplayer' //导入WasmPlayer.js
这样直接new WasmPlayer就好了
1、首先把动态的窗口排一下
1、render里面
<img onClick={this.quanping} src={imgURL } className="quanping"/>
<div className='VideoMonitorNewMix' ref={fullScreenRef}>//在你想要设置成全屏的div里面写ref
{this.createGrid()} //父元素用display: flex;flex-wrap: wrap;布局
</div>
2、js里面
const fullScreenRef = React.createRef();
quanping = () => {
fullScreenRef.current.requestFullscreen();//全屏
}
componentDidMount() {
for(let i=0;i<this.state.Size;i++){
window['flvPlayer'+i] = new WasmPlayer(
null,
'video'+i,
this.callbackfun,{
Height:true
})
}
}
// 动态生成Grid
createGrid() {
var res = [];
for (let i = 0; i < this.state.Size; i++) {
res.push(
<div className={'video_panel' + Math.sqrt(this.state.Size)}>
<div id={'video'+i} onClick={(e)=>{this.getCameraIndex(i+'',e)}}></div>//getCameraIndex控制窗口的选中状态
<div id={"info"+i} className="info"></div>
</div>);
}
return res;
}
3、scss
.VideoMonitorNewMix{
width: 100%;
height: calc(100% - 40px);
display: flex;
flex-wrap: wrap;
//@mixin video_panel($count) {
//width:100% / $count;
//height:100% / $count;
//border:1px solid #061b31;
//}
//1x1
.video_panel1 {
// @include video_panel(1);//想用scss写的,报错了,尴尬!!
width:100%;
height:100%;
border:1px solid #061b31;
position: relative;
.info{
color: yellow;
position: absolute;
left: 10px;
top: 10px;
}
}
// 2*2
.video_panel2 {
// @include video_panel(2);
width:50%;
height:50%;
border:1px solid #061b31;
position: relative;
.info{
color: yellow;
position: absolute;
left: 10px;
top: 10px;
}
}
}
2、初始按顺序加载所有监控(默认)
在componentDidMount中,确保已经获取所有监控数据this.state.MonitorData过后,开始加载,这里注意一下窗口数量和监控数量的取舍,保证资源的可利用性
for(let i=0;i<(this.state.Size<=this.state.MonitorData.length?this.state.Size:this.state.MonitorData.length);i++){
this.flv_reload(this.state.MonitorData[i].Url,i)//初始按顺序加载所有监控
}
flv_reload(rtspUrl,index=0){
window['flvPlayer'+index]&&window['flvPlayer'+index].destroy()//释放播放器
let deleteIndex,camera
for(let i=0;i<this.state.cameraInfo.length;i++){
if(this.state.cameraInfo[i].index==index){
camera=this.state.cameraInfo[i]
deleteIndex=i
}
}
if(camera){//如果正在播放,先停止
this.state.cameraInfo.splice(deleteIndex,1)//从正在播放的监控中删除
//为了多客户端,不发停止指令
// getFlvOpt({
// opt: 'stop',
// rtspaddr:camera.rtspUrl,
// FlvAddr:camera.playurl
// }).then(res => {
// this.state.cameraInfo.splice(deleteIndex,1)//从正在播放的监控中删除
// console.log(this.state.cameraInfo)
// //法1:WasmPlayer.js播放器
// // 实例化播放器
// // window['flvPlayer'+index] = new WasmPlayer(null, 'video'+key+index, this.callbackfun)
// // 调用播放
// window['flvPlayer'+index].stop(camera.playurl, 1)
// })
}
getFlvOpt({
opt: 'start',
rtspaddr:rtspUrl
}).then(res => {
let playurl =res.FlvAddr
this.state.cameraInfo.push({index:index+'',rtspUrl:rtspUrl,playurl:playurl})//把当前窗口播放的监控url保存
console.log(this.state.cameraInfo)
//法1:WasmPlayer.js播放器
// 实例化播放器
window['flvPlayer'+index] = new WasmPlayer(null, 'video'+index, this.callbackfun,{
Height:true
})
// 调用播放--浏览器长连接-6个解决方案
if(this.state.cameraInfo.length>6){
let videoUrl=playurl.split('/')
let port=videoUrl[2].split(':')
playurl=videoUrl[0]+'//'+'videoService:'+port[1]+'/'+videoUrl[3]+'/'+videoUrl[4]
}
window['flvPlayer'+index].play(playurl, 1)
//添加摄像头名称、位置信息
let moniInfo=this.state.MonitorData.filter(item=>{
return item.Url==rtspUrl
})
if(moniInfo&&moniInfo.length>0){
//1、获取div
let div = document.getElementById("info"+index);
div.innerHTML = "";
div.innerHTML="<p>名称:"+moniInfo[0].Name+"</p>"+
"<p>位置:"+moniInfo[0].Location+"</p>"
}
})
}
2021.12.02 更新
由于浏览器对http请求的并发限制,导致多窗口视频监控时总需要配置hosts更改域名。但是对ws并没有这个限制,于是上段代码示例中 getFlvOpt
优化如下:
getFlvOptNew({
opt:"getvideobyrtsp",
rtspaddr:rtspUrl,
type:'flv'
}).then(res => {
if(res.code==200){
//ws播放,浏览器没有长连接限制--2021.12.02优化
let ws_addrArr=res.data.ws_url.split('/')
let ws_playFlvAddr=ws_addrArr[0]+"//"+window.location.host+"/"+ws_addrArr[3]+"/"+ws_addrArr[4]
res.data.ws_url=ws_playFlvAddr
let ws_playurl =res.data.ws_url
//ws方案-end
this.state.cameraInfo.push({index:index+'',rtspUrl:rtspUrl,playurl:playurl,DevId:DevId})//把当前窗口播放的监控url保存
console.log(this.state.cameraInfo)
//法1:WasmPlayer.js播放器
// 实例化播放器
window['flvPlayer'+index] = new WasmPlayer(null, 'video'+index, this.callbackfun,{
Height:true
})
window['flvPlayer'+index].play(ws_playurl, 1)
//添加摄像头名称、位置信息
let moniInfo=this.state.MonitorData.filter(item=>{
return item.Url==rtspUrl
})
if(moniInfo&&moniInfo.length>0){
//1、获取div
let div = document.getElementById("info"+index);
div.innerHTML = "";
div.innerHTML="<p>名称:"+moniInfo[0].Name+"</p>"+
"<p>位置:"+moniInfo[0].Location+"</p>"
}
}else{
Feedback.toast.prompt(res.message);
}
})
2021.12.02 更新end
3、窗口可选择,可手动控制监控点和监控窗口,同一窗口可切换监控画面
//选择窗口-为了选择监控点
getCameraIndex=(index,e)=>{
for(let i=0;i<this.state.Size;i++){
document.getElementById('video'+i).className = '';
}
document.getElementById('video'+index).className = 'active';
this.setState({
index:index+'',
video:''
})
}
.active{
border:1px solid yellow !important;
}
4、监控视频(摄像头)可控制方向、缩放
<div className="VideoMixControl">
<div className='controlBtn'>
<Icon type="arrow-up" size="small" className="up" onMouseDown={(e)=>this.controlCamera('up',e)} onMouseUp={(e)=>this.stopControlCamera('up',e)}/>
<div style={{display:"flex"}}>
<Icon type="arrow-left" size="small" className="left" onMouseDown={(e)=>this.controlCamera('left',e)} onMouseUp={(e)=>this.stopControlCamera('left',e)}/>
<div className="control"></div>
<Icon type="arrow-right" size="small" className="right" onMouseDown={(e)=>this.controlCamera('right',e)} onMouseUp={(e)=>this.stopControlCamera('right',e)}/>
</div>
<Icon type="arrow-down" size="small" className="down" onMouseDown={(e)=>this.controlCamera('down',e)} onMouseUp={(e)=>this.stopControlCamera('down',e)}/>
</div>
<div className="controlZoomBtn">
<Icon type="add" size="large" className="add" onMouseDown={(e)=>this.controlCamera('add',e)} />
<Icon type="minus" size="large" className="minus" onMouseDown={(e)=>this.controlCamera('minus',e)}/>
</div>
<img onClick={::this.quanping} src={imgURL } className="quanping"/>
</div>
//控制摄像头方向
controlCamera=(direction,e)=>{
if(this.state.index==""){
Feedback.toast.prompt("请选择窗口");
return
}
let camera=this.state.cameraInfo.filter(item=>{
return item.index==this.state.index
})
if(camera.length==0){
Feedback.toast.prompt("当前窗口没有监控");
return
}
let CameraisCtrl=this.state.MonitorData.filter(item=>{
return item.Url==camera[0].rtspUrl
})
console.log(CameraisCtrl)
let opt=''
switch(direction){
case 'up':
opt='UpStart';break;
case 'right':
opt='RightStart';break;
case 'down':
opt='DownStart';break;
case 'left':
opt='LeftStart';break;
case 'add':
opt='ZoomIn';break;
case 'minus':
opt='ZoomOut';break;
}
controlCameraOpt({
opt:opt,
ipaddr:CameraisCtrl[0].IPAddr,
}).then(res => {
})
}
//停止摄像头旋转
stopControlCamera=(direction,e)=>{
if(this.state.index==""){
Feedback.toast.prompt("请选择窗口");
return
}
let camera=this.state.cameraInfo.filter(item=>{
return item.index==this.state.index
})
if(camera.length==0){
Feedback.toast.prompt("当前窗口没有监控");
return
}
let CameraisCtrl=this.state.MonitorData.filter(item=>{
return item.Url==camera[0].rtspUrl
})
console.log(CameraisCtrl)
let opt=''
switch(direction){
case 'up':
opt='UpStop';break;
case 'right':
opt='RightStop';break;
case 'down':
opt='DownStop';break;
case 'left':
opt='LeftStop';break;
}
controlCameraOpt({
opt:opt,
ipaddr:CameraisCtrl[0].IPAddr,
}).then(res => {
})
}
四、总结
主要技术点:
1、动态排列成栅格布局–参考这个 2、浏览器长连接–了解详细请点击这里 3、画面铺满整个播放器–了解详情点击这里
五、参考
easywasmplayer文档easyWasmPlayer demo开源流媒体服务器-