场景需求
- 需要画面监控设备实时播放,支持HTTP-FLV直播流,支持其他流后续可能会更换
- 需要类似于安防监控多个视频实时画面同步
- 播放器可控制度强,完全由我们来控制播放暂停进行拉流断流
实现过程
项目使用的是的vue3+ts
1. 使用flv.js
原本使用的video.js 但是不支持HTTP-FLV播放,改用flv.js
引入flv.js
npm install --save flv.js
开发问题和解决方案:
- flv.js视频暂停时会有延迟增长,随着暂停时间越久延迟越长几秒几分钟或者更长
解决办法:手动处理buffer时长,进行删减帧或加速播放 - 我需要当暂停播放按钮触发时呼叫服务端拉流断流,flv.js视频播放时离开此页面或者有遮挡此页面会自动触发video标签的暂停事件,回到此页面时也会自动帮你播放,但不会通知你也不会触发video标签的播放事件
解决办法:执行销毁再创建flv.js
flv.js组件代码(需要请自行修改监听部分逻辑)
<template>
<div class="w-full h-full" v-if="deviceState !== 0">
<video
v-show="createEl"
id="videoElement"
ref="videoElement"
controls
disablePictureInPicture
class="FlvVideo w-full h-full"
@click.prevent="onClick"
@pause="onPause"
muted
@play="play"
></video>
<!--下面部分html是暂停时的画面,flv.js没有创建时是触发不了播放,通过这个暂停画面来创建实例/播放 -->
<!-- class="rounded-md flex justify-center items-center w-full h-full maskLayer" 这个是Tailwind Css语法-->
<div v-show="!createEl" class="rounded-md flex justify-center items-center w-full h-full maskLayer">
<SvgIcon name="loading" size="30" class="rotate" v-if="loading" />
<div class="wrap" @click="onPlay" v-else>
<SvgIcon name="play" size="20" fillColor="#ffffff" class="ml-0.8" />
</div>
</div>
</div>
<div class="rounded-md flex justify-center items-center w-full h-full maskLayer text-light-50" v-else>
设备离线
</div>
</template>
<script setup name="videoFlv" lang="ts">
import { ref, onBeforeUnmount, watch } from "vue"
import flvjs from "flv.js"
import SvgIcon from "/@/components/Icon"
import _ from "lodash-es"
type VideoProps = {
destroy: number | boolean//拉流断流的状态
sources: string//视频地址
status?: number | undefined
loading?: boolean
deviceState?: number
}
const emit = defineEmits(["onPlay", "onPause"])
const props = defineProps<VideoProps>()
let flvPlayer: flvjs.Player | null = null
let videoElement = ref<HTMLMediaElement | null>(null)
let timer: any = null
let createEl = ref<boolean | null>(null)
const pauseState = ref<boolean>(true) //为了避免在销毁时重复执行暂停
// 清除缓存延迟
const buffered = () => {
timer = window.setInterval(() => {
if (videoElement.value && videoElement.value.buffered.length > 0) {
const end = videoElement.value.buffered.end(0) // 视频结尾时间
const current = videoElement.value.currentTime // 视频当前时间
const diff = end - current // 相差时间
const diffCritical = 4 // 这里设定了超过4秒以上就进行跳转
const diffSpeedUp = 1 // 这里设置了超过1秒以上则进行视频加速播放
const maxPlaybackRate = 4 // 自定义设置允许的最大播放速度
let playbackRate = 1.0 // 播放速度
if (diff > diffCritical) {
// console.log("相差超过4秒,进行跳转");
videoElement.value.currentTime = end - 1.5
playbackRate = Math.max(1, Math.min(diffCritical, 16))
} else if (diff > diffSpeedUp) {
// console.log("相差超过1秒,进行加速");
playbackRate = Math.max(1, Math.min(diff, maxPlaybackRate, 16))
}
videoElement.value.playbackRate = playbackRate
}
}, 1000)
}
// 创建flv.js实例
const createVideo = (url) => {
if (flvjs.isSupported()) {
flvPlayer = flvjs.createPlayer(
{
type: "flv",
url, //你的url地址
isLive: true,
hasVideo: true,
hasAudio: true
},
{
enableWorker: false, //不启用分离线程
enableStashBuffer: true, //关闭IO隐藏缓冲区
reuseRedirectedURL: true, //重用301/302重定向url,用于随后的请求,如查找、重新连接等。
autoCleanupSourceBuffer: true, //自动清除缓存
lazyLoad: false, // 去掉懒加载,新增
fixAudioTimestampGap: false //false才会音视频同步,新增
}
)
flvPlayer.attachMediaElement(videoElement.value as HTMLMediaElement)
flvPlayer.load()
flvPlayer.play()
flvPlayer.on(flvjs.Events.ERROR, () => {
flvPlayer && reloadVideo()
})
}
createEl.value = true
}
const antiShake = (Fn) => _.throttle(Fn, 2000, { leading: true })
const onClick = antiShake(() => {
if (!videoElement.value || !flvPlayer) return
if (videoElement.value.paused) {
flvPlayer.play()
} else {
flvPlayer.pause()
}
})
// 这一步其实处理buffer有没有都可以,只不过防止拉流中卡顿的可能性
const play = () => {
buffered()
}
// 自定义暂停页面的paly事件
const onPlay = antiShake(() => {
emit("onPlay", true)
})
const onPause = antiShake(() => {
//初始化静音时页面被遮挡才会由flv.js触发puase
timer && window.clearInterval(timer)
if (!props.status && !pauseState.value) {
destoryVideo()
emit("onPause", false)
pauseState.value = true
}
})
// 重置
const reloadVideo = () => {
destoryVideo()
createVideo(props.sources)
}
// 销毁/创建
const watchDestroy = watch(
() => props.destroy,
() => {
if (props.destroy && createEl.value) {
timer && window.clearInterval(timer)
destoryVideo()
} else if (props.destroy === 0) {
pauseState.value = false
createVideo(props.sources)
}
}
)
// 销毁
const destoryVideo = () => {
if (!pauseState.value) {
flvPlayer!.pause() //暂停播放数据流
pauseState.value = true
}
flvPlayer!.unload() //取消数据流加载
flvPlayer!.detachMediaElement() //将播放实例从节点中取出
flvPlayer!.destroy() //销毁播放实例
flvPlayer = null
createEl.value = false
}
// 组件卸载/清除监听
onBeforeUnmount(() => {
watchDestroy()
timer && window.clearInterval(timer)
props.destroy && createEl.value && destoryVideo()
})
</script>
<style scoped>
html[data-theme="dark"] .wrap,
html[data-theme="dark"] .maskLayer {
background-color: #000;
}
.maskLayer {
background-color: var(--bg-dark-color);
}
@keyframes rotate {
0% {
transform: rotate(0);
}
25% {
transform: rotate(90deg);
}
50% {
transform: rotate(180deg);
}
75% {
transform: rotate(270deg);
}
100% {
transform: rotate(360deg);
}
}
.FlvVide {
object-fit: fill;
}
video::-webkit-media-controls-timeline {
display: none;
}
video::-webkit-media-controls-current-time-display {
display: none;
}
.wrap {
width: 65px;
height: 65px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
background: rgb(154 154 154 / 0%);
border: 2px solid #fff;
cursor: pointer;
}
.rotate {
animation: rotate 3s linear infinite;
}
</style>
我是借鉴这位博主的 https://blog.csdn.net/weixin_45906632/article/details/115031633
这篇分享的也很完整 https://juejin.cn/post/7050739831403446286
2. 使用EasyPlayer.js
上面flv.js已经解决了我一部分的使用需求,但是销毁创建的方法,用户体验很差,只是暂停每次都要等个三秒左右才可以播放(因为要重新创建加拉流这个过程),并且不能支持其他直播协议,不能同时控制多个直播分屏画面(flv.js 创建六个视频共同维护起来很不方便),这些问题EasyPlayer都有做相应的改善
引入EasyPlayer
npm install @easydarwin/easyplayer --save
Vue 集成调用
copy node_modules/@easydarwin/easyplayer/dist/component/EasyPlayer.swf 到 静态文件 根目录
copy node_modules/@easydarwin/easyplayer/dist/component/crossdomain.xml 到 静态文件 根目录
copy node_modules/@easydarwin/easyplayer/dist/component/EasyPlayer-lib.min.js 到 静态文件 根目录
注意: 没有调用会出现无法加载对应插件的报错
在 html 中引用 dist/component/EasyPlayer-lib.min.js
###H.265 copy node_modules/@easydarwin/easyplayer/dist/component/EasyPlayer.wasm 到 静态文件 根目录
详细引入可以查看官网文档 https://github.com/tsingsee/EasyPlayer.js/?tab=readme-ov-file
gitHub打不开的可以看这个https://www.npmjs.com/package/@easydarwin/easyplayer
开发问题和解决方案:
- 引入时,因为官方针对vue的引入方式是npm ,通过npm安装包import引入组件使用,引入组件后一直报错,我以为是没有将文件copy到根目录的原因(官网有说copy node_modules的文件),我又根据文档copy了文件并且在html中引入,还是会有报错
解决方法:只要不用npm的引入即可,也就不用import的导入了,也就是npm安装只是为了让你能copy对应的文件,具体的使用还是通过cdn的方式,copy之后就可以移除包,这么看像是文档有误导了
这篇文章就给了很好的解答 https://juejin.cn/post/7235908012673089573#comment
EasyPlayer组件代码使用就很简单了,暂时还没有需要多屏实时播放,也不需要处理buffer,只需要暂停播放时触发接口就好
<template>
<div class="video-box">
<easy-player
:video-url="sources"
ref="videoEl"
aspect="16:9"
live
autoplay
stretch
@pause="onPause"
@play="onPlay"
:video-title="title || ''"
/>
</div>
</template>
<script setup lang="ts" name="EasyPlayerFlv">
import { ref, onUnmounted } from "vue"
type VideoProps = {
sources: string//url
title?: string//直播名称
}
const emit = defineEmits(["onPlay", "onPause"])
defineProps<VideoProps>()
let videoEl = ref<any>(null)
const onPlay = () => {
emit("onPlay", true)
}
const onPause = () => {
emit("onPause", false)
}
onUnmounted(() => {
console.log("videoEl.value :>> ", videoEl.value)
// easyPlayer.value!.destroyPlayer()
})
</script>
<style scoped>
.video-box {
aspect-ratio: 16/9;
width: 100%;
}
.video-box video {
width: 100%;
object-fit: cover;
}
</style>
3. 使用LivePlayer.js
EasyPlayer多屏实时播放 | 时差 | 延迟处理的很完善,EasyPlayer对于fvl.js的延迟问题采用的是追帧和加速播放,随着你暂停时间越久他追帧速度越慢,并且在追到延迟在10s时就停止了,也就是直播画面总是有10s的延迟(直播设备是测试过没问题的,并且在销毁重创画面是实时的)
后来又发现了livePlayer,测试使用发现以上问题都不存在了,延迟也最慢控制在3s,也是我们项目中能接受的范围
引入
vue2
npm install @liveqing/liveplayer
vue3
npm install @liveqing/liveplayer-v3
第一步 :复制依赖文件(示例 通过 webpack 插件自动复制依赖)
如果正在使用 vue2 + vue-cli, 编辑你的 vue.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
configureWebpack: {
plugins: [
new CopyWebpackPlugin([
{ from: 'node_modules/@liveqing/liveplayer/dist/component/crossdomain.xml'},
{ from: 'node_modules/@liveqing/liveplayer/dist/component/liveplayer.swf'},
{ from: 'node_modules/@liveqing/liveplayer/dist/component/liveplayer-lib.min.js', to: 'js/'},
])
]
}
}
如果正在使用 vue3 + vite, 编辑你的 vite.config.js
import copy from 'rollup-plugin-copy'
export default defineConfig({
plugins: [vue(), copy({
targets: [
{src: 'node_modules/@liveqing/liveplayer-v3/dist/component/liveplayer-lib.min.js', dest: 'public/js'},
]
})]
})
第二步: html模板中引入依赖js
在 html 中引用 www 根目录 liveplayer-lib.min.js
<!DOCTYPE HTML>
<html>
<head>
<title>template</title>
......
<script src="js/liveplayer-lib.min.js"></script>
<!-- 如果正在使用 vue-cli:
<script src="<%= BASE_URL %>js/liveplayer-lib.min.js"></script>
-->
</head>
<body>
......
</body>
</html>
第三步 编辑你的 Vue 组件
import LivePlayer from '@liveqing/liveplayer' // vue2
// import LivePlayer from '@liveqing/liveplayer-v3' // vue3
components: {
LivePlayer
}//vue3,语法糖就不需要这一步了
<LivePlayer :videoUrl="videoUrl" fluent autoplay live stretch></LivePlayer>
如果你上述有报错或者一些问题可以转变成手动复制文件
copy node_modules/@liveqing/liveplayer/dist/component/liveplayer.swf 到根目录
copy node_modules/@liveqing/liveplayer/dist/component/crossdomain.xml 到根目录
copy node_modules/@liveqing/liveplayer/dist/component/liveplayer-lib.min.js 到根目录
我遇到swf文件不能访问的错误,这个问题很简单,只是因为我在html引入LivePlayer文件时没有删除EasyPlayer的引入导致它们互相影响,本来是想做一下比较所以才没有删除EasyPlayer(这时候就要提醒一下引入任何相同插件时确保项目中只有一种此功能插件,可能会因为内部使用相同东西导致冲突,真的会查不到问题在哪,也查不到遇到相关问题的😭)文章来源:https://www.toymoban.com/news/detail-820673.html
更多详细了解可以看官方文档https://www.liveqing.com/docs/manuals/LivePlayer.html#%E5%AE%89%E8%A3%85文章来源地址https://www.toymoban.com/news/detail-820673.html
到了这里,关于vue项目中视频实时播放时 播放器遇到的问题和解决过程 flv.js - EasyPlayer - LivePlayer的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!