基于flv.js的监控直播流技术解析与前端实现

作者:资源驿站

时间:2026-10-31

浏览:347

本文深入解析直播流与点播的区别,重点介绍利用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) // 16

ArrayBuffer仅用于存储二进制数据,若需对其进行操作,则必须使用视图对象。

视图对象本身不存储数据,其作用是对中的数据进行结构化处理,从而便于前端操作这些数据。简言之,视图对象是操作二进制数据的接口。

常见的视图对象包括:

  • 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,默认为flv
  • isLive:可选参数,标识是否为直播流,默认为true
  • hasAudio:标识是否包含音频
  • 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播放监控直播流的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章