1. 前言

最近在处理前端直播的业务,根据业务需要,使用 flv.js 的方案播放实时的flv视频流。不得不承认,flv.js 是一个伟大的库。

在使用flv.js开发的过程中,遇到了一些问题,也无外乎是视频延迟,视频卡顿等问题,经过在github issues里摸爬滚打,加上长时间的试错,将这些问题归纳出了对应的解决方案,也自己封装了一个扩展插件 flvExtend

于是写这篇文章来对我遇到的一些问题进行总结,我提出的解决方案不一定适合所有场景,如果有更好的解决方案,欢迎讨论,这也是我写这篇文章的目的,也是我写文章的初心。

2. 前端直播

在讲解 flv.js 的优化方案之前,我想先简单的介绍一下前端直播的方案,为什么要使用 flv.js,方便大家理解以及作为一项技术来储备。

2.1 常见直播协议

  • RTMP: 底层基于 TCP,在浏览器端依赖 Flash。
  • HTTP-FLV: 基于 HTTP 流式 IO 传输 FLV,依赖浏览器支持播放 FLV。
  • WebSocket-FLV: 基于 WebSocket 传输 FLV,依赖浏览器支持播放 FLV。WebSocket 建立在 HTTP 之上,建立 WebSocket 连接前还要先建立 HTTP 连接。
  • HLS: Http Live Streaming,苹果提出基于 HTTP 的流媒体传输协议。HTML5 可以直接打开播放。
  • RTP: 基于 UDP,延迟 1 秒,浏览器不支持。

可以看到,在浏览器端,可以考虑的方案有:HTTP-FLVWebSocket-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 一样甚至更好。

2.2 flv.js 的原理

flv.js 的主要工作就是,在获取到 FLV 格式的音视频数据后通过原生的 JS 去解码 FLV 数据,再通过 Media Source Extensions API 喂给原生 HTML5 Video 标签。(HTML5 原生仅支持播放 mp4/webm 格式,不支持 FLV)

flv.js 为什么要绕一圈,从服务器获取 FLV 再解码转换后再喂给 Video 标签呢?原因如下:

  1. 兼容目前的直播方案:目前大多数直播方案的音视频服务都是采用 FLV 容器格式传输音视频数据。
  2. FLV 容器格式相比于 MP4 格式更加简单,解析起来更快更方便。

2.3 flv.js 的简单使用

<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>

主要流程就是:

  1. 创建flvjs.Player对象,可以传递两个参数:MediaDataSource,以及 Config,具体的可以看下官方文档
  2. 挂载元素
  3. 加载视频流
  4. 播放视频流

附:官方 API 文档

3. flv.js 的优化方案

我们根据官方的例子,可以很容易地把 flv 直播流播起来,但是在实际项目中使用时,还会遇到一些问题,我们需要手动对这些问题进行优化处理

3.1 追帧-解决延迟累积问题

flv.js 有一个最大的问题,就是延迟问题,一方面是直播端的延迟,一方面是浏览器的延迟,而且浏览器的延迟如果不做特殊处理,会造成延时累积的问题,对直播的实时性影响很大。

解决方案需要从以下两部分入手:

3.1.1 修改 config 配置

{
enableWorker: true, // 启用分离的线程进行转换
enableStashBuffer: false, // 关闭IO隐藏缓冲区
stashInitialSize: 128, // 减少首帧显示等待时长
}
  • 开启 flv.js 的 Worker,多线程运行 flv.js 提升解析速度可以优化延迟
  • 关闭 buffer 缓存,这个选项可以明显地降低延迟,缺点就是由于关闭了 buffer 缓存,网络不好的时候可能会出现 loading 加载
  • 调低 IO 缓冲区的初始尺寸,减少首帧显示的等待时长

3.1.2 追帧设置

解决延时累加最有效的方式就是进行追帧设置

追帧,就是去判断缓冲区末尾的 buffer 值与当前播放时间的差值,如果大于某个值,就进行追帧设置,具体的思路如下:

  1. 首先,在 progress 事件,或者定时器中进行追帧逻辑
  2. 判断 buffer 的差值 delta
let end = this.player.buffered.end(0); //获取当前buffered值(缓冲区末尾)
let delta = end - this.player.currentTime; //获取buffered与当前播放位置的差值
  1. 如果 delta 值大于某个设定的值,则进行追帧操作
  2. 追帧有两种方式

    1)一种是直接更新当前的时间:this.player.currentTime = this.player.buffered.end(0) - 1,缺点是如果频繁触发会导致跳帧,观感差;

    2)一种是调快播放速度的方式来慢慢追帧: this.videoElement.playbackRate = 1.1,优点是稳定,缺点是如果 delta 值过大,通过这种方式追得太慢

    在实际使用中两种方式可以结合起来。

代码实现:

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;
}
});

3.2 断流重连

断流重连即在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();
});

3.3 实时更新

直播需要保证视频的实时性,以下两种操作都会导致视频的实时性得不到保证:

  • 用户点击了暂停,过一段时间后再点播放,这时候的直播视频不是最新的
  • 网页切到后台,再重新切换回前台,视频不是最新的

所以需要根据这两种情况来实时更新视频

代码实现:

// 点击播放按钮后,更新视频
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;
};

3.4 解决 stuck 问题

有的时候,视频在播放的过程中会突然卡住,或者控制台有时会报错 “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);
}

4. 封装插件 flvExtend.js

我将这些优化方案封装成了一个插件 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();

5. 其他问题

这里打算长期记录一下遇到的问题以及解决思路,欢迎大家讨论,我会更新补充

1)多路视频同时直播

由于浏览器对 http 1.0 的限制,以Chrome为例,同一个浏览器下,最多只能播6路同源地址下的视频(包括多个标签页也会被合算在内)

目前的解决方案有:

  1. 使用http 2.0,由于http 2.0的多路复用,可以同屏播放多个视频流
  2. 使用 websocket
  3. 通过为流分配不同的服务端地址

参考

flv.js的追帧、断流重连及实时更新的直播优化方案的更多相关文章

  1. flv.js怎么用?全面解读flv.js代码

    flv.js项目的代码有一定规模,如果要研究的话,我建议从demux入手,理解了demux就掌握了媒体数据处理的关键步骤,前面的媒体数据下载和后面的媒体数据播放就变得容易理解了. 先普及点背景知识,为 ...

  2. Flv.js

    Flv.js 是 HTML5 Flash 视频(FLV)播放器,纯原生 JavaScript 开发,没有用到 Flash.由 bilibili 网站开源. 该项目依托于 Media Source Ex ...

  3. js 利用canvas + flv.js实现视频流 截屏 、本地下载功能实现,兼容火狐,谷歌;canvas截屏跨域问题,无音频视频流加载不显示问题

    项目:物联网监控项目----后台视频流管理(前端实现视频截屏功能) 本文就不同视频源分情况展示: 1 本地视频(项目同目录视频)截屏(canvas.getContext("2d).drawI ...

  4. SRS+flv.js打造兼容性较高的直播、点播平台

    **************************************************************************************************** ...

  5. 【JS】313- 复习 回流和重绘

    点击上方"前端自习课"关注,学习起来~ 原文地址:我不是陈纪庚 segmentfault.com/a/1190000017329980 回流和重绘可以说是每一个web开发者都经常听 ...

  6. livego+obs+flv.js 搭建视频直播

    一.流程 主播通过 obs软件通过直播 ->推流到->直播服务器 客户通过浏览器 访问站点->flv.js拉取直播服务器视频流并播放 二.环境 centos7 直播服务器 https ...

  7. Flv.js文档使用随记

    关键字:Flv.js | Flv js | Flv-js | HTML5 FLV Player | 0x001: 前言以下涉及到 flv.js 所有内容均是V1.5.0版本内的,如方法.属性.常量.监 ...

  8. Flv.js记录(vue)

    下载 npm install flv.js 插入 import flvjs from 'flv.js'   第一种报错:Failed to read the 'buffered' property f ...

  9. vlc+flv.js 摄像头 H5 直播

    背景 业务需求:用最短的时间搞定摄像头直播到Web页面.因为没有过这方面经验,所以走了很多弯路,其实也不算弯路吧,大部分时间花在学习基础概念,寻找快速方案中.惯性思维想当然的以为找组件,配地址就能搞定 ...

随机推荐

  1. JVM探究

    1.JVM探究 请你谈谈你对JVM的理解?java8虚拟机和之前的变化更新? 什么是OOM,什么是栈溢出StackOverFlowError?怎么分析? JVM的常用调优参数有哪些? 内存快照如何抓取 ...

  2. 用简单的 Node.js 后台程序浅析 HTTP 请求与响应

    用简单的 Node.js 后台程序浅析 HTTP 请求与响应 本文写于 2020 年 1 月 18 日 我们来看两种方式发送 HTTP 请求,一种呢,是命令行的 curl 命令:一种呢是直接在浏览器的 ...

  3. 电机噪声之谐波分析(内附simulink中FFT分析的相关参数配置与解析)

    电机噪声之谐波分析(内附simulink中FFT分析的相关参数配置与解析) 目录 电机噪声之谐波分析(内附simulink中FFT分析的相关参数配置与解析) 写在前面 正文 电机噪声 谐波的产生 什么 ...

  4. C语言函数调用栈

    C语言函数调用栈 栈溢出(stack overflow)是最常见的二进制漏洞,在介绍栈溢出之前,我们首先需要了解函数调用栈. 函数调用栈是一块连续的用来保存函数运行状态的内存区域,调用函数(calle ...

  5. SUSE系统---keepalived的搭建

    作为博客第一篇,先小尝初试一下搭建keepalived,因为项目需要搭建集群,为了满足需要,需要服务器对外暴露个虚拟IP,进行集群部署. 第一步:先把keepalived.tra.gz压缩包放到相应目 ...

  6. mysql Bad handshake

    由于 Java 程序访问 MySQL 时,MySQL 抛出 Bad handshake 错误,导致接口抛错,然后在 MySQL 配置文件新增 skip_ssl 配置(忽略 SSL 密钥和证书文件),重 ...

  7. mybatis 查询返回的类型中字段类型为 List<xx>

    基本类型数组 mapper.xml <resultMap id="xxDtoResultMap" type="com.xx.xxDto"> < ...

  8. 万字长文,带你轻松学习 Spark

    大家好,我是大D. 今天给大家分享一篇 Spark 核心知识点的梳理,对知识点的讲解秉承着能用图解的就不照本宣科地陈述,力求精简.通俗易懂.希望能为新手的入门学习扫清障碍,从基础概念入手.再到原理深入 ...

  9. [codeforces] 暑期训练之打卡题(一)

    每个标题都做了题目原网址的超链接 Day1<Vanya and Lanterns> 题意: 一条长度为 l 的街道,在这条街道上放置了n个相同的灯,街道一端位置记为0,每个灯的位置在ai处 ...

  10. 有趣的BUG之Stack Overflow

    今天遇到一个很有意思的bug,当程序开发完成后打包到服务器运行,总是会出现栈溢出异常,经过排查发现,问题出现在一个接口上,但这个接口逻辑并不复杂,除了几局逻辑代码外和打印语句之外也没有其他的了,但是只 ...