项目总结目录
项目说明
公司最近想要将智慧展厅中的海康摄像头布控系统功能 做成一套Web端的展示界面方便用于对外做展示。主要功能点:摄像视频监控播放,进出人员人脸识别报警,图表展示
- 技术栈:React+Mobx+Echarts+Flvjs+Antd+Swiper
web端实现视频监控播放
关于海康摄像头输出
海康默认输出的是rtsp协议的视频流,而这在PC端是无法直接通过video的形式来播放的,所以这中间需要一层转码。
- 常用的转码工具有FFmpeg,Live555
- 转码方式: rstp–> rtmp,flv,hls
rtmp,flv,hls三者区别
由于rtmp需要flash支持,而chrome默认已经禁用了flash插件且到2020.12后停用flash,而flash本身也即将被淘汰,所以暂不考虑rtmp
- | RTMP | HLS | HTTP-FLV |
---|---|---|---|
协议 | TCP长连接 | HTTP短连接 | HTTP长链接 |
原理 | 每个时刻的数据收到后立刻转发 | 集合一段时间的数据,生成切片文件,并更新m3u8索引 | 将流媒体数据封装成 FLV 格式,然后通过 HTTP 协议传输给客户端【基于 HTTP/80 传输】。 |
延时 | 1-3秒 | 5-20秒(依切片情况) | 1-3秒 |
Web支持 | H5中需要用插件video.js | 支持H5 | H5中需要使用插件flv.js |
其他 | 跨平台支持差,需要Flash技术支持 | 播放时需要多次请求,对网络质量要求高 | 需要flash技术支持(但可以通过flv.js来实现无flash播放),不支持多音频流,多视频流 |
缺陷 | 基于TCP,可能会被防火墙阻拦, | 延迟高,网络要求高 | 会让流媒体资源缓存在本地客户端,在保密性方面不够好 |
项目应用方案
项目前后经过三次技术尝试:
- jsmpeg+canvas+websocket实现前端播放rtsp,参考html5播放rtsp方案
- hls+video.js实现H5播放【延迟高】
- flv+flv.js实现H5播放【最终方案】
canvas直接播放视频
这种方式前端用到jsmpeg插件,通过webSocket发送MPEG,前端通过js解析MPEG不断绘制canvas,包括音频。html5播放rtsp方案
ffmpeg解码
:用于视频解码node
:搭建webSocket服务器,以及运行一个jsmpeg的js文件,jsmpeg
:运行主程序(绘制canvas播放视频及音频)
放弃原因:视频虽然能够正常播放,但容易花屏(搜索解决方案后说在ffmpeg解码时默认使用的是UDP协议,可以设置使用TCP进行解码可以解决),而且这套方案不适合放在生产环境上
hls视频流播放
起初用这套方案是因为后台只会转hls协议流视频,所以临时用这种方式实现播放,后端通过转码后给到的地址像这样http://xxxxxxx.m3u8
,HLS是Apple公司推出的一种视频流协议,在IOS上兼容性可以说很好,而且视频做移动端项目,缺陷在于延迟较高,PC在播放容易卡顿
video.js
:前端拿到HLS的视频地址后,只需要一个video.js用于视频播放即可(也可用hls.js包更小更方便,video.js在7.x版本后默认是引用了hls.js的)
flv直播
最终选择这套方案是因为flv相对hls延迟更低,可以无flash插件播放。最终确定后端实现rtsp到http-flv格式的转码,前端实现flv视频流的播放
- 工具:
flv.js
import React,{Component} from 'react'
import Flv from 'flv.js'
class VideoPlayer extends Component{
videoNode = React.createRef()
render(){
setTimeout(()=>{
this.reRenderVideo()
},0)
return(
<div className="video-wrapper height-100">
<video style={{width:'100%',height:'100%'}} ref={this.videoNode} id="videoElement"></video>
</div>
)
}
componentDidMount(){
if (this.flvPlayer) {
this.flvPlayer.destroy();
}
var videoElement = this.videoNode.current;
this.flvPlayer = Flv.createPlayer({
type: 'flv',
//后端最终给到的视频流地址
url: 'http://192.168.0.253:9001/live?port=xxxx&app=xxxx&stream=bb646a3390',
muted:true,
});
this.flvPlayer.attachMediaElement(videoElement);
this.flvPlayer.load();
this.flvPlayer.play();
}
componentWillUnmount() {
if (this.flvPlayer) {
this.flvPlayer.destroy();
}
}
}
export default VideoPlayer;
Swiper引入问题记录
由于需要用到多视频画面切换以及进出人员展示的swiper所以想用引用这个库。这里只记录遇到的问题
问题一:在componentDidMount中设置
new Swiper('.swiper-container')
时,没有相应效果。【原因猜想:DOM挂载的异步延迟使得swiper在加载时没有拿到相应的DOM,导致渲染不成功】
- 引入swiper上
import Swiper from 'swiper'
import 'swiper/css/swiper.min.css'
- 解决:通过
setTimeout(()=>{},0)
的方式延后设置。估计是DOM挂载完成时间问题
Echarts使用问题记录
echarts算是用得很多的一款数据可视化工作库了,这次主要难点在于它的
渐变色块的实现吧
解决方法:使用echarts内置的渐变色生成器来实现,通过封装实现
import echarts from "echarts";
/*
* @description 利用echarts渐变生成器来实现图表渐变色
* @param {string} color1 起始色
* @param {string} color2 终止色
* @return {object} 渐变对象
* */
export function generateGradient(color1,color2){
return new echarts.graphic.LinearGradient(
0, 0, 1, 0,
[
{offset: 0, color: color1},
{offset: 1, color: color2},
]
)
}
/*
* @description 将传入的渐变起止色二维数组转化为echarts可用的渐变值
* @param {array} colorList 渐变色数组
* @return {array} 渐变值数组
* */
export function generateColorList(colorList ){
if(colorList.length<=0){return false}
let res = colorList.map(item=>{
return generateGradient(item[0],item[1])
})
return res
}
项目打包优化
由于使用的是create-react-app脚手架搭建的项目,在打包时生成的文件还是挺大的,最大的js达到了1.1MB,这导致首屏加载白屏时间很长差不多要10s了
1. 开启webpack打包分析
- 借助
webpack-bundle-analyzer
插件可以实现对打包后文件的文件大小分析 npm install -D webpack-bundle-analyzer
- 通过
npm run eject
可以不可逆的打开create-react-app的自定义webpack配置,从而实现自定义
// webpack.config.js
const BundleAnalyzerPlugin= require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
..省略千字..
plugins:[
//打包大小分析
new BundleAnalyzerPlugin(
{
analyzerMode: 'server',
// analyzerHost: '127.0.0.1',
analyzerPort: 8889,
reportFilename: 'report.html',
// 应该是`stat`,`parsed`或者`gzip`中的一个。
defaultSizes: 'parsed',
// 在默认浏览器中自动打开报告
openAnalyzer: true,
// 如果为true,则Webpack Stats JSON文件将在bundle输出目录中生成
generateStatsFile: false,
statsFilename: 'stats.json',
statsOptions: null,
logLevel: 'info' /*日志级别。可以是'信息','警告','错误'或'沉默'。*/
})
]
- 效果如下图
2. 服务端开启Gzip
- 这一步让运维小哥做好对应服务的Gzip开启就好了,自己测试玩儿的服务器也可以用如下配置来开启某一服务的Gzip
location /cameraControl {
# 映射服务器
proxy_pass http://47.98.146.53:81/camera-control;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
index index.html index.htm index.jsp;
# 开启gzip压缩
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png application/javascript;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
}
- 开启以后,通过chrome控制台可以查看是否开启成功,方法如下:打开Network–》在信息table头处右键–》
Response Header
–》勾选Content-Encoding
3. 引入的antd V3.24.0 默认引入的icons资源过大
在通过webpack-bundle-plugin打包分析可以看出,整个打包出来最大的js文件中,包含antd的内容也是最大的,而antd中光icons资源都占有了483K左右(parsed),优化方案:通过将icons资源单独打包出来,并异步加载引用实现主js包大小的缩减。【目前版本的antd即使不引用Icon组件一样会有这样么大的包,所以只能通过优化打包的方式缩减,未来antd优化了这一部分issue就可以再去除这步】
npm install webpack-ant-icon-loader
// webpack.config.js
optimization:{
splitChunk:{
chunks:function(chunk){
// 这里的name 可以参考在使用`webpack-ant-icon-loader`时指定的`chunkName`
return chunk.name !== 'antd-icons';
},
}
}
- 最终打包后会发现多了一个
antd-icons.14c2af0f.chunk.js
文件,这就是分离出来的antd-icon,