简介
flv.js是一款使用纯 JavaScript 编写的 HTML5 Flash 视频 (FLV) 播放器,无需 Flash即可播放视频。
- 具有 H.264 + AAC / MP3 编解码器播放功能的 FLV 容器
- 多部分分段视频播放
- HTTP FLV低延迟直播流播放
- 通过 WebSocket 播放 FLV 直播流
- 兼容 Chrome、FireFox、Safari 10、IE11 和 Edge
- 极低的开销,并由您的浏览器硬件加速
tips:对于 FLV 直播流播放,可以考虑使用mpegts.js,mpegts.js 针对低延迟实时流播放进行了优化,例如 DVB/ISDB 电视或监控摄像头, 基于flv.js开发。
概述
flv.js 的工作原理是将 FLV 文件流转换为 ISO BMFF(碎片 MP4)片段,然后<video>
通过媒体源扩展API 将 mp4 片段输入 HTML5 元素。
演示
安装
npm install --save flv.js
方法
flvjs.createPlayer()
function createPlayer(mediaDataSource: MediaDataSource, config?: Config): Player;
根据中指定的type字段创建一个播放器实例mediaDataSource(可选)config。
MediaDataSource
Field | Type | Description |
---|---|---|
type | string | 媒体类型,'flv'或'mp4' |
isLive? | boolean | 数据源是否为实时流 |
cors? | boolean | 是否启用CORS进行http提取 |
withCredentials? | boolean | 是否对Cookie进行http提取 |
hasAudio? | boolean | 流是否有音频轨道 |
hasVideo? | boolean | 流中是否有视频轨道 |
duration? | number | 总媒体持续时间(以毫秒为单位) |
filesize? | number | 媒体文件的总文件大小,以字节为单位 |
url? | string | 表示媒体URL,可以以'https(s)'或'ws(s)'(WebSocket)开头 |
segments? | Array<MediaSegment> | 多段播放的可选字段,请参见MediaSegment |
如果segments存在字段,则transmuxer会将其MediaDataSource视为多部分源。
在多部分模式下,结构中的duration filesize url字段MediaDataSource将被忽略。
MediaSegment
Field | Type | Description |
---|---|---|
duration | number | 必填字段,指示段持续时间(以毫秒为单位) |
filesize? | number | 可选字段,指示段文件大小(以字节为单位) |
url | string | 必填字段,指示段文件URL |
Config
Field | Type | Default | Description |
---|---|---|---|
enableWorker? | boolean | false | 启用分离的线程进行转换(暂时不稳定) |
enableStashBuffer? | boolean | true | 启用IO隐藏缓冲区。如果您需要实时(最小延迟)来进行实时流播放,则设置为false,但是如果网络抖动,则可能会停顿。 |
stashInitialSize? | number | 384KB | 指示IO暂存缓冲区的初始大小。默认值为384KB。指出合适的尺寸可以改善视频负载/搜索时间。 |
isLive? | boolean | false | 同样要isLive在MediaDataSource,如果忽略已经在MediaDataSource结构集合。 |
lazyLoad? | boolean | true | 如果有足够的数据可播放,则中止http连接。 |
lazyLoadMaxDuration? | number | 3 * 60 | 指示要保留多少秒的数据lazyLoad |
lazyLoadRecoverDuration? | number | 30 | 指示lazyLoad恢复时间边界,以秒为单位。 |
deferLoadAfterSourceOpen? | boolean | true | 在MediaSource sourceopen事件触发后加载。在Chrome上,在后台打开的标签页可能不会触发sourceopen事件,除非切换到该标签页。 |
autoCleanupSourceBuffer | boolean | false | 对SourceBuffer进行自动清理 |
autoCleanupMaxBackwardDuration | number | 3 * 60 | 当向后缓冲区持续时间超过此值(以秒为单位)时,请对SourceBuffer进行自动清理 |
autoCleanupMinBackwardDuration | number | 2 * 60 | 指示进行自动清除时为反向缓冲区保留的持续时间(以秒为单位)。 |
fixAudioTimestampGap | boolean | true | 当检测到较大的音频时间戳间隙时,请填充无声音频帧,以避免A / V不同步。 |
accurateSeek? | boolean | false | 精确查找任何帧,不限于视频IDR帧,但可能会慢一些。可用的Chrome > 50,FireFox和Safari。 |
seekType? | string | 'range' | 'range'使用范围请求进行搜索,或'param'在url中添加参数以指示请求范围。 |
seekParamStart? | string | 'bstart' | 指示的搜索起始参数名称 seekType = 'param' |
seekParamEnd? | string | 'bend' | 指示的搜索结束参数名称 seekType = 'param' |
rangeLoadZeroStart? | boolean | false | Range: bytes=0-如果使用范围查找,则发送首次负载 |
customSeekHandler? | object | undefined | 指示自定义搜索处理程序 |
reuseRedirectedURL? | boolean | false | 重复使用301/302重定向的url进行子序列请求,例如搜索,重新连接等。 |
referrerPolicy? | string | no-referrer-when-downgrade | 指示使用FetchStreamLoader时的推荐人策略 |
headers? | object | undefined | 指示将添加到请求的其他标头 |
flvjs.isSupported()
如果您的浏览器支持使用flv.js,则返回true
function isSupported(): boolean;
flvjs.getFeatureList()
function getFeatureList(): FeatureList;
返回FeatureList具有以下详细信息的对象:
FeatureList
Field | Type | Description |
---|---|---|
mseFlvPlayback | boolean | 与flvjs.isSupported()相同,表示您的浏览器是否可以进行基本播放。 |
mseLiveFlvPlayback | boolean | HTTP FLV实时流是否可以在您的浏览器上工作。 |
networkStreamIO | boolean | 指示网络加载程序是否正在流式传输。 |
networkLoaderName | string | 指示网络加载程序类型名称。 |
nativeMP4H264Playback | boolean | 指示您的浏览器是否本身支持H.264 MP4视频文件。 |
nativeWebmVP8Playback | boolean | 指示您的浏览器是否本机支持WebM VP8视频文件。 |
nativeWebmVP9Playback | boolean | 指示您的浏览器是否本机支持WebM VP9视频文件。 |
实例方法
flv.js 提供了一系列方法来控制视频的播放、暂停、销毁等操作。
1.createPlayer
创建一个新的播放器实例。
参数
- mediaDataSource(对象):包含流媒体的相关信息。
- config(对象,可选):播放器的配置选项。
var flvPlayer = flvjs.createPlayer({
type: 'flv',
url: 'http://example.com/live.flv'
}, {
isLive: true,
enableWorker: true
});
2. attachMediaElement(element)
参数:
- element(HTMLMediaElement):要绑定的 HTMLMediaElement,例如 <video> 标签。
说明:将播放器绑定到指定的 HTMLMediaElement。
flvPlayer.attachMediaElement(document.getElementById('videoElement'));
3. load()
参数:无
说明:加载媒体资源。调用此方法后,播放器将开始缓冲媒体数据。
flvPlayer.load();
4. play()
参数:无
说明:开始播放媒体。如果媒体已经加载,则从当前位置开始播放。
flvPlayer.play();
5. pause()
参数:无
说明:暂停播放。
flvPlayer.pause();
6. destroy()
参数:无
说明:销毁播放器实例并释放相关资源。
flvPlayer.destroy();
7. seek(seconds)
参数:
- seconds(Number):要跳转到的位置,以秒为单位。
说明:将播放位置跳转到指定的时间点。
flvPlayer.seek(30); // 跳转到 30 秒的位置
8. updateStashBuffer(newBufferSize)
功能:动态更新播放器的缓冲区大小。
参数:
- newBufferSize(Number):新的缓冲区大小,以秒为单位。此参数决定了播放器预加载数据的时长。
返回值:无
适用场景:当需要根据实时网络状况或播放情况调整缓冲区大小时,可以使用该方法。例如,丢帧时增大缓冲区以减少卡顿,或者当网络状况改善时减小缓冲区以降低延迟。
9. on(event, callback)
参数:
- event(String):要监听的事件名称。
- callback(Function):事件触发时调用的回调函数。
说明:注册一个事件监听器,用于处理播放器的各种事件。
flvPlayer.on(flvjs.Events.ERROR, function(eventType, detail) {
console.error('Error type:', eventType);
console.error('Error detail:', detail);
});
示例代码
<template>
<div>
<video id="videoElement" crossOrigin="anonymous" controls autoplay muted
style="width: 100vw; height: calc(100vh - 6px)" />
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, watchEffect, nextTick, onBeforeUnmount, reactive } from "vue";
import flvjs from "flv.js";
import { useWebSocket } from "@vueuse/core";
const isShowModal = ref(false);
const flvPlayer = ref();
const state = reactive({
server: "ws://xx.xx.xx.xx:xxx/websocket", // 测试地址
playUrl: "",
status: ""
});
/**
* status 是连接的状态值
* data 是 socket 推送过来的消息
*/
const { status, data } = useWebSocket(state.server, {
onConnected: function (ws) {
console.log('websocket 连接成功!', ws)
},
onDisconnected: function (ws, event) {
console.log('onDisconnected')
},
onError: function (ws, event) {
console.log('onError')
},
onMessage: function (ws, event) {
console.log('event.data', event.data)
if (event.data) {
const parsedData = JSON.parse(event.data)
state.playUrl = parsedData.url;
// 这里写对应的业务逻辑
......
}
},
autoReconnect: true,
heartbeat: false
});
// 设置视频配置(注意:createVideo应放在异步函数里或mounted之后,不可在created里直接加载,否则不生效)
const createVideo = () => {
if (flvjs.isSupported()) {
flvPlayer.value = flvjs.createPlayer(
{
type: "flv",
isLive: true,
// hasAudio: true, // 播放的音频文件是否有声音,这里若设置错误,视频会播放不出来
url: state.playUrl
},
{
enableWorker: false, // 是否多线程工作
enableStashBuffer: false, // 是否启用缓存
stashInitialSize: 128, // 缓存大小(kb) 默认384kb
autoCleanupSourceBuffer: true // 是否自动清理缓存
}
);
let videoElement = document.getElementById("videoElement");
flvPlayer.value.attachMediaElement(videoElement); //挂载元素
if (state.playUrl !== "") {
flvPlayer.value.load(); //加载流
flvPlayer.value.play(); //播放流
}
// 报错重连
flvPlayer.value.on(flvjs.Events.ERROR, handleError);
}
};
const destoryVideo = () => {
if (flvPlayer.value) {
flvPlayer.value.pause(); // 暂停播放数据流
flvPlayer.value.unload(); // 取消数据流加载
flvPlayer.value.detachMediaElement(); // 将播放实例从节点中取出
flvPlayer.value.destroy(); // 销毁播放实例
flvPlayer.value = null;
}
};
const reloadVideo = () => {
destoryVideo();
createVideo();
};
const handleError = (err: any, errdet: any) => {
if (err === flvjs.ErrorTypes.NETWORK_ERROR) {
console.log("网络错误");
if (errdet === flvjs.ErrorDetails.NETWORK_STATUS_CODE_INVALID) {
console.log("http状态码异常");
}
} else if (err === flvjs.ErrorTypes.MEDIA_ERROR) {
console.log("媒体错误");
if (errdet === flvjs.ErrorDetails.MEDIA_FORMAT_UNSUPPORTED) {
console.log("媒体格式不支持");
}
} else if (err === flvjs.ErrorTypes.OTHER_ERROR) {
console.log("其他异常:", errdet);
}
reloadVideo();
};
onMounted(() => {
nextTick(() => {
createVideo();
});
});
onBeforeUnmount(() => {
destoryVideo();
});
</script>
可能遇到的问题
1.隐藏浏览器视频页面时,直播视频会自动暂停,重新回来到页面时,继续播放的还是隐藏浏览器视频页面前的画面,不是实时画面
原因分析:这种现象可能是由于浏览器默认行为导致的。在浏览器中,当页面切换到后台或不可见状态时,浏览器会暂停视频和其他资源的播放,以降低性能消耗。当页面再次切换到前台或可见状态时,浏览器会自动恢复资源的播放。这可能导致 FLV 视频流在切换页面时暂停播放,从而导致一段时间的延迟。
解决方法:
// 视频页面不在前台时,浏览器自动暂停视频,当页面恢复在前台时,跳转至最新时间播放
const seekFn = () => {
if (flvPlayer.value && flvPlayer.value.buffered.length) { // 监听通过判断buffered属性(该属性记录了视频的缓存范围),若存在buffered,则应该将currentTime赋值到缓存范围尾端
flvPlayer.value.currentTime = flvPlayer.value.buffered.end(0) - 0.1; // 指向当前buffer.end(index)往前一点点继续播
}
}
onMounted(() => {
// 监听浏览器的显隐
document.addEventListener('visibilitychange', function () {
// 离开了当前页面时触发
if (document.visibilityState === 'hidden') {
// console.log('hidden');
}
// 打开或回到页面时触发
if (document.visibilityState === 'visible') {
// 防止视频延时
seekFn()
}
});
});