使用 Vue 和 flv.js 实现流媒体视频播放:完整教程
在调用 video的 play() 方法之后就立即被之后一次调用 pause() 方法中断了。那么上述的问题就有了解决方法: 在 play() 执行成功后,播放视频,然后执行后续操作。功能:点击右边菜单出现实时监控画面,页面有四个窗体,每个窗体播放不同的视频,当四个窗体都在播放时,点击下一个会依次替换每个窗体,随机点四个页面会出现对应的实时视频。EasyPlayer flv m3u8 ws
写在前面 后来项目更新采用的是EasyPlayer插件
文章地址:
VUE项目中优雅使用EasyPlayer
简单介绍下:
Flv.js 是 HTML5 Flash 视频(FLV)播放器,纯原生 JavaScript 开发,没有用到 Flash。由 bilibili 网站开源。
开源地址: https://github.com/Bilibili/flv.js/
先看一下页面效果:
功能:点击右边菜单出现实时监控画面,页面有四个窗体,每个窗体播放不同的视频,当四个窗体都在播放时,点击下一个会依次替换每个窗体,随机点四个页面会出现对应的实时视频
开发问题1: 离开页面视频会暂停
当用户离开页面时,浏览器默认会暂停视频以节省资源。为了解决这个问题,可以尝试以下方法:
使用 Page Visibility API 检测页面的可见性状态。当页面变为不可见时,可以继续播放视频,但要确保视频的实时性不会受影响。
在 setInterval 内部继续调用 videoElement.play() 以确保视频保持播放状态。
开发问题2: 页面会疯狂报错 (不影响功能,,有报错信息)
错误提示 The play() request was interrupted by a call to pause()
是由于 play() 调用后立即调用了 pause()。
解决方法:通过 await 关键字等待 play() 的 Promise 返回成功后,再执行后续操作。或者在 play() 方法的 Promise 中处理 pause()。
开发问题3: 四个窗体都在播放时,点击下一个视频会依次替换每个窗体
this.flvPlayerList.shift() 删除第一个视频并重新加载新视频。
开发问题4: 怎么断掉上一个视频的推流 (视频会卡顿)
destoryVideo 方法中,调用 pause()、unload()、detachMediaElement() 和 destroy() 的方式。确保在销毁之前将 flvPlayer 设置为 null,避免在播放过程中重复创建和销毁实例导致的卡顿问题。
开发问题5: 怎么断开重新连接
已经实现了重新连接机制,即在 ERROR 事件触发时,调用 reloadVideo() 方法。你可以考虑在重新连接前添加一个短暂的延迟,以减少重新连接时可能产生的冲突。
相关代码:
下载包 cnpm install --save flv.js
页面结构
<div class="video">
<video
v-for="i in 4"
:id="'videoElement' + i"
controls
autoplay
muted
></video>
</div>
data部分
data() {
return {
flvPlayer: null,
url: "",
count: 1, // 当前点击标识
flvPlayerList: []
};
},
创建实例
createVideo() {
if (flvjs.isSupported()) {
var videoElement = document.getElementById("videoElement" + this.count);
this.flvPlayer = flvjs.createPlayer(
{
type: "flv",
isLive: true,
hasAudio: false,
url: this.url
},
{
enableWorker: false, //不启用分离线程
enableStashBuffer: false, //关闭IO隐藏缓冲区
reuseRedirectedURL: true, //重用301/302重定向url,用于随后的请求,如查找、重新连接等。
autoCleanupSourceBuffer: true //自动清除缓存
}
);
this.flvPlayer.attachMediaElement(videoElement);
// this.flvPlayer.load();
if (this.url !== "" && this.url !== null) {
this.flvPlayer.load();
this.flvPlayer.play();
}
}
//定时方法是为了用户离开页面视频是实时播放的,暂停按钮无用
setInterval(function() {
// console.log(videoElement.buffered,"idididid");
if (videoElement.buffered.length > 0) {
const end = videoElement.buffered.end(0); // 视频结尾时间
const current = videoElement.currentTime; // 视频当前时间
const diff = end - current; // 相差时间
// console.log(diff);
const diffCritical = 4; // 这里设定了超过4秒以上就进行跳转
const diffSpeedUp = 1; // 这里设置了超过1秒以上则进行视频加速播放
const maxPlaybackRate = 4; // 自定义设置允许的最大播放速度
let playbackRate = 1.0; // 播放速度
if (diff > diffCritical) {
// this.flvPlayer.load();
// console.log("相差超过4秒,进行跳转");
videoElement.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.playbackRate = playbackRate;
if (videoElement.paused) {
videoElement.play();
}
}
// if (videoElement.buffered.length) {
// let end = this.flvPlayer.buffered.end(0);//获取当前buffered值
// let diff = end - this.flvPlayer.currentTime;//获取buffered与currentTime的差值
// if (diff >= 0.5) {//如果差值大于等于0.5 手动跳帧 这里可根据自身需求来定
// this.flvPlayer.currentTime = this.flvPlayer.buffered.end(0);//手动跳帧
// }
// }
}, 1000);
this.flvPlayer.on(flvjs.Events.ERROR, (errType, errDetail) => {
// alert("网络波动,正在尝试连接中...");
if (this.flvPlayer) {
this.reloadVideo(this.flvPlayer);
}
// errType是 NetworkError时,对应errDetail有:Exception、HttpStatusCodeInvalid、ConnectingTimeout、EarlyEof、UnrecoverableEarlyEof
// errType是 MediaError时,对应errDetail是MediaMSEError 或MEDIA_SOURCE_ENDED
});
this.flvPlayerList.push(this.flvPlayer);
},
断开重连机制
this.flvPlayer.on(flvjs.Events.ERROR, (errType, errDetail) => {
// alert("网络波动,正在尝试连接中...");
if (this.flvPlayer) {
this.reloadVideo(this.flvPlayer);
}
// errType是 NetworkError时,对应errDetail有:Exception、HttpStatusCodeInvalid、ConnectingTimeout、EarlyEof、UnrecoverableEarlyEof
// errType是 MediaError时,对应errDetail是MediaMSEError 或MEDIA_SOURCE_ENDED
});
销毁断流方法
destoryVideo(flvPlayer) {
flvPlayer.pause();
flvPlayer.unload();
flvPlayer.detachMediaElement();
flvPlayer.destroy();
flvPlayer = null;
},
reloadVideo(flvPlayer) {
this.destoryVideo(flvPlayer);
this.createVideo();
},
左边菜单点击的方法
clickhandleitem(data, index) {
let ip = data.ipAddress;
let admin = data.videoname;
let password = data.videopas;
this.url =
"ws://服务地址:端口号/live?url=rtsp://" +
admin +
":" +
password +
"@" +
ip +
"/media/video2/multicast";
if (this.flvPlayerList.length > 3) {
this.destoryVideo(this.flvPlayerList[0]);
this.flvPlayerList.shift();
}
this.createVideo();
this.count > 3 ? (this.count = 1) : this.count++;
},
附上页面所有代码
<template>
<div class="videobox">
<div class="videolist">
<div class="search">
<el-input
clearable
v-model="inputvalue"
placeholder="请输入内容"
size="mini"
></el-input>
<el-button type="primary" size="mini" @click="clicksearch"
>查询</el-button
>
<ul>
<li
v-for="(item, index) in list"
@click="clickhandleitem(item, index)"
>
<i class="el-icon-video-camera-solid"></i>
{{ item.devicename + item.deviceAddr }}
</li>
</ul>
</div>
</div>
<div class="video">
<video
v-for="i in 4"
:id="'videoElement' + i"
controls
autoplay
muted
></video>
</div>
</div>
</template>
<script>
import flvjs from "flv.js";
import { mapGetters } from "vuex";
import { findAlllist } from "@/api/admin/equipmentlist";
export default {
data() {
return {
flvPlayer: null,
inputvalue: "",
devicename: "60",
url: "",
list: [],
count: 1, // 当前点击标识
flvPlayerList: []
};
},
computed: {
...mapGetters(["communityId"])
},
created() {
this.findAlllistApi();
},
methods: {
//查询
clicksearch() {
this.findAlllistApi();
},
clickhandleitem(data, index) {
let ip = data.ipAddress;
let admin = data.videoname;
let password = data.videopas;
this.url =
"ws://服务地址:端口/live?url=rtsp://" +
admin +
":" +
password +
"@" +
ip +
"/media/video2/multicast";
if (this.flvPlayerList.length > 3) {
this.destoryVideo(this.flvPlayerList[0]);
this.flvPlayerList.shift();
}
this.createVideo();
this.count > 3 ? (this.count = 1) : this.count++;
},
play(flvPlayer) {
flvPlayer.play();
},
createVideo() {
if (flvjs.isSupported()) {
var videoElement = document.getElementById("videoElement" + this.count);
this.flvPlayer = flvjs.createPlayer(
{
type: "flv",
isLive: true,
hasAudio: false,
url: this.url
},
{
enableWorker: false, //不启用分离线程
enableStashBuffer: false, //关闭IO隐藏缓冲区
reuseRedirectedURL: true, //重用301/302重定向url,用于随后的请求,如查找、重新连接等。
autoCleanupSourceBuffer: true //自动清除缓存
}
);
this.flvPlayer.attachMediaElement(videoElement);
// this.flvPlayer.load();
if (this.url !== "" && this.url !== null) {
this.flvPlayer.load();
this.flvPlayer.play();
}
}
//定时方法是为了用户离开页面视频是实时播放的,暂停按钮无用
setInterval(function() {
// console.log(videoElement.buffered,"idididid");
if (videoElement.buffered.length > 0) {
const end = videoElement.buffered.end(0); // 视频结尾时间
const current = videoElement.currentTime; // 视频当前时间
const diff = end - current; // 相差时间
// console.log(diff);
const diffCritical = 4; // 这里设定了超过4秒以上就进行跳转
const diffSpeedUp = 1; // 这里设置了超过1秒以上则进行视频加速播放
const maxPlaybackRate = 4; // 自定义设置允许的最大播放速度
let playbackRate = 1.0; // 播放速度
if (diff > diffCritical) {
// this.flvPlayer.load();
// console.log("相差超过4秒,进行跳转");
videoElement.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.playbackRate = playbackRate;
if (videoElement.paused) {
videoElement.play();
}
}
// if (videoElement.buffered.length) {
// let end = this.flvPlayer.buffered.end(0);//获取当前buffered值
// let diff = end - this.flvPlayer.currentTime;//获取buffered与currentTime的差值
// if (diff >= 0.5) {//如果差值大于等于0.5 手动跳帧 这里可根据自身需求来定
// this.flvPlayer.currentTime = this.flvPlayer.buffered.end(0);//手动跳帧
// }
// }
}, 1000);
this.flvPlayer.on(flvjs.Events.ERROR, (errType, errDetail) => {
// alert("网络波动,正在尝试连接中...");
if (this.flvPlayer) {
this.reloadVideo(this.flvPlayer);
}
// errType是 NetworkError时,对应errDetail有:Exception、HttpStatusCodeInvalid、ConnectingTimeout、EarlyEof、UnrecoverableEarlyEof
// errType是 MediaError时,对应errDetail是MediaMSEError 或MEDIA_SOURCE_ENDED
});
this.flvPlayerList.push(this.flvPlayer);
},
reloadVideo(flvPlayer) {
this.destoryVideo(flvPlayer);
this.createVideo();
},
destoryVideo(flvPlayer) {
flvPlayer.pause();
flvPlayer.unload();
flvPlayer.detachMediaElement();
flvPlayer.destroy();
flvPlayer = null;
},
findAlllistApi() {
findAlllist(this.communityId, this.inputvalue, this.devicename).then(
res => {
this.list = res.data;
}
);
}
}
};
</script>
<style lang="scss" scoped>
.videobox {
display: flex;
/* justify-content: space-between; */
/* flex-wrap: wrap; */
}
.videolist {
width: 550px;
height: 906px;
display: flex;
.search {
margin-left: 50px;
.el-input {
width: 300px;
margin-top: 20px;
}
ul {
width: 100%;
height: 777px;
overflow: hidden;
overflow-y: auto;
list-style: none;
li {
padding: 7px;
margin: 1px 0;
text-decoration: none;
white-space: nowrap;
font-size: 14px;
cursor: pointer;
&:hover {
background: #fff;
}
.el-icon-video-camera-solid {
font-size: 16px;
color: #67c23a;
}
}
}
}
}
.video {
display: flex;
flex-wrap: wrap;
width: 100%;
justify-content: space-evenly;
video {
object-fit: contain;
width: 600px;
height: 390px;
}
}
</style>
更多推荐
所有评论(0)