@videojs-player/vue 直播fiv 组件封装 添加flv-extend 延迟问题
包的版本
"@videojs-player/vue": "^1.0.0",
"flv.js": "1.6.2",
"video.js": "^8.22.0",
"flv-extend": "^0.3.0",
<template>
<!-- 封装的视频播放器组件 -->
<video-player ref="videRef" :options="state.playerOptions" :src="videoSrc" @mounted="handleMounted" @unmounted="handleUnmounted" />
</template>
<script setup lang="ts">
import { reactive, defineProps, defineEmits, getCurrentInstance, watch,ref } from "vue";
import { VideoPlayer } from '@videojs-player/vue'
import videojs from 'video.js'
import 'video.js/dist/video-js.css'
import video_zhCN from 'video.js/dist/lang/zh-CN.json'
import { FlvJsTech } from './flv-video-tech'
videojs.addLanguage('zh-CN', video_zhCN)
const { proxy } = getCurrentInstance();
// 注册 FlvJsTech
videojs.registerTech('Flvjs', FlvJsTech);
// 定义 props 类型
type Props = {
// 视频源地址
videoSrc: string;
// 是否显示控制栏
showControls: boolean;
// 是否自动播放
autoplay: boolean;
// 是否静音
muted: boolean;
// 是否循环播放
loop: boolean;
// 音量大小
volume: number;
// 是否禁用画中画
disablePictureInPicture: boolean;
// v-model 绑定的值,用于控制播放状态
modelValue: boolean;
};
// 定义 props
const props = defineProps<Props>();
/**
* 定义视频播放器可能触发的所有事件列表
* 这些事件将用于监听视频播放器的各种状态变化
* 例如加载开始、播放、暂停、结束等
*/
const eventsArr = [
// 视频开始加载时触发
'loadstart',
// 视频加载暂停时触发
'suspend',
// 视频加载中止时触发
'abort',
// 视频加载出错时触发
'error',
// 视频元素被清空时触发
'emptied',
// 视频加载停滞时触发
'stalled',
// 视频元数据加载完成时触发
'loadedmetadata',
// 视频的第一帧数据加载完成时触发
'loadeddata',
// 视频可以开始播放时触发
'canplay',
// 视频可以流畅播放时触发
'canplaythrough',
// 视频开始播放时触发
'playing',
// 视频暂停等待数据时触发
'waiting',
// 视频开始跳转时触发
'seeking',
// 视频跳转完成时触发
'seeked',
// 视频播放结束时触发
'ended',
// 视频时长发生变化时触发
'durationchange',
// 视频播放时间更新时触发
'timeupdate',
// 视频加载进度更新时触发
'progress',
// 视频开始播放时触发
'play',
// 视频暂停时触发
'pause',
// 视频播放速率改变时触发
'ratechange',
// 视频播放器尺寸改变时触发
'resize',
// 视频音量改变时触发
'volumechange',
// 视频海报改变时触发
'posterchange',
// 视频语言设置改变时触发
'languagechange',
// 视频全屏状态改变时触发
'fullscreenchange',
// 视频播放速率选项改变时触发
'playbackrateschange',
// 视频控制栏禁用时触发
'controlsdisabled',
// 视频控制栏启用时触发
'controlsenabled',
// 视频进入全屏窗口时触发
'enterFullWindow',
// 视频退出全屏窗口时触发
'exitFullWindow',
// 视频进入画中画模式时触发
'enterpictureinpicture',
// 视频离开画中画模式时触发
'leavepictureinpicture',
// 视频源设置完成时触发
'sourceset',
// 视频文本轨道改变时触发
'texttrackchange',
// 视频文本数据更新时触发
'textdata',
// 用户活动时触发
'useractive',
// 用户不活动时触发
'userinactive',
// 视频使用自定义控制栏时触发
'usingcustomcontrols',
// 视频使用原生控制栏时触发
'usingnativecontrols',
// 视频播放器销毁时触发
'dispose',
// 视频插件设置前触发
'beforepluginsetup',
// 视频插件设置完成时触发
'pluginsetup',
// 视频组件尺寸改变时触发
'componentresize',
// 视频播放器尺寸改变时触发
'playerresize',
// 视频播放器被点击时触发
'tap',
// 视频播放器准备好时触发
'ready'
];
// 定义事件
const emits = defineEmits();
type VideoJsPlayer = ReturnType<typeof videojs>;
const state = reactive({
playerOptions: {
// 是否显示控制栏,从 props 获取
controls: props.showControls,
// 是否等浏览器准备好后自动播放,从 props 获取
autoplay: props.autoplay,
// 是否静音,从 props 获取
muted: props.muted,
// 结束后是否重新开始,从 props 获取
loop: props.loop,
// 播放视频源,从 props 获取
// sources: [{ type: 'video/flv', src: props.videoSrc }],
// 为 true 时,播放器具有流畅的大小
fluid: true,
// 播放顺序
techOrder: [ 'flvjs','html5'],
// 音量,从 props 获取
volume: props.volume,
language: 'zh-CN',
// 禁用画中画,从 props 获取
disablePictureInPicture: props.disablePictureInPicture
}
});
const handleMounted = ({ player }: { player: VideoJsPlayer }) => {
// 设置视频源
// 触发自定义事件,将 player 对象传递给父组件
emits('update:playerReady', player);
eventsArr.forEach(event => {
player.on(event, (e) => {
let parameter = {
event: e,
theNameOfTheEvent: event,
videoJsPlayer: player,
}
emits('update:' + event, parameter);
});
});
// 监听播放和暂停事件,更新 v-model 绑定的值
player.on('play', () => {
emits('update:modelValue', true);
});
player.on('pause', () => {
emits('update:modelValue', false);
});
// 根据 v-model 绑定的值控制播放状态
if (props.modelValue) {
player.play();
} else {
player.pause();
}
};
const handleUnmounted = () => {
};
</script>
<style lang="scss" scoped>
/* 组件样式 */
</style>
flv-video-tech.ts 文件
import flvjs from "flv.js";
import FlvExtend from "flv-extend";
import videojs from "video.js";
// 获取 Video.js 的 Html5 技术类实例
const Html5 = videojs.getTech("Html5")! as any;
/**
* 自定义的 FlvJsTech 类,继承自 Video.js 的 Html5 技术类
* 用于支持 FLV 视频的播放,并集成 FlvExtend 以实现自动追帧等功能
*/
export class FlvJsTech extends Html5 {
private flvPlayer: flvjs.Player | null = null;
private flvExtendInstance: FlvExtend | null = null; // 新增 FlvExtend 实例
constructor(options: any, ready: any) {
super(options, ready);
}
/**
* 设置视频源
* @param {string} src - 视频源的 URL
*/
setSrc(src: string) {
this.dispose(); // 销毁之前的播放器
// 初始化 FlvExtend 实例
this.flvExtendInstance = new FlvExtend({
element: this.el_, // 绑定的 HTML 元素
frameTracking: true, // 开启追帧设置
updateOnStart: true, // 点击播放后更新视频
updateOnFocus: false, // 获得焦点后更新视频 注意 如果失去焦点的话 缩小和切换其他页面的时候会自动暂停直播导致播放器在失去焦点时断开连接 浏览器会限制后台标签页的资源使用(尤其是无音频的视频流),自动暂停播放以节省性能。
reconnect: true, // 开启断流重连
trackingDelta: 1, // 追帧最大延迟,延迟超过1s即开启追帧
showLog: true, // 是否显示插件的 log 信息
reconnectInterval: 1000, // 断流重连间隔时间,单位为毫秒
trackingPlaybackRate: 1.5, // 追帧播放速度
});
// 初始化 flvPlayer 实例,并将其与 FlvExtend 关联
this.flvPlayer = this.flvExtendInstance.init(
{
type: "flv", // 视频类型
url: src, // 视频源 URL
isLive: true, // 是否为直播
hasAudio: false, // 是否包含音频
cors: true // 显式启用跨域
},
{
autoCleanupSourceBuffer: true, // 是否自动清理源缓冲区
enableStashBuffer: false, // 是否启用缓存缓冲区
enableWorker: true, // 是否启用 Web Worker
stashInitialSize: 128, // 初始缓存大小
}
) as unknown as flvjs.Player;
this.flvEvent(); // 绑定事件
this.flvPlayer.load(); // 加载视频
this.flvPlayer.play(); // 开始播放
}
/**
* 绑定 flvPlayer 的事件
*/
flvEvent() {
if (this.flvPlayer) {
this.flvPlayer.on(flvjs.Events.ERROR, (errorType, errorDetail, errorInfo) => {
this.trigger("error"); // 触发错误事件
});
}
}
/**
* 销毁实例并清理资源
*/
dispose() {
if (this.flvExtendInstance) {
// 先销毁 FlvExtend 实例,确保内部播放器正确清理
this.flvExtendInstance.destroy();
this.flvExtendInstance = null;
}
// 移除对 flvPlayer 的手动销毁,避免重复操作
this.flvPlayer = null;
}
/**
* 支持的视频格式
* @type {{ 'video/flv': string; 'video/x-flv': string }}
*/
static formats = {
"video/flv": "FLV",
"video/x-flv": "FLV"
};
/**
* 检查当前环境是否支持 FLV 视频播放
* @returns {boolean} - 如果支持则返回 true,否则返回 false
*/
static isSupported = () => flvjs.isSupported();
/**
* 检查指定的视频类型是否可以播放
* @param {string} type - 视频类型
* @returns {string} - 如果支持则返回 'maybe',否则返回空字符串
*/
static canPlayType = (type: string) =>
FlvJsTech.isSupported() && type in FlvJsTech.formats ? "maybe" : "";
/**
* 检查指定的视频源是否可以播放
* @param {any} source - 视频源对象
* @returns {string} - 如果支持则返回 'maybe',否则返回空字符串
*/
static canPlaySource = (source: any) =>
FlvJsTech.isSupported() && source.src.endsWith(".flv") ? "maybe" : "";
}
使用方式
<VideoPlayerWrapper v-model="state.isPlaying" :videoSrc="state.videoSrc" :showControls="true"
:autoplay="true" :muted="true" :loop="false" :volume="0.6" :disablePictureInPicture="true"
@update:usingnativecontrols="onTap" />
import VideoPlayerWrapper from '@/components/VideoPlayerWrapper/index.vue'