基于flv.js的监控直播流技术解析与前端实现
本文深入解析直播流与点播的区别,重点介绍利用flv.js在浏览器中实现监控直播的技术方案。涵盖推流拉流原理、二进制数据处理、HTTP-FLV与WebSocket-FLV选型,以及播放控制、异常处理和样式定制等核心代码实现。
引言
这一技术落地困难的原因,一方面在于GitHub上的官方文档较为晦涩且说明简略;另一方面,开发者往往受限于传统的“视频播放”思维,对数据流缺乏足够认知及处理经验。
点播与直播
何为直播?何为点播?
直播的概念在抖音等应用普及后已广为人知,即实时传输音视频内容。而点播则是指播放预先制作好的视频文件,其体验与在哔哩哔哩等平台观看视频无异,核心在于将已存在的视频资源呈现给用户。
对于前端开发而言,点播的实现相对简单:只需获取mp4格式的链接地址,并将其填入video标签中。浏览器会自动处理视频解析与播放任务,用户甚至可以通过拖动进度条自由跳转至任意时间点。
然而,直播机制与点播截然不同,主要具备以下两个显著特征:
- 数据以流的形式获取
- 对实时性有极高要求
首先解析“流数据”的概念。大多数未涉足音视频领域的前端开发者,日常接触的数据多为通过ajax接口获取的json数据,或是文件上传场景。这类数据的共同点是“一次性获取”,即单次请求即可返回完整数据。
相比之下,流数据的获取是分帧进行的,可以理解为数据被切割成极小的片段。直播流并非完整的视频文件,而是由微小的二进制数据组成,前端需要将这些碎片逐步拼接,才能还原出连续的视频画面。
其次是实时性要求。点播模式下,完整视频存储于服务器,前端通过链接即可播放。但直播的实时性决定了数据源无法预置于服务器,而必须源自某个实时产生数据的客户端。
既然数据源位于客户端,这些数据又是如何传输至其他观看端的呢?
具体传输机制可参考下述流程图:

如图所示,发起直播的客户端连接至流媒体服务器,将实时产生的视频流推送至服务端,此过程称为推流。其他客户端同样连接该服务器,但作为播放端,它们从服务端实时拉取视频流,此过程称为拉流。
“推流至服务器,再由服务器分发给拉流端”是目前主流且标准的直播解决方案。由此可见,直播全流程均涉及流数据传输,前端需直接处理二进制数据,其复杂度远超点播。
具体到业务场景中的摄像头实时监控预览,其架构与上述流程完全一致,区别仅在于发起直播的客户端是摄像头设备,而观看端则是浏览器。
静态数据与流数据
常见的文本、json、图片等均属于静态数据,前端通过ajax请求接口获取的即为静态数据。
如前所述,直播产生的视频和音频属于流数据。流数据以帧为单位传输,本质为二进制数据。由于其体量微小且如水流般连续不断,非常适合实时传输场景。
静态数据在前端代码中有明确的数据类型对应,例如string,json,array等。那么,流数据(即二进制数据)在前端的数据类型是什么?应如何存储与操作?
首先明确,前端具备存储和操作二进制数据的能力。最基础的二进制对象是ArrayBuffer,它表示固定长度的二进制数组,例如:
let buffer = new ArrayBuffer(16) // 创建一个 16 字节 的 buffer,用 0 填充
alert(buffer.byteLength) // 16ArrayBuffer仅用于存储二进制数据,若需对其进行操作,则必须使用视图对象。
视图对象本身不存储数据,其作用是对中的数据进行结构化处理,从而便于前端操作这些数据。简言之,视图对象是操作二进制数据的接口。
常见的视图对象包括:
- Uint8Array:每个元素占1个字节
- Uint16Array:每个元素占2个字节
- Uint32Array:每个元素占4个字节
- Float64Array:每个元素占8个字节
依据上述标准,一个16字节的可转换为不同视图对象,其长度如下:
- Uint8Array:长度为16
- Uint16Array:长度为8
- Uint32Array:长度为4
- Float64Array:长度为2
此处仅简要介绍流数据在前端的存储方式,旨在帮助开发者在浏览器中识别长串的,明确其本质为二进制数据。
为何选择flv格式?
前文提及直播对实时性要求极高,因此延迟越低越好。影响传输速度的因素众多,其中视频数据本身的大小是关键之一。
在点播场景中,最常见的mp4格式对前端兼容性最佳。然而,mp4文件体积较大,解析过程相对复杂,这在直播场景下成为其劣势。
flv格式则不同,其头部文件极小,结构简单,解析速度快,在直播对实时性的严苛要求下具有显著优势,因此成为最常用的直播方案之一。
除flv外,还有其他格式及对应的直播协议,我们逐一进行对比:
- RTMP:底层基于TCP协议,在浏览器端依赖Flash插件支持。
- HTTP-FLV:基于HTTP流式IO传输FLV格式,依赖浏览器支持播放FLV。
- WebSocket-FLV:基于WebSocket传输FLV格式,依赖浏览器支持播放FLV。
- HLS(Http Live Streaming):由苹果提出的基于HTTP的流媒体传输协议,HTML5可直接打开播放。
- RTP:基于UDP协议,延迟约1秒,但浏览器原生不支持。
早期常用的直播方案是RTMP,其兼容性尚可,但依赖Flash技术。鉴于目前浏览器默认禁用Flash,该技术已被时代淘汰,故不予考虑。
HLS协议同样常见,对应视频格式为m3u8。该方案由苹果推出,对移动端支持极佳,但其致命缺点是延迟较高(10至30秒),因此亦不适用。
RTP因浏览器不支持被排除,综上所述,仅剩flv格式可用。
然而,flv又细分为HTTP-FLV和WebSocket-FLV两种传输方式,二者看似相似,实则存在区别。
前文已述,直播流需实时传输,连接建立后保持长连接,需持续进行推拉流操作。针对此类长连接场景,首先想到的方案自然是WebSocket,因其专为长连接实时互传设计。
不过,随着JavaScript原生能力的扩展,出现了如fetch这样比ajax更强大的技术。它不仅支持更友好的Promise语法,且天生具备处理流数据的能力,性能优异且使用简单,对开发者更为友好,因此催生了基于HTTP的flv方案。
综上所述,最适合浏览器直播的是flv格式。但flv并非万能,其缺点在于前端原生video标签无法直接播放,需经过额外处理。
处理该问题的方案,即本文的核心主角:flv.js。
协议与基础实现
前文提到,flv同时支持WebSocket和HTTP两种传输方式,幸运的是,flv.js库也同时兼容这两种协议。
选择使用HTTP还是WebSocket,在功能和性能上差异不大,关键取决于后端提供的协议支持。此处选择HTTP方案,因前后端处理均较为便捷。
接下来介绍flv.js的具体接入流程,官方文档链接在此。
假设现有直播流地址为:http://test.stream.com/fetch-media.flv,第一步,按照官网快速开始指南构建demo:
import flvjs from 'flv.js'
if (flvjs.isSupported()) {
var videoEl = document.getElementById('videoEl')
var flvPlayer = flvjs.createPlayer({
type: 'flv',
url: 'http://test.stream.com/fetch-media.flv'
})
flvPlayer.attachMediaElement(videoEl)
flvPlayer.load()
flvPlayer.play()
}首先安装flv.js,代码第一行用于检测浏览器是否支持flv.js,大部分现代浏览器均支持。随后获取video标签的DOM元素。flv.js会将处理后的流输出至video元素,进而实现视频流播放。
接下来是关键步骤,即创建flvjs.Player对象,称为播放器实例。该实例通过flvjs.createPlayer函数创建,参数为配置对象,常用配置如下:
type:媒体类型,可选flv或mp4,默认为flvisLive:可选参数,标识是否为直播流,默认为truehasAudio:标识是否包含音频hasVideo:标识是否包含视频url:指定流地址,支持https(s)或ws(s)
上述音频和视频配置需依据流地址实际内容而定。例如,若监控流仅含视频无音频,即便配置hasAudio为true,也无法获取声音。
播放器实例创建完成后,执行以下三步操作:
挂载元素:flvPlayer.attachMediaElement(videoEl)加载流:flvPlayer.load()播放流:flvPlayer.play()
基础实现流程至此结束,下文将探讨处理过程中的细节与要点。
细节处理要点
上述为基本用法,以下阐述实践中的关键问题。
暂停与播放逻辑
点播中的暂停与播放操作直观:播放器界面通常设有播放/暂停按钮,用户可随时暂停,再次点击播放时从暂停处继续。但在直播场景中,逻辑截然不同。
正常直播界面不应包含播放/暂停按钮及进度条。因为直播展示的是实时信息,若用户暂停视频,再次点击播放时,不应从暂停处继续,而应获取最新的实时流,播放最新画面。
具体到技术实现,前端video标签默认带有进度条和暂停按钮。将直播流输出至video标签后,点击暂停按钮视频会停止,这与点播逻辑一致。但若再次点击播放,视频仍从暂停处继续,这显然不符合直播需求。
因此,我们需要重新审视直播的播放/暂停逻辑。
直播为何需要暂停功能?以视频监控为例,页面可能同时显示多个摄像头画面。若每个播放器持续与服务器保持连接并拉流,将产生大量连接消耗,导致不必要的流量成本。
优化方案如下:用户进入页面时,选择关注的摄像头并点击播放以拉流;当不再观看时,点击暂停使播放器断开连接。如此可有效节省无效流量消耗。
因此,直播中播放/暂停的核心逻辑实为拉流与断流。
基于此理解,我们的方案应是隐藏video标签默认的暂停/播放按钮,并自行实现播放和暂停逻辑。
沿用前述代码示例,播放器实例(即flvPlayer变量)保持不变,播放/暂停逻辑代码如下:
const onClick = isplay => {
// 参数 isplay 表示当前是否正在播放
if (isplay) {
// 在播放,断流
player.unload()
player.detachMediaElement()
} else {
// 已断流,重新拉流播放
player.attachMediaElement(videoEl.current)
player.load()
player.play()
}
}异常处理机制
使用flv.js接入直播流过程中可能遭遇各类问题,既包括后端数据流异常,也涉及前端处理逻辑错误。由于流数据实时获取且flv实时转换输出,一旦发生错误,浏览器控制台会循环打印异常信息。
若使用React和TypeScript开发,满屏异常将严重影响开发进度。鉴于直播流本身可能频繁出现异常,完善的异常处理机制至关重要。
官方对异常处理的说明较为隐晦,此处简要总结:
首先,flv.js的异常分为两个级别,可视为一级异常和二级异常。
其次,flv.js具有特殊性,其事件和错误均使用枚举表示,具体如下:
flvjs.Events:表示事件类型flvjs.ErrorTypes:表示一级异常类型flvjs.ErrorDetails:表示二级异常详情
下文介绍的异常和事件均基于上述枚举,可理解为枚举下的具体key值。
一级异常包含三类:
NETWORK_ERROR:网络错误,表示连接问题MEDIA_ERROR:媒体错误,涉及格式或解码问题OTHER_ERROR:其他错误
二级异常常用包含三类:
NETWORK_STATUS_CODE_INVALID:HTTP状态码错误,说明url地址有误NETWORK_TIMEOUT:连接超时,涉及网络或后台问题MEDIA_FORMAT_UNSUPPORTED:媒体格式不支持,通常因流数据非flv格式所致
了解上述分类后,可在播放器实例上监听异常:
// 监听错误事件
flvPlayer.on(flvjs.Events.ERROR, (err, errdet) => {
// 参数 err 是一级异常,errdet 是二级异常
if (err == flvjs.ErrorTypes.MEDIA_ERROR) {
console.log('媒体错误')
if(errdet == flvjs.ErrorDetails.MEDIA_FORMAT_UNSUPPORTED) {
console.log('媒体格式不支持')
}
}
if (err == flvjs.ErrorTypes.NETWORK_ERROR) {
console.log('网络错误')
if(errdet == flvjs.ErrorDetails.NETWORK_STATUS_CODE_INVALID) {
console.log('http状态码异常')
}
}
if(err == flvjs.ErrorTypes.OTHER_ERROR) {
console.log('其他异常:', errdet)
}
}此外,实现自定义播放/暂停逻辑时,还需掌握加载状态。可通过以下方法监听视频流加载完成:
player.on(flvjs.Events.METADATA_ARRIVED, () => {
console.log('视频加载完成')
})样式定制
为何需要样式定制?前文已述,直播流的播放/暂停逻辑与点播不同,因此需隐藏video的操作栏元素,通过自定义元素实现相关功能。
首先需隐藏播放/暂停按钮、进度条及音量按钮,可通过CSS实现:
/* 所有控件 */
video::-webkit-media-controls-enclosure {
display: none;
}
/* 进度条 */
video::-webkit-media-controls-timeline {
display: none;
}
video::-webkit-media-controls-current-time-display {
display: none;
}
/* 音量按钮 */
video::-webkit-media-controls-mute-button {
display: none;
}
video::-webkit-media-controls-toggle-closed-captions-button {
display: none;
}
/* 音量的控制条 */
video::-webkit-media-controls-volume-slider {
display: none;
}
/* 播放按钮 */
video::-webkit-media-controls-play-button {
display: none;
}播放和暂停逻辑上文已述,样式方面需自定义一个按钮。此外,可能还需要全屏按钮,全屏逻辑实现如下:
const fullPage = () => {
let dom = document.querySelector('.video')
if (dom.requestFullscreen) {
dom.requestFullscreen()
} else if (dom.webkitRequestFullScreen) {
dom.webkitRequestFullScreen()
}
}其他自定义样式,如弹幕功能,只需在video元素上方覆盖一层元素自行实现即可。
以上即为通过flv.js播放监控示例深入探究直播流技术的详细内容,更多关于flv.js播放监控直播流的资料请关注本站其它相关文章!
JS中使用位运算(~~)和一元加号(+)快速转换字符串为整数
2026-11-03 22:41:12
怎么使用命令行批量格式化项目中的JS文件
2026-11-03 19:04:42
JS代码美化后报错怎么办?常见格式化冲突解决方法
2026-11-03 18:47:01
不用moment.js,怎么用原生JS Intl API格式化时间戳
2026-11-03 18:10:30
原生JS与dayjs获取当前日期的代码对比
2026-11-03 17:57:26
