最近在处理前端直播的业务,根据业务需要,使用 flv.js 的方案播放实时的flv视频流。不得不承认,flv.js 是一个伟大的库。
在使用flv.js开发的过程中,遇到了一些问题,也无外乎是视频延迟,视频卡顿等问题,经过在github issues里摸爬滚打,加上长时间的试错,将这些问题归纳出了对应的解决方案,也自己封装了一个扩展插件 flvExtend。
于是写这篇文章来对我遇到的一些问题进行总结,我提出的解决方案不一定适合所有场景,如果有更好的解决方案,欢迎讨论,这也是我写这篇文章的目的,也是我写文章的初心。
在讲解 flv.js 的优化方案之前,我想先简单的介绍一下前端直播的方案,为什么要使用 flv.js,方便大家理解以及作为一项技术来储备。
可以看到,在浏览器端,可以考虑的方案有:HTTP-FLV、WebSocket-FLV 以及 HLS, 我们可以对比一下这几个直播协议之间的性能:
(以下数据来源于网络,只做对比参考)
传输协议 | 播放器 | 延迟 | 内存 | CPU |
---|---|---|---|---|
RTMP | Flash | 1s | 430M | 11% |
HTTP-FLV | Video | 1s | 310M | 4.4% |
HLS | Video | 20s | 205M | 3% |
可以看出在浏览器里做直播,使用 HTTP-FLV 协议是不错的,性能优于 RTMP+Flash,延迟可以做到和 RTMP+Flash 一样甚至更好。
flv.js 的主要工作就是,在获取到 FLV 格式的音视频数据后通过原生的 JS 去解码 FLV 数据,再通过 Media Source Extensions API 喂给原生 HTML5 Video 标签。(HTML5 原生仅支持播放 mp4/webm 格式,不支持 FLV)
flv.js 为什么要绕一圈,从服务器获取 FLV 再解码转换后再喂给 Video 标签呢?原因如下:
<script src="flv.min.js"></script>
<video id="videoElement"></video>
<script>
if (flvjs.isSupported()) {
var videoElement = document.getElementById("videoElement");
var flvPlayer = flvjs.createPlayer({
type: "flv",
isLive: true,
url: "http://example.com/flv/video.flv",
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
}
</script>
主要流程就是:
我们根据官方的例子,可以很容易地把 flv 直播流播起来,但是在实际项目中使用时,还会遇到一些问题,我们需要手动对这些问题进行优化处理
flv.js 有一个最大的问题,就是延迟问题,一方面是直播端的延迟,一方面是浏览器的延迟,而且浏览器的延迟如果不做特殊处理,会造成延时累积的问题,对直播的实时性影响很大。
解决方案需要从以下两部分入手:
3.1.1 修改 config 配置
{
enableWorker: true, // 启用分离的线程进行转换
enableStashBuffer: false, // 关闭IO隐藏缓冲区
stashInitialSize: 128, // 减少首帧显示等待时长
}
3.1.2 追帧设置
解决延时累加最有效的方式就是进行追帧设置
追帧,就是去判断缓冲区末尾的 buffer 值与当前播放时间的差值,如果大于某个值,就进行追帧设置,具体的思路如下:
let end = this.player.buffered.end(0); //获取当前buffered值(缓冲区末尾)
let delta = end - this.player.currentTime; //获取buffered与当前播放位置的差值
代码实现:
videoElement.addEventListener("progress", () => {
let end = player.buffered.end(0); //获取当前buffered值(缓冲区末尾)
let delta = end - player.currentTime; //获取buffered与当前播放位置的差值
// 延迟过大,通过跳帧的方式更新视频
if (delta > 10 || delta < 0) {
this.player.currentTime = this.player.buffered.end(0) - 1;
return;
}
// 追帧
if (delta > 1) {
videoElement.playbackRate = 1.1;
} else {
videoElement.playbackRate = 1;
}
});
断流重连即在flvjs播放失败的回调中,进行重建视频的操作
代码实现:
this.player.on(flvjs.Events.ERROR, (e) => {
// destroy
this.player.pause();
this.player.unload();
this.player.detachMediaElement();
this.player.destroy();
this.player = null;
// 进行重建的逻辑,这里不再展开
this.init();
});
直播需要保证视频的实时性,以下两种操作都会导致视频的实时性得不到保证:
所以需要根据这两种情况来实时更新视频
代码实现:
// 点击播放按钮后,更新视频
videoElement.addEventListener("play", () => {
let end = player.buffered.end(0) - 1;
this.player.currentTime = end;
});
// 网页重新激活后,更新视频
window.onfocus = () => {
let end = player.buffered.end(0) - 1;
this.player.currentTime = end;
};
有的时候,视频在播放的过程中会突然卡住,或者控制台有时会报错 “Playback seems stuck at 0, seek to 1.1”。
我们需要判断视频是否卡住了,然后重建视频实例
思路就是判断 decodedFrames 是否产生变化,如果视频是播放状态并且该值没有产生变化,则可以判断视频卡住了。
代码实现:
function handleStuck() {
let lastDecodedFrames = 0;
let stuckTime = 0;
this.interval && clearInterval(this.interval);
this.interval = setInterval(() => {
const decodedFrames = this.player.statisticsInfo.decodedFrames;
if (!decodedFrames) return;
if (lastDecodedFrames === decodedFrames && !this.videoElement.paused) {
// 可能卡住了,重载
stuckTime++;
if (stuckTime > 1) {
console.log(`%c 卡住,重建视频`, "background:red;color:#fff");
// 先destroy,再重建视频实例
this.rebuild();
}
} else {
lastDecodedFrames = decodedFrames;
stuckTime = 0;
}
}, 800);
}
我将这些优化方案封装成了一个插件 flvExtend.js,它相当于是 flv.js 的一个功能扩展
插件地址:https://github.com/shady-xia/flvExtend
使用起来是这个样子:
import FlvExtend from "flv-extend";
// 配置需要的功能
const flv = new FlvExtend({
element: videoElement, // *必传
frameTracking: true, // 开启追帧设置
updateOnStart: true, // 点击播放后更新视频
updateOnFocus: true, // 获得焦点后更新视频
reconnect: true, // 开启断流重连
reconnectInterval: 2000, // 断流重连间隔
});
// 调用 init 方法初始化视频
// init 方法的参数与 flvjs.createPlayer 相同,并返回 flvjs.player 实例
const player = flv.init(
{
type: "flv",
url: "http://192.168.0.11/stream",
isLive: true,
},
{
enableStashBuffer: false, // 如果您需要实时(最小延迟)来进行实时流播放,则设置为false
stashInitialSize: 128, // 减少首帧显示等待时长
}
);
// 直接调用play即可播放
player.play();
这里打算长期记录一下遇到的问题以及解决思路,欢迎大家讨论,我会更新补充
1)多路视频同时直播
由于浏览器对 http 1.0 的限制,以Chrome为例,同一个浏览器下,最多只能播6路同源地址下的视频(包括多个标签页也会被合算在内)
目前的解决方案有:
来源:https://www.cnblogs.com/xiahj/archive/2022/07/20/flvExtend.html
Flv.js 就是由 bilibili 网站开源的 HTML5 Flash 视频(FLV)播放器,纯原生 JavaScript 开发(ECMAScript 6 编写) ,没有用到 Flash。它的工作原理是 Flv.js 在 JavaScript 中流式解析 flv 文件流
为什么要在这个时候探索flv.js做直播呢?原因在于各大浏览器厂商已经默认禁用Flash,之前常见的Flash直播方案需要用户同意使用Flash后才可以正常使用直播功能,这样的用户体验很致命。
先取到接口数据,后播放。使用async await关键字实现顺序加载视频 伪代码,在mounted生命周期内渲染视频,lv.js在播放前需要检查是否已创建实例,如果已有实例,则销毁,在重新创建播放器实例。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!