前言
■ 需求
提供小程序播放(安卓和ios实现不同)、pc播放(各种业务的单路播放和大屏页面的9路并行播放并轮询)
■ 实现
走的国标gb28181,rtsp取流,pc和安卓采用ws-flv,前端连接为ws连接,ios采用hls,前端请求为m3u8列表,ts切片流。
一一解决
■ 项目情况
合同的开发早已完成,但客户对播放效果非常不满,导致项目迟迟无法验收。由于项目未结束,客户也会不停的提出其它的业务迭代要求,我方不断的进行无利润的更新迭代。由于视频播放差,开发周期不断延长,本是香饽饽的项目也变成了一个累赘合同。
■ 1.初始状况
客户反馈: 大屏完全播不了,就算能播也是卡顿、花屏,效果非常差。小程序安卓能播,ios看不了? 复现: 经理是着重测试第二点,提出的是安卓很正常,让大伙看看ios播放效果。测试无法出结论,同样的条件下有时正常播放,有时完全不能播放,上一分钟我能播放,下一分钟另一个人点就不能播放,完全摸不出规律。而且当时我使用的是用了6年的iPhone6,wifi坏的差不多,我这边也是难以排查是自身网络问题,还是服务器又或者客户门店带宽问题。 回应: 多重问题造成的现象,难以排查,无法明确责任人,也就会出现典型的互相甩锅,结果查到最后会发现人人都有责任,没有一方是无辜的。经理让前端排查问题,前端回应她能播放,没有问题,是视频流开发的问题。找视频流开发,看都不看,直接讲客户门店带宽不够,网络差之类。那客户这边回应门店是百M上传带宽,质疑服务器带宽偷工减料导致的大屏播放差,ios无法播放更是让他们质疑我方的能力。
==》第一次介入客户端问题
视频的播放实现本来无关web后端开发,并且也没有意识到严重性,由于迟迟未解决,我开始介入直播的开发。 排查: 首先观察Grafana,下行带宽和上行带宽不对等,上行带宽周期性地撑爆服务器界限,且是下行带宽的好几倍。视频服务器提供的连接数统计接口查询明显异常,极端情况下出现过一路摄像头存在50个连接数的现象。
可得客户端有问题(流量走向:门店 =》服务器 =》客户端)。
检查项目,由于特殊的请求移动端难以捕获,安卓采用pc谷歌浏览器开发者工具的Surface Duo模式模拟,ios采用macos的safari模拟,发现客户端存在大量问题。
① 视频流服务提供了一个close接口来关闭流,而前端只有pc切换关闭时调用了close接口,ws重连和小程序那边都没有调用close。
② pc和安卓(ws-flv)有大量的重连(ws连接重连),重连的旧连接仍然占用带宽,一分钟后才会自动停止。
③ ios四倍服务器带宽占用。ios每次播放虽然只有请求一个m3u8,但是会刷双倍的ts文件,并且返回上一级会挂一份ts在后台,反反复复,挂在后台的ts请求越来越多,将会撑死带宽,而且每份ts视频流服务器带宽监控显示再次翻倍。
④ 自研大屏有1 * 1、2 * 2、3 * 3、4 * 4等不同规格的通道展示,而每次切换的时候,会发现漏了几路请求在后台,有多有少,手动切换路数,ws请求会越来越多。 解决: 接着,就是一番对客户端开发的整顿。 ① 借用8.5后版本的postman测试websocket,disconnect后ws状态变为DISCONNECTING,但是数据还在传输,必须关闭标签才没有流量占用,又或者等待一分钟状态变为DISCONNECTED。 postman的disconnect和js的ws.close()相当,这是一种优雅的关闭方式,客户端申请服务器关闭,状态转为关闭中,待服务器善后完毕才会双向关闭ws连接,所以说前端插件flv.js里面其实是有调用ws.close()的,但是由于视频流服务器存在问题,所以没有立马关闭。我特地写了个springboot整合的wsserver测试,发现DISCONNECTING状态下,数据是不可传输的,这个问题抛给了视频流开发。这个没有解决,然后是采用修改flv.js,关闭重连等都调用close接口。同时前端检查项目,需要调用关闭的地方通通调用close方法。
② 这一点其实包含在①中,由于视频流开发存在某种问题导致的ws.close()后1分钟仍然占用带宽,视频流开发是修改了flv.js通过调接口的方式立马关闭流。至于频繁重连的问题,当时是认为门店带宽问题,此时未作处理,后面讲。
③ 处理ios问题了,4倍带宽占用?并且每播放一路就会漏2倍带宽在后台占用。。。。难怪ios问题更严重,并且每当别人说不行,反馈回来已经隔了很久了,你去测试播放又可以了。 首先解决服务器双倍的问题,实时监控服务器带宽异常发给视频流开发,初始是抵赖的,理由是这个监控带宽是本地带宽未过公网,正常统计,我便使用wireshark抓包,有大量的请求是form-本机内网ip,destination-本机公网ip,可得在转hls的内部处理中有ip配错成公网ip了,导致双倍带宽占用,修改即可。 再看客户端双倍的问题,由于VConsole只统计xhr内容,前端只看见了一次播放一个请求m3u8列表便认为自己没问题,事不关己,无论发啥都不看,甚至认为是在针对她。我便介入排查,然后带来了自己的imac,演示ts请求异常,以及修复效果,仍然态度恶劣。这就涉及到了团队问题,差劲的项目开发流程,带来无穷无尽的迭代需求,击垮了团队的开发热情,掏空了老板的口袋,以致后面正常开发沟通都不愉快,只有经理跨级介入,才会好好说话。 回归开发谈bug,如下两段代码,可能是hls的特殊性,前端另写了一个动态追加html。但是我测试发现,第一段代码的 < source> 部分删除后,双倍现象消失了,由于怕引来其它问题,将这个提交给了前端,但前端仍然不搭理,坚持没问题,因为js里清除了才播放,不可能双倍,后经理介入才得以修复。
<div id="player-wrapper" v-show="streamMode === 'hls' && brandId === 'XXX'">
<video
id="player-hls"
class="video-js vjs-default-skin vjs-big-play-centered auto-play"
controls="controls"
autoplay="autoplay"
x-webkit-airplay="true"
x5-video-player-fullscreen="false"
preload="auto"
playsinline="true"
webkit-playsinline="true"
x5-video-player-type="h5"
muted>
<source type="application/x-mpegURL" :src="cgStreamUrl">
</video>
</div>
playing(option) {
const { streamMode, domId, playAddress, thumbnailUrl, callback } = option;
if (streamMode === 'hls') {
$('#player-wrapper').empty();
$('#player-wrapper').eq(0).append(`
<video id="${domId}"
style="object-fit: fill;"
width="100%" height="100%"
controls="controls" autoplay="autoplay"
x-webkit-airplay="true" x5-video-player-fullscreen="true"
preload="auto" playsinline="true" webkit-playsinline="true"
x5-video-player-type="h5" muted="true"
poster=${thumbnailUrl}>
<source type="application/x-mpegURL" src="${playAddress}">
</video>`);
const video = document.getElementById(domId);
video.play();
callback && callback();
return null;
}
④ 第四点可以锁定前端问题,明确了责任,后续一番优化后有所好转,但仍未根除。
■ 2.初步优化客户端后续
解决了各种异常占用带宽的问题后,服务器带宽明显富余,同步几十路播放都不成问题,迎来客户再次吐槽。 客户反馈: 有的时候还行,有的时候花屏卡顿?门店百M上行带宽,再次怀疑视频流服务器流量混用于其它服务,导致播放异常。 复现: 经理测试,白天播放相对正常,晚上开始花屏、绿屏、ws不停地各种重连,导致画面各种跳动,绿屏最为恶心,因为实在太明显了,这种情况只在pc上出现,而安卓小程序相对正常。我使用火狐浏览器测试发现,火狐浏览器相对不同,播放正常多了,不会出现绿屏,只会有少量的模糊画质,也没有了重连。而安卓的微信小程序webview效果大概和火狐浏览器差不多。 回应: 当时这个确实没辙,网络?设备?服务?仍是无法定责,结果自然会是接着甩锅。视频流开发抓包测试udp丢包率百分之四五十,仍然回复是客户网络问题,客户回复网络没有问题,不信自己测试。
==》第二次介入取流协议问题
经理和实施对门店带宽进行了测速,在线web测速,点对服务器ftp测速,点对服务器http测速(我搭了一个speedtest)。发现晚上网络确实不稳定,会比白天差些,但带宽最差也足够支持住9路并行,更不用说测试的时候看一路都卡,这明显说明不是网速问题。 排查: 再次观察Grafana,上下行带宽对等了,相对正常了且服务器带宽富余,但是网络曲线抖动得非常厉害,说明确实经常播放异常。然后让现场测试一下网络抖动,发现这个值明显偏大(非电信宽带的门店)。 解决: 后续门店取流改用tcp,播放正常了。滑稽的是视频流开发方的大佬早在16年就写了一篇关于udp花屏的博客。然后视频流开发优化无果。我仍然认为是视频流开发存在问题,它没有针对异常网络作出一定的优化,只有电信带宽才算正常带宽的标准,这显然是不负责的,改用tcp播放没有相对udp明显的延迟更说明了服务的问题。 注: 网络抖动大时,udp会出现丢包 、花屏、绿屏等现象。tcp不存在丢包,表现为卡住,然后继续播放的现象。
■ 3.改用TCP后续
改用tcp后,播放正常了,效果简直是天差地别。 客户反馈: 有某些通道掉线,有nvr假死,通道掉线为现场问题,nvr假死未知情况,现场那边重启恢复了,多次重现折腾死了技术支持,直到周末迎来爆发,nvr陆续假死,吓的不敢讲话,还好客户没有发现,并且假死一两天后有的会自动恢复。 复现: 掉线的全部都是改用tcp的门店,在此之前,从未有过这种情况。并且当时仍使用udp的门店正常,使用电信宽带的门店正常。便怀疑是改用tcp引来的问题。 回应: 视频流开发回应无从下手,建议找设备厂商解决。 排查: 使用wireshark抓包,发现tcp挥手经常只有单向断开。 例如:服务器=FIN=》门店······门店=RST,ACK=》服务器 解决: 怀疑是阻塞问题,海康的设备更新了一下设备软件版本,后面没再掉线。抓包为 服务器=FIN=》门店······门店=RST,ACK=》服务器······门店=RST=》服务器 注: 正常四次挥手 A=FIN=》B······B=ACK=》A······B=FIN=》A······A=ACK=》B 挥手回应Ack=挥手请求Seq+1
■ 4.初验
大致没问题了才引来初验 客户反馈: 大屏经常有一路不能播,但是刷新正常;有一家门店频繁跳帧。 复现: ① 报错Cannot read property 'flushStashedSamples' of null ; ② 因为可以远程操作的是另一家门店,我作了调试,其它门店的海康摄像头也时常跳帧,小牌摄像头反而正常,我修改该摄像头一些参数,竟然正常了。 解决: ① 寻找视频流开发做出优化重连。 ② 然而现场修改该门店的摄像头参数无效,最后重启设备正常了许多。结论就是修改参数可以优化不怎么严重的跳帧。而类似该门店客流量大,一旦画面变化大就跳帧,重启设备得以修复,也是没有找到原因。
■ 5.再次优化
以上问题解决后,也就差不多了,可以慢慢调优了。 现象: 前面说过手动切换路数,ws请求会越来越多。 复现: 自研大屏频繁切换路数,客户大屏快速切换门店,小程序快速切换摄像头,pc打开播放界面快速关闭弹窗,都会出现连接挂在后台,并且不同于重连,重连是有调用ws.close()的,就算没调接口也会一分钟自动关闭,而此处不会自动关闭。 回应: 从最初开始前端就有关于这个的优化,自研大屏切换路数挂后台的概率变低了,但是仍然经常复现。比如9路切换本来会挂大半在后台,现在只会挂小几路,然后回应已修复。
==》再次介入链式没有及时关闭问题
其实挂的路数变少了,是前端修复了一些没有close场景。还在进行链式的时候X掉了窗口,链式还会继续执行才是问题所在。通俗讲就是播放函数请求打开通道,这时候客户端关掉了播放界面,会调用销毁player,因为打开通道的请求还没返回,player还没生成,所以报错,且没有结束后续播放请求,浏览器就在后台默默执行完播放函数,也就占用了带宽。 解决: 修改为可取消的Promise, 直接上代码
工具类httpRequest.js 将参数引出
http.CancelToken = axios.CancelToken
修改播放函数,设置为外界可取消,可通过reject取消,也可通过中断http请求来取消,此处使用中断http请求
playLive(id, mode = 'rtsp', transporttype = 'tcp') {
let source = http.CancelToken.source();
const wrappedPromise = new Promise((resolve, reject) => {
http({
url: http.adornUrl('。。。openstream'),
method: 'get',
cancelToken: source ? source.token : '',
params: {
id,
transporttype,
mediatype: 'video',
format: mode,
},
}).then((response) => {
let data = response.data;
if (data.PlayAddress !== 'Stream not exist') {
resolve(data);
} else {
reject(response);
}
})
})
return {
promise: wrappedPromise,
cancel() {
source.cancel('取消请求');
},
};
},
调用播放函数,如果函数中断,then后序不会执行。
this.vPromise = Video.playLive(paramId, 'rtsp', transporttype);
this.vPromise.promise.then((data) => {
。。。
然后在销毁方法里添加这个中断方法即可。
vPromise.cancel();
效果如图 这样就避免了那些莫名其妙多出来的连接。
■ 6.视频流鉴权问题
参考 视频直播鉴权结合业务系统的token或session
最后
到目前为止,相对稳定了,也能说得过去,运维也相对容易,宏观Grafana带宽可得大体播放情况,设备是否异常,局部使用ELK统计超时打开通道的请求,可以马上定位异常通道。
|