系列文章目录
vue前端对接监控视频 hls格式
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
接触智慧园区,智慧工地,水泵站等项目之后,发现都有实时监控的对接,并且是可以多屏进行,就研究了相关的技术栈,找到了这个强大的播放器对前端还是很友好的,话不多说上干货!!!
提示:代码片段里有博主对的接口,读代码的时候注意
一、准备工作
1. 使用npm 、***pm、yarn 下载如下插件
2. npm install xgplayer
3. npm install xgplayer-hls
下好以后在 package.json 检查一下
二、引入到index的文件当中
1.引入库、介绍
代码如下(示例):
import Player from "xgplayer"; // 这是他的一个方法
import FlvPlayer from "xgplayer-hls"; // 这个是搭配上面哪个使用的
import 'xgplayer/dist/index.min.css' // 这个是播放器的样式 ,可以在当前页面使用,也可以放全局
let a = new Player({
id: this.playList[0].id,// 对应<div :id='this.playList[0].id'></div>
isLive: true, // 是否是直播,
plugins: [FlvPlayer], // 视频播放的方式
url: '你的视屏地址', // 地址
autoplay: true, //
lang: "zh-***", // 是否自动播放,
autoplayMuted: true,//是否自动静音自动播放
screenShot: true, //是否使用截图插件
rotate:false,//是否使用旋转插件
download:false,//是否使下载按钮
pip:false,//是否使用画中画插件
mini:false,//是否启用mini小窗插件
playbackRate:[0.5, 0.75, 1, 1.5, 2],//倍速插件显示列表
});
2.开始绘制页面
提示 :这里用到了Element-ui的tree组件,还有博主自己写一些样式就直接拿过来的 取自己用的即可
代码如下(示例):
<template>
<div class="list">
<div class="lsieqqwe">
<div class="boseList bgImg">
<span
@click="getall"
style="color: #fff; margin-left: 12px; font-size: 16px"
>设备分组</span
>
</div>
<div style="padding: 5px">
<el-input
v-model="deptName"
placeholder="请输入部门名称"
clearable
size="small"
prefix-icon="el-icon-search"
style="margin-bottom: 5px"
/>
</div>
<div class="treeDataLsit">
<el-tree
class="filter-tree"
:data="treeData"
ref="tree"
:props="defaultProps"
node-key="id"
default-expand-all
:expand-on-click-node="false"
highlight-current
@node-click="handleNodeClick"
:filter-node-method="filterNode"
>
<span slot-scope="{ node, data }" class="customize-tree-p">
<div class="haha" v-if="node.level == 3">
<img
:src="
data.isOnline == 1
? '/img/isOnli***rue.png'
: '/img/isOnlinefalse.png'
"
alt=""
/>
<span>{{ data.channelName }} </span>
</div>
<span v-else>{{ data.channelName }} </span>
</span>
</el-tree>
</div>
<div
style="
width: 100%;
height: 42%;
display: flex;
flex-direction: column;
position: relative;
"
>
<div
class="control toplst"
v-if="zheDielist"
@click="zheDielist = false"
>
<div></div>
<div>云台控制</div>
<div><img src="@/assets/carmear/xiala.png" alt="" /></div>
</div>
<div v-if="zheDielist" style="height: 90%; flex: 1; margin-top: 65px">
<div class="control_list">
<img
@mousedown="cameraBtn(1)"
@mouseup="mouseup(1)"
class="ShArrows"
src="@/assets/jianTou/shang.png"
alt=""
/>
<img
@mousedown="cameraBtn(2)"
@mouseup="mouseup(2)"
class="XArrows"
src="@/assets/jianTou/xia.png"
alt=""
/>
<img
@mousedown="cameraBtn(3)"
@mouseup="mouseup(3)"
class="ZArrows"
src="@/assets/jianTou/zuo.png"
alt=""
/>
<img
@mousedown="cameraBtn(4)"
@mouseup="mouseup(4)"
class="YArrows"
src="@/assets/jianTou/you.png"
alt=""
/>
<img
@mousedown="cameraBtn(7)"
@mouseup="mouseup(7)"
class="YSArrows"
src="@/assets/jianTou/Yshang.png"
alt=""
/>
<img
@mousedown="cameraBtn(8)"
@mouseup="mouseup(8)"
class="YXArrows"
src="@/assets/jianTou/Yxia.png"
alt=""
/>
<img
@mousedown="cameraBtn(6)"
@mouseup="mouseup(6)"
class="ZXArrows"
src="@/assets/jianTou/Zxia.png"
alt=""
/>
<img
@mousedown="cameraBtn(5)"
@mouseup="mouseup(5)"
class="ZSArrows"
src="@/assets/jianTou/Zshang.png"
alt=""
/>
</div>
<div class="footerLsitStyle_why">
<div
style="padding: 5px"
@mousedown="BigCamera(1)"
@mouseup="BigCameraMouseup(1)"
>
<img src="@/assets/jianTou/magnify.png" alt="" />
</div>
<div
style="padding: 5px; border-left: 2px solid #1f4075"
@mousedown="BigCamera(2)"
@mouseup="BigCameraMouseup(2)"
>
<img src="@/assets/jianTou/reduce.png" alt="" />
</div>
</div>
</div>
<div class="control footer" v-else @click="zheDielist = true">
<div></div>
<div>云台控制</div>
<div><img src="@/assets/carmear/xiala.png" alt="" /></div>
</div>
</div>
</div>
<div style="width: 100%; padding: 10px">
<div class="carStyle">
<div class="shexinag">
<div style="margin-left: 40px; display: flex">
<div
style="padding: 6px; font-size: 17px"
:class="1 == 1 ? 'tableLIstStyle ' : 'table_hui'"
>
实时监控
</div>
<div
style="padding: 5px; font-size: 17px"
:class="1 == 2 ? 'tableLIstStyle' : 'table_hui'"
>
录像回放
</div>
</div>
</div>
</div>
<ul :class="1 != 1 ? 'FenPingCenter_box' : 'FenPingCenter_list_box'">
<li
:class="
item == FenPingCenType ? 'FenPingCenter_Ui' : 'FenPingCenter_li'
"
v-for="(item, index) in IconFontNumber"
:key="item"
:style="{ width: 100 / IconFontTypenumber + '%' }"
>
<div class="FenPingLise" v-if="item == mouType">
<button @click="playerBtnItem(index)">关 闭</button>
</div>
<div
class="divVide"
:id="'FenPingCenter_li' + index"
@mouseenter="mouseenteBtn(item)"
@click="
FenPingCenClick({ type: item, Nme: 'FenPingCenter_li' + index })
"
></div>
</li>
</ul>
<div class="footer_box">
<div class="iconFlist_box">
<span style="color: #fff">分屏 : </span>
<i
@mousedown="FenPing(item)"
v-for="item in IconFont"
:key="item.className"
:class="
item.number == IconFontNumber
? ` iconFlist ${item.className}`
: ` iconStyle ${item.className}`
"
></i>
</div>
<div>
<ul class="operationUl">
<li
v-for="(item, index) in operationArray"
:key="index"
@click="FooterBtn(item)"
>
<div><img :src="item.image" alt="" /></div>
<div>{{ item.name }}</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
定义的变量
data() {
return {
FenPingCenType: null, //
mouType: null,
destroyType: null,// 判断是否销毁
zheDielist: false, // 左侧云台控制显示
uuidv4: '',// uuid 视频放大时使用
treeData: [ // 左侧 tree 的数据
{
id: 1,
channelName: "全部",
children: [
],
},
],
// 图标
IconFontNumber: 1,
IconFontTypenumber: 1,
IconFont: [
{
className: 'iconfont icontiFenPingOne',
number: 1,
},
{
className: 'iconfont icontiFenPingSi',
number: 4,
},
{
className: 'iconfont icontiFenPingLiu',
number: 6,
},
],
operationArray: [ // 分屏数据
// {
// name: '抓拍',
// image: require('../../../assets/carmear/zhuapai.png')
// },
// {
// name: '放大',
// image: require('../../../assets/carmear/BigList.png')
// },
// {
// name: '录像',
// image: require('../../../assets/carmear/luxiang.png')
// },
// {
// name: '音频',
// image: require('../../../assets/carmear/yinpin.png')
// },
// {
// name: '对讲',
// image: require('../../../assets/carmear/duijiang.png')
// },
// {
// name: '全屏',
// image: require('../../../assets/carmear/qaunping.png')
// },
],
// tree 重置 数据的属性名
defaultProps: {
children: 'children',
label: 'channelName'
},
// 部门名称
deptName: undefined, // 用于筛选tree 数据
selectArry: '',
playList: [], // 所有视频的 实例
cameraName: null, // 判断是否选中视频
checkchannelId: null // 调取视频接口 接收 其他页面传过来的数据
};
},
css样式
<style lang="scss" scoped>
.list {
width: 100%;
padding: 20px;
height: 90vh;
display: flex;
overflow-y: auto;
background-color: #040c1e;
.lsieqqwe {
width: 20%;
border: 1px solid #13355a;
.boseList {
padding: 5px;
padding-left: 20px;
font-size: 17px;
}
.bgImg {
background-size: 100%, 100%;
background-image: url("../../../assets/carmear/reight.png");
}
}
.control {
display: flex;
justify-content: space-between;
align-items: center;
text-align: center;
border-top: 2px solid #409eff;
height: 10%;
padding: 5px;
color: #fff;
background: linear-gradient(180deg, #246ab1 0%, #183a74 100%);
img {
width: 30px;
}
}
.control_list {
width: 100%;
height: 210px;
background-image: url("../../../assets/carmear/yuanpan.png");
background-size: 100% 100%;
background-repeat: no-repeat;
position: relative;
.ShArrows {
position: absolute;
top: 0;
left: 0;
margin-top: 9%;
margin-left: 42%;
}
.XArrows {
position: absolute;
top: 0;
left: 0;
margin-top: 58%;
margin-left: 42%;
}
.ZArrows {
position: absolute;
top: 0;
left: 0;
margin-top: 28%;
margin-left: 17%;
}
.YArrows {
position: absolute;
top: 0;
right: 0;
margin-top: 28%;
margin-right: 18%;
}
.YSArrows {
position: absolute;
top: 0;
right: 0;
margin-top: 15%;
margin-right: 28%;
}
.YXArrows {
position: absolute;
top: 0;
right: 0;
margin-top: 50%;
margin-right: 28%;
}
.ZXArrows {
position: absolute;
top: 0;
left: 0;
margin-top: 50%;
margin-left: 28%;
}
.ZSArrows {
position: absolute;
top: 0;
left: 0;
margin-top: 16%;
margin-left: 28%;
}
}
.footerLsitStyle_why {
padding: 5px;
display: flex;
justify-content: space-between;
div {
text-align: center;
flex: 1;
background-color: #183a74;
img {
width: 2rem;
}
}
}
.carStyle {
width: 100%;
height: 50px;
.shexinag {
width: 100%;
height: 100%;
display: flex;
background-size: 100%, 100%;
background-repeat: no-repeat;
background-image: url("../../../assets/carmear/left.png");
}
.tableLIstStyle {
color: #54c0ff;
border-top: 3px solid #54c0ff;
}
.table_hui {
border-top: 3px solid #54c0ff00;
}
}
.footer_box {
display: flex;
padding: 10px;
align-items: center;
border: 1px solid #1d4a92;
justify-content: space-between;
.operationUl {
display: flex;
li {
margin-right: 10px;
text-align: center;
}
img {
width: 30px;
}
}
}
.iconFlist_box {
display: flex;
font-size: 20px;
.iconFlist {
color: #239de7;
font-size: 25px;
margin-right: 15px;
}
.iconStyle {
color: #6e809c;
font-size: 25px;
margin-right: 15px;
}
}
}
.footer {
bottom: 0;
right: 0;
position: absolute;
width: 100%;
height: 45px;
}
.toplst {
top: 0;
left: 0;
position: absolute;
width: 100%;
height: 45px;
}
.FenPingCenter_box {
width: 100%;
height: 80%;
display: flex;
flex-wrap: wrap;
background-color: #ce9292;
.FenPingCenter_li {
border: 1px solid #25a9f6;
background-color: #0c3781;
position: relative;
overflow: hidden;
}
.FenPingCenter_Ui {
border: 2px solid #d18109;
background-color: #0c3781;
position: relative;
overflow: hidden;
}
.FenPingLise {
position: absolute;
z-index: 99999;
}
}
::v-deep.FenPingCenter_list_box:first-child {
width: 66% !important;
border: 10px solid red !important;
}
.FenPingCenter_list_box {
height: 80%;
display: flex;
flex-wrap: wrap;
background-color: #ce9292;
.FenPingCenter_li {
border: 1px solid #25a9f6;
background-color: #0c3781;
position: relative;
overflow: hidden;
}
.FenPingCenter_Ui {
border: 2px solid #d18109;
background-color: #0c3781;
position: relative;
overflow: hidden;
}
.FenPingLise {
position: absolute;
z-index: 99999;
}
}
// .leftbox {
.treeDataLsit {
height: 48%;
// height: 78%;
border: 1px solid #1e5494;
overflow-y: auto;
::v-deep .el-tree {
color: rgba(255, 255, 255, 0.7);
.el-tree-node__content:hover {
background-color: #0c3781 !important;
}
&.el-tree--highlight-current
.el-tree-node.is-current
> .el-tree-node__content {
background-color: #0c3781 !important;
}
}
}
.el-tree--highlight-current {
background-color: #040c1e !important;
}
.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
background-color: #0c3781 !important;
}
.el-tree-node__content {
background-color: red !important;
}
::v-deep .el-input--small .el-input__inner {
background-color: #040c1e !important;
border: none;
}
</style>
<style lang="scss">
.divVide {
width: 100% !important;
position: relative !important;
height: 100%;
// border: 1px solid red;
}
::v-deep video {
height: 50% !important;
}
.player-content {
width: 100%;
height: 10%;
position: absolute;
display: block;
}
// .iconqingxiLOGO {
// display: none !important;
// }
#FenPingCenter_li0,
#FenPingCenter_li1,
#FenPingCenter_li2,
#FenPingCenter_li3,
#FenPingCenter_li4,
#FenPingCenter_li5,
#FenPingCenter_li6,
#FenPingCenter_li7,
#FenPingCenter_li8 {
height: 100% !important;
}
// .xgplayer-fullscreen,
.xgplayer-cssfullscreen,
.xgplayer-volume,
.icon-text {
display: none !important;
}
</style>
三核心代码的书写
- video 划入事件
// video 划入事件
mouseenteBtn(value) {
this.mouType = value;
console.log(11111)
},
- video 划出事件
// video 划入事件
mouseout() {
setTimeout(() => {
this.mouType = null
}, 1000)
},
提示:视频出来后 显示 全屏或者 抓拍的 按钮 这个块不需要 如果是就一个平并且需要写自己的全屏抓拍样式的话可以用道 这个播放器本身带这些功能 只是样式不好看
FooterBtn(FooterObj) {
console.log(FooterObj.name)
if (FooterObj.name == '全屏') {
//
this.playList[this.FenPingCenType - 1].ShiLi.getFullscreen()
} else if (FooterObj.name == '抓拍') {
let aasd = ""
// 这格式掉后端的接口
xxxx({ id: this.defaultPropsBtnBjc.deviceCode }).then(res => {
aasd = res.msg
window.open(aasd).download()
})
}
},
- 分屏按钮 分别为 单屏 、四平、六平
// 分屏按钮
FenPing(value) {
/*
1.当你点击下方的三个图标的时候,分别获取 4 ,3 ,1三个值
对应下面的三个判断
2. 将对应的盒子添加上是shili:'' ,id(对应的上每一个盒子),当前视频的状态
*/
this.IconFontNumber = value.number
console.log(value.number)
if (value.number == 4) {
this.IconFontTypenumber = 2
for (let index = 1; index < 4; index++) {
this.playList.push({ ShiLi: null, id: `${'FenPingCenter_li' + index}`, type: 0, flg: false })
}
} else {
this.IconFontTypenumber = 3
for (let index = 1; index < 6; index++) {
this.playList.push({ ShiLi: null, id: `${'FenPingCenter_li' + index}`, type: 0, flg: false })
}
}
if (value.number == 1) {
this.IconFontTypenumber = 1
this.playList = this.playList.splice(0, 1)
this.$forceUpdate()
}
},
- 每个分屏 的 实例销毁 按钮
playerBtnItem(index) {
console.log(index); // 这个index是下标
/**
1.实例销毁调用.ShiLi.destroy();这个方法
2.初始化当前的数据的状态
*/
this.mouType = null;
this.destroyType = true;
// // 视频的销毁
this.playList[index].ShiLi.destroy();
this.playList[index].ShiLi = null;
this.playList[index].flg = true;
this.playList[index].type = 0;
},
- 联合tree node-click事件
handleNodeClick(value) {
this.defaultPropsBtnBjc = value
console.log(value)
let a = []
/**
* 判断是否是tree的最里层的数据
*/
if (!value.children) {
this.playList.forEach(item => {
if (item.type == 1) {
a.push(item.type)
}
})
// 接口
xxxxx({ channelId: value.channelCode || this.checkchannelId }).then(res => {
let b = a.length
/**
* 判断是否是 选中 容器
* 如果选择容器
* 判断他是否为null 查找到他的下标
* 拿容器ID 生成 播放器实例
* 并且插入到 实例的数据组中
* 将播放状态 改为 1
* */
if (this.cameraName && !this.destroyType) {
let player = new Player({
id: this.cameraName,
isLive: true,
plugins: [FlvPlayer],
url: res.data.url,
autoplay: true,
lang: "zh-***",
autoplayMuted: true,
"screenShot": true
});
this.playList[this.FenPingCenType - 1].ShiLi = player
this.playList[this.FenPingCenType - 1].type = 1
} else {
/**
* 判断是否是 是否是 销毁实例后
* 判断他是否为null 查找到他的下标
* 拿容器ID 生成 播放器实例
* 并且插入到 实例的数据组中
* 将播放状态 改为1
* */
if (this.destroyType) {
let iB = this.playList.findIndex(item => { return item.ShiLi == null })
console.log(iB, 'ppppppp', this.FenPingCenType)
if (this.FenPingCenType) {
this.playList[this.FenPingCenType - 1].ShiLi = new Player({
id: this.cameraName,
isLive: true,
plugins: [FlvPlayer],
url: res.data.url,
autoplay: true,
lang: "zh-***",
autoplayMuted: true,
"screenShot": true
});
this.playList[this.FenPingCenType - 1].type = 1
this.destroyType = !this.playList.every(item => item.type == 1)
// this.classLList()
} else {
this.playList[iB].ShiLi = new Player({
id: this.playList[iB].id,
isLive: true,
plugins: [FlvPlayer],
url: res.data.url,
autoplay: true,
lang: "zh-***",
autoplayMuted: true,
"screenShot": true
});
this.playList[iB].type = 1
this.destroyType = !this.playList.every(item => item.type == 1)
}
} else {
for (let i = 0; i < this.playList.length; i++) {
if (this.IconFontTypenumber == 1) {
this.playList.forEach(item => {
if (item.ShiLi != null) {
item.ShiLi.destroy();
item.ShiLi = null;
item.type = 0;
let a = new Player({
id: this.playList[0].id,
isLive: true,
plugins: [FlvPlayer],
url: res.data.url,
autoplay: true,
lang: "zh-***",
autoplayMuted: true,
"screenShot": true,
});
item.ShiLi = a
}
return item
})
}
console.log(9999)
if (this.playList[i].ShiLi == null) {
this.playList[b].ShiLi = new Player({
id: this.playList[b].id,
isLive: true,
plugins: [FlvPlayer],
url: res.data.url,
autoplay: true,
lang: "zh-***",
autoplayMuted: true,
"screenShot": true,
playsinline: true,
height: window.innerHeight,
width: window.innerWidth
});
this.playList[b].type = 1
break;
}
}
}
}
})
}
},
- 下面的代码是防止报错加的
// 云台控制必须是球机-上下左右转动
cameraBtn(value) {},
// 停止事件xx`q
mouseup(value) {},
// 放大开始事件
BigCamera(value) {},
// 放大结束事件
BigCameraMouseup(value) {}