項目總結目錄
項目說明
公司最近想要將智慧展廳中的海康攝像頭布控系統功能 做成一套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,