技术需求请看上一篇文章,这篇使用vue实现视频监控(可直接复制代码运行)
支持摄像头多开,点击全屏播放,窗口拖拽,操控摄像头上下左右旋转放大缩小等操作
vue父组件页面
<template>
<div class="monitorPageBox">
<div class="controlHide" v-if="controlShow">
<i
class="el-icon-d-arrow-right"
@click="controlShow = false"
title="展开控制台"
style="cursor:pointer"
></i>
</div>
<div class="monitorList" v-else>
<div class="monitorListTitleBox">
<div class="monitorListTitle">
当前摄像头
</div>
<div class="monitorListIcon">
<i
class="el-icon-d-arrow-left"
@click="controlShow = true"
title="隐藏控制台"
style="cursor:pointer"
></i>
</div>
</div>
<div class="displaysTypeBox">
<div
class="displaysNumber"
v-for="(item, index) in displaysType"
:key="index"
:class="displaysTypeStyle(index)"
>
<div @click="displaysTypeClick(index)" style="cursor:pointer">
{{ item }}
</div>
</div>
</div>
<div class="searchInputBox">
<el-input placeholder="输入摄像头名称" v-model="monitorName"
><el-button
slot="append"
icon="el-icon-search"
@click="queryEquipment"
></el-button
></el-input>
</div>
<div class="monitorEquipmentDataBox">
<el-scrollbar style="height: 100%">
<div
class="monitorEquipmentBox"
v-for="(item, index) in videoList"
:key="index"
@dblclick="monitorControl(item)"
style="cursor:pointer"
:title="
monitorPlay(item.id) ? `双击关闭${item.ip}` : `双击播放${item.ip}`
"
>
<div class="monitorIPText" draggable="true" @dragstart="drag(item)">
<span>{{ item.ip }} </span>
<span v-if="monitorPlay(item.id)"
>--摄像头{{ playMonitor(item.id) }}</span
>
</div>
<div class="monitorControlBox">
<i
class="el-icon-circle-close"
v-if="monitorPlay(item.id)"
style="color:#ff3b30;"
></i>
<i class="el-icon-video-play" style="color:#1c9eff;" v-else></i>
</div>
</div>
</el-scrollbar>
</div>
<div class="controlBox" v-if="selectMonitorID">
<div class="directionBox">
<div
class="directionButton"
v-for="(item, index) in directionData"
:key="index"
@click="index == 4 ? rotatePreset('右') : ''"
@mousedown="index != 4 ? move($event, index) : ''"
@mouseup="end('direction')"
@mouseout="end('direction')"
>
{{ item }}
</div>
</div>
<div class="focusingBox">
<div
class="equipmentOperationBox"
v-for="(item, index) in equipmentOperationData"
:key="index"
@mousedown="equipmentOperation($event, index)"
@mouseup="index < 2 ? end('operation') : ''"
@mouseout="index < 2 ? end('operation') : ''"
>
{{ item }}
</div>
<div class="rotateTimeoutBox">
<el-input-number
v-model="timeout"
style="width: 100px"
></el-input-number>
</div>
</div>
</div>
<div class="speedSliderBox" v-if="selectMonitorID">
<el-slider v-model="moveSpeed" :min="20"></el-slider>
</div>
<div class="presuppositionBox" v-if="selectMonitorID">
<el-scrollbar style="height: 100%">
<div
class="presupposition"
v-for="item in presuppositionData"
:key="item.$.token"
>
<div
class="presuppositionText"
:style="existencePreset(item.PTZPosition) ? 'color:#bbbbbb' : ''"
>
{{ item.Name }}
</div>
<div
class="presuppositionIcon"
v-if="presetSetUp(item.$.token)"
:style="existencePreset(item.PTZPosition) ? 'color:#bbbbbb' : ''"
>
<div class="IconBox">
<i
class="el-icon-more"
title="功能"
@click="openPreset = item.$.token"
></i>
</div>
</div>
<div class="presuppositionIcon" v-else>
<div class="IconBox">
<i
class="el-icon-s-tools"
title="设置"
@click="setPreset(item)"
:style="
existencePreset(item.PTZPosition) ? 'color:#bbbbbb' : ''
"
></i>
</div>
<!-- <div class="IconBox">
<i
title="删除"
@click="removePreset(item)"
class="el-icon-error"
></i>
</div> -->
<div class="IconBox">
<i
v-if="!existencePreset(item.PTZPosition)"
title="前往"
@click="gotoPreset(item)"
class="el-icon-s-promotion"
></i>
</div>
</div>
</div>
</el-scrollbar>
</div>
</div>
<div class="monitorShowBox" id="monitorBox">
<div
v-for="item in displaysNumber"
:key="item"
:style="monitorNumberStyle(canvasStyle)"
class="monitorRevealBox"
:class="
selectMonitor(videoData[item - 1] ? videoData[item - 1].id : false)
"
@dragover.prevent="allowDrop($event)"
@drop="drop(item - 1)"
>
<div
v-if="videoPlay(item - 1)"
@click="selectVideo(videoData[item - 1].id)"
>
<canvasVideo
:videoId="`video-canvas` + videoData[item - 1].id"
:canvasData="videoData[item - 1]"
:width="canvasStyle.canvasWidth"
:height="canvasStyle.canvasHeight"
></canvasVideo>
</div>
<div v-else>请添加摄像头{{ item }}</div>
</div>
</div>
</div>
</template>
<script>
import canvasVideo from "../../components/videoPage/canvasVideo"; //视频测试页
import elementResizeDetectorMaker from "element-resize-detector"; //element元素宽高变化
export default {
name: "monitorPage",
components: {
canvasVideo
},
data() {
return {
videoData: [""], //视频的数组
monitorName: "", //查询的设备名称
canvasStyle: {
//摄像头样式
canvasWidth: `0px`,
canvasHeight: `0px`
},
//摄像头方向数据
directionData: [
"左上",
"向上",
"上右",
"向左",
"旋转",
"向右",
"左下",
"向下",
"下右"
],
//设备功能数据
equipmentOperationData: ["放大", "缩小", "左旋", "右旋"],
controlShow: false, //控制台显影
//摄像头列表
videoList: [
{
id: 90,
name: `admin`,
password: `Szzgkon2016`,
ip: `192.168.1.50`,
flow: "ch1"
},
{
id: 91,
name: `admin`,
password: `Szzgkon@2016`,
ip: `192.168.1.51`,
flow: "ch1"
},
{
id: 92,
name: `admin`,
password: `Szzgkon2016`,
ip: `192.168.1.50`,
flow: "ch1"
},
{
id: 93,
name: `admin`,
password: `Szzgkon@2016`,
ip: `192.168.1.51`,
flow: "ch1"
},
{
id: 94,
name: `admin`,
password: `Szzgkon2016`,
ip: `192.168.1.50`,
flow: "ch1"
},
{
id: 95,
name: `admin`,
password: `Szzgkon@2016`,
ip: `192.168.1.51`,
flow: "ch1"
},
{
id: 96,
name: `admin`,
password: `Szzgkon2016`,
ip: `192.168.1.50`,
flow: "ch1"
},
{
id: 97,
name: `admin`,
password: `Szzgkon@2016`,
ip: `192.168.1.51`,
flow: "ch1"
},
{
id: 98,
name: `admin`,
password: `Szzgkon2016`,
ip: `192.168.1.50`,
flow: "ch1"
},
{
id: 99,
name: `admin`,
password: `szzgkon@2016`,
ip: `192.168.0.55`,
flow: "ch33"
}
],
displaysType: ["1*1", "2*2", "3*2", "3*3"], //摄像头的展示数量
displaysTypeIndex: 0, //选中的摄像头展示数量
displaysNumber: 0, //展示摄像头的数量
moveData: {}, //拖拽的数据
moveTimer: null, //定时器
moveSpeed: 75, //默认移动速度
selectMonitorID: null, //选中的摄像头
device: { xaddr: "", user: "", pass: "" }, //当前选中的摄像头数据
presuppositionData: [], //预测点数据
openPreset: null, //设置预置点
timeout: 10 //摄像头旋转时间
};
},
mounted() {
//添加element动态改变摄像展示页大小
this.erd = elementResizeDetectorMaker();
let _this = this;
_this.$nextTick(() => {
_this.erd.listenTo(document.getElementById("monitorBox"), element => {
let timer = setTimeout(function() {
if (!_this.checkFull()) {
_this.canvasStyleChange();
}
}, 100);
});
});
_this.displaysNumber = 1;
this.$socket.open();//局部引入socket
},
beforeDestroy() {
this.$socket.close();//退出组件时关闭socket
},
sockets: {
// 连接后台socket
connect() {
console.log("socket 连接成功");
},
//获取测试数据
devicePresupposition(data) {
this.presuppositionData = data.GetPresetsResponse.Preset;
}
},
watch: {
selectMonitorID(nval, oval) {
let deviceData = this.videoList.filter(item => {
return item.id == this.selectMonitorID;
});
this.device = {
xaddr: `http://${deviceData[0].ip}/onvif/device_service`,
user: deviceData[0].name,
pass: deviceData[0].password
};
}
},
methods: {
//摄像头旋转
rotatePreset(type) {
let speedX;
if (type == "左") {
speedX = -this.moveSpeed / 100;
} else {
speedX = this.moveSpeed / 100;
}
this.$socket.emit("rotatePreset", this.device, speedX, this.timeout);
},
//预置点是否存在
existencePreset(item) {
let x = item.PanTilt.$.x;
let y = item.PanTilt.$.y;
let z = item.Zoom.$.x;
if (x == y && z == 0) {
return true;
} else {
return false;
}
},
//设置预设点
setPreset(data) {
this.openPreset = null;
this.$socket.emit("setPreset", this.device, data);
},
//删除预置点
removePreset(data) {
this.openPreset = null;
this.$socket.emit("removePreset", this.device, data);
},
//前往预设点
gotoPreset(data) {
this.openPreset = null;
this.$socket.emit("gotoPreset", this.device, data);
},
//打开预置点功能区
presetSetUp(token) {
if (this.openPreset == token) {
return false;
} else {
return true;
}
},
//设备操作按钮
equipmentOperation(e, index) {
if (!this.selectMonitorID) {
this.$message({
message: "尚未选择摄像头",
type: "warning"
});
return;
}
if (index == 0 || index == 1) {
this.operatioDirection(index);
this.moveTimer = setInterval(() => {
this.operatioDirection(index);
}, 500);
} else {
if (index == 2) {
this.rotatePreset("左");
} else if (index == 3) {
this.rotatePreset("右");
}
}
},
//设备操作
operatioDirection(index) {
let z = 1;
if (index == 0) {
z = this.moveSpeed / 100;
} else if (index == 1) {
z = -this.moveSpeed / 100;
}
let speed = { x: 0, y: 0, z: z };
this.$socket.emit("move", this.device, speed);
},
//按下移动摄像头
move(e, index) {
if (!this.selectMonitorID) {
this.$message({
message: "尚未选择摄像头",
type: "warning"
});
return;
}
this.moveDirection(index);
this.moveTimer = setInterval(() => {
this.moveDirection(index);
}, 500);
},
//释放停止移动摄像头
end(type) {
if (!this.selectMonitorID) {
return;
}
if (!this.moveTimer) {
return;
}
window.clearInterval(this.moveTimer);
this.moveTimer = null;
this.$socket.emit("stop", this.device);
},
//获取预设点
getPresupposition() {
this.$socket.emit("presupposition", this.device);
},
//摄像头移动的方向
moveDirection(type) {
let x = 0;
let y = 0;
if (type == 0) {
x = -this.moveSpeed / 100;
y = this.moveSpeed / 100;
} else if (type == 1) {
y = this.moveSpeed / 100;
} else if (type == 2) {
x = this.moveSpeed / 100;
y = this.moveSpeed / 100;
} else if (type == 3) {
x = -this.moveSpeed / 100;
} else if (type == 5) {
x = this.moveSpeed / 100;
} else if (type == 6) {
x = -this.moveSpeed / 100;
y = -this.moveSpeed / 100;
} else if (type == 7) {
y = -this.moveSpeed / 100;
} else if (type == 8) {
x = this.moveSpeed / 100;
y = -this.moveSpeed / 100;
}
let speed = { x: x, y: y, z: 0 };
this.$socket.emit("move", this.device, speed);
},
//当前选中的摄像头
selectMonitor(id) {
if (id == this.selectMonitorID) {
return "selectMonitorClass";
}
},
//点击选中的摄像头
selectVideo(id) {
this.selectMonitorID = id;
this.getPresupposition();
},
//拖拽触发
drag(item) {
this.moveData = item;
},
//拖拽时触发
allowDrop(e) {},
//拖拽释放
drop(index) {
this.monitorModify(this.moveData, index);
},
//播放的摄像位置
playMonitor(id) {
let num;
this.videoData.forEach((item, index) => {
if (item.id == id) {
num = index;
}
});
return num + 1;
},
// 判断全屏
checkFull() {
//判断浏览器是否处于全屏状态 (需要考虑兼容问题)
//火狐浏览器
let isFull =
document.mozFullScreen ||
document.fullScreen ||
//谷歌浏览器及Webkit内核浏览器
document.webkitIsFullScreen ||
document.webkitRequestFullScreen ||
document.mozRequestFullScreen ||
document.msFullscreenEnabled;
if (isFull === undefined) {
isFull = false;
}
return isFull;
},
//该视频是否正在播放
monitorPlay(id) {
let play = false;
this.videoData.forEach(item => {
if (item.id == id) {
play = true;
}
});
return play;
},
//当前播放的视频
videoPlay(index) {
let play = false;
if (this.videoData[index]) {
play = true;
}
if (this.videoData[index] == "") {
play = false;
}
return play;
},
//操作摄像头
monitorControl(item) {
let _this = this;
if (_this.monitorPlay(item.id)) {
_this.monitorClose(item.id);
} else {
_this.monitorData(item);
}
},
//查询摄像头
queryEquipment() {
console.log("查询" + this.monitorName);
},
//摄像头切换
pageChange(video) {
let params = [];
video.forEach(item => {
params.push({
id: item.id,
ip: item.ip,
rtsp: `rtsp://${item.name}:${item.password}@${item.ip}:554/h264/${item.flow}/sub/av_stream`
});
});
this.axios
.post("http://localhost:3120/open", params)
.then(req => {
let data = req.data;
this.videoData = [];
data.forEach(item => {
this.videoData.push({ id: item.id, port: item.port });
});
})
.catch(err => {
console.log(err);
});
},
//传递摄像头数据
monitorData(video) {
let play = true;
for (let i = 0; i < this.videoData.length; i++) {
if (this.videoData[i] == "") {
play = false;
break;
}
}
if (play && this.videoData.length == this.displaysNumber) {
this.$message({
message: "页面摄像头数已满,请切换摄像头或关闭摄像头后再添加",
type: "warning"
});
return;
}
let params = [];
params.push({
id: video.id,
ip: video.ip,
rtsp: `rtsp://${video.name}:${video.password}@${video.ip}:554/h264/${video.flow}/sub/av_stream`
});
console.log("params", params);
this.selectMonitorID = video.id;
this.axios
.post("http://localhost:3120/open", params)
.then(req => {
let data = req.data;
let video = true;
for (let i = 0; i < this.videoData.length; i++) {
if (this.videoData[i] == "") {
this.videoData.splice(i, 1, data[0]);
video = false;
break;
}
}
if (video) {
this.videoData = this.videoData.concat(data);
}
this.getPresupposition();
})
.catch(err => {
console.log(err);
});
},
//关闭视频
monitorClose(id) {
for (let i = 0; i < this.videoData.length; i++) {
if (this.videoData[i].id == id) {
this.videoData.splice(i, 1, "");
break;
}
}
this.selectMonitorID = null;
this.presuppositionData = [];
},
//切换视频
monitorModify(item, index) {
let rtsp = `rtsp://${item.name}:${item.password}@${item.ip}:554/h264/${item.flow}/sub/av_stream`;
let params = { id: item.id, ip: item.ip, rtsp: rtsp };
this.selectMonitorID = item.id;
this.axios
.post("http://localhost:3120/modify", params)
.then(req => {
let data = req.data;
this.videoData.splice(index, 1, "");
this.videoData.forEach((item, index) => {
if (item.id == data.id) {
this.videoData.splice(index, 1, "");
}
});
this.$nextTick(() => {
this.videoData.splice(index, 1, {
id: data.id,
port: data.port
});
this.getPresupposition();
});
})
.catch(err => {
console.log(err);
});
},
//选中的摄像头展示类型
displaysTypeStyle(index) {
if (this.displaysTypeIndex == index) {
return "selectDisplaysType";
}
},
//摄像头的数量类型
displaysTypeClick(index) {
this.displaysTypeIndex = index;
if (this.displaysTypeIndex == 0) {
this.displaysNumber = 1;
} else if (this.displaysTypeIndex == 1) {
this.displaysNumber = 4;
} else if (this.displaysTypeIndex == 2) {
this.displaysNumber = 6;
} else {
this.displaysNumber = 9;
}
this.videoPlayNum(this.displaysNumber);
this.canvasStyleChange();
},
//切换摄像头数量
videoPlayNum(num) {
let _this = this;
let video = JSON.parse(JSON.stringify(_this.videoData));
_this.videoData = [];
this.$nextTick(() => {
for (let i = 0; i < video.length; i++) {
if (video[i] != "") {
this.videoData.push(video[i]);
if (_this.videoData.length == num) {
break;
}
}
}
for (let i = 0; i < num; i++) {
if (_this.videoData.length == num) {
break;
} else {
_this.videoData.push("");
}
}
});
},
//可展示的摄像组盒子
monitorNumberStyle(canvasStyle) {
let style;
const monitorBox = document.getElementById("monitorBox");
let width = monitorBox.getBoundingClientRect().width - 2;
let height = monitorBox.getBoundingClientRect().height - 2;
if (this.displaysTypeIndex == 0) {
style = `width:${width}px; height:${height}px;`;
} else if (this.displaysTypeIndex == 1) {
style = `width:${width / 2}px; height:${height / 2}px;`;
} else if (this.displaysTypeIndex == 2) {
style = `width:${width / 3}px; height:${height / 2}px;`;
} else {
style = `width:${width / 3}px; height:${height / 3}px;`;
}
return style;
},
//摄像头展示数量
monitorBoxStyle(canvasStyle) {
return `width:${canvasStyle.canvasWidth}px; height:${canvasStyle.canvasHeight}px;float:left`;
},
//摄像页面宽高变化
canvasStyleChange() {
const monitorBox = document.getElementById("monitorBox");
let width = monitorBox.getBoundingClientRect().width;
let height = monitorBox.getBoundingClientRect().height;
if (this.displaysTypeIndex == 0) {
this.canvasStyle = {
canvasWidth: width - 4 + `px`,
canvasHeight: height - 4 + `px`
};
} else if (this.displaysTypeIndex == 1) {
this.canvasStyle = {
canvasWidth: (width - 6) / 2 + `px`,
canvasHeight: (height - 6) / 2 + `px`
};
} else if (this.displaysTypeIndex == 2) {
this.canvasStyle = {
canvasWidth: (width - 8) / 3 + `px`,
canvasHeight: (height - 6) / 2 + `px`
};
} else {
this.canvasStyle = {
canvasWidth: (width - 8) / 3 + `px`,
canvasHeight: (height - 8) / 3 + `px`
};
}
}
}
};
</script>
<style lang="less" scoped>
.box(@width:"100%",@height:"100%",@size:"14px") {
width: @width;
height: @height;
font-size: @size;
}
.textBox(@width,@height,@align:center) {
width: @width;
height: @height;
line-height: @height;
text-align: @align;
}
.monitorPageBox {
display: flex;
width: 100%;
height: 87vh;
overflow: auto;
min-width: 1500px;
background-color: #edf0ef;
.controlHide {
flex: 0.05;
background-color: #fff;
margin-right: 15px;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
}
.monitorList {
flex: 0.8;
background-color: #fff;
margin-right: 15px;
border-radius: 5px;
padding: 20px 10px;
.monitorListTitleBox {
margin-bottom: 0.677rem;
display: flex;
.monitorListTitle {
font-size: 16px;
user-select: none;
flex: 9;
}
.monitorListIcon {
flex: 1;
}
}
.displaysTypeBox {
.box(280px, 32px);
margin-bottom: 0.677rem;
display: flex;
.displaysNumber {
border: 1px solid #efefef;
font-size: 0.73rem;
flex: 1;
text-align: center;
line-height: 1.67rem;
}
.selectDisplaysType {
background-color: #05c399;
color: #fff;
}
}
.searchInputBox {
.box(280px, 32px);
margin-bottom: 0.677rem;
}
.monitorEquipmentDataBox {
height: calc(100% - 450px);
.monitorEquipmentBox {
font-size: 0.731rem;
color: rgba(78, 82, 79, 1);
line-height: 1.562rem;
padding-left: 15px;
.box(100%, 32px);
display: flex;
.monitorIPText {
flex: 9;
}
.monitorControlBox {
flex: 1;
}
}
}
.controlBox {
display: flex;
.directionBox {
flex: 1.5;
.box(150px, 150px);
.directionButton {
.textBox(50px, 50px);
background-color: #edf0ef;
cursor: pointer;
border-radius: 50%;
float: left;
border: 1px #05c399 solid;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
}
.focusingBox {
flex: 1;
.equipmentOperationBox {
.textBox(50px, 50px);
background-color: #edf0ef;
cursor: pointer;
border-radius: 50%;
float: left;
border: 1px #05c399 solid;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.rotateTimeoutBox {
.box(100px, 50px);
}
/deep/ .el-input-number__decrease {
width: 20px;
}
/deep/ .el-input-number__increase {
width: 20px;
}
/deep/ .el-input__inner {
width: 100px;
padding: 0;
}
}
}
.speedSliderBox {
}
.presuppositionBox {
height: 150px;
overflow: auto;
.presupposition {
padding: 0 15px;
display: flex;
.presuppositionText {
flex: 1;
font-size: 0.731rem;
color: rgba(78, 82, 79, 1);
line-height: 1.562rem;
}
.presuppositionIcon {
flex: 1;
.IconBox {
.box(30px, 30px);
padding: 7px;
cursor: pointer;
float: right;
}
}
}
}
}
.monitorShowBox {
flex: 4;
background-color: #fff;
border-radius: 5px;
overflow: hidden;
border: 1px #333 solid;
.monitorRevealBox {
float: left;
border: 1px solid #333;
}
.selectMonitorClass {
border: 1px solid red;
}
}
}
</style>
<style>
.searchInputBox .el-input__inner {
background: #fff;
border-radius: 0.206rem;
height: 1.67rem;
}
</style>
<style>
.el-slider__runway.disabled .el-slider__bar {
background-color: #05c399;
}
.el-slider__button {
background: #fff;
border: #05c399 2px solid;
}
.el-slider__bar {
background-color: #05c399;
}
.el-slider__runway {
background-color: #edf0ef;
}
</style>
vue子组件页面
<template>
<div class="video" ref="vcontainer" @dblclick="toggleFullscreen()">
<canvas
class="video__player"
:id="videoId"
:style="`width:${width};height:${height}`"
>您的浏览器暂不支持Canvas,请更换浏览器后再试</canvas
>
</div>
</template>
<script>
import maskBox from "../layout/maskBox"; //弹窗层
export default {
name: "canvasVideo",
components: { maskBox },
props: ["canvasData", "videoId", "width", "height"], //传入的视频连接
data() {
return {
players: null //视频播放器
};
},
mounted() {
this.start();
},
destroyed() {
this.players.destroy();
},
methods: {
//加载视频
start() {
const canvas = document.getElementById(this.videoId);
let urls = `ws://172.16.10.81:` + this.canvasData.port;
this.players = new JSMpeg.Player(urls, {
canvas: canvas,
autoplay: true
});
},
FontChart(res) {
//获取到屏幕的宽度
let clientWidth =
window.innerWidth ||
document.documentElement.clientWidth ||
document.body.clientWidth;
if (!clientWidth) return; //报错拦截:
let fontSize = 1;
if (clientWidth > 1920) fontSize = clientWidth / 1920;
return res * fontSize;
},
//全屏播放
toggleFullscreen() {
const isFullscreen = document.webkitIsFullScreen || document.fullscreen;
const canvas = document.getElementById(this.videoId);
if (isFullscreen) {
const exitFunc =
document.exitFullscreen || document.webkitExitFullscreen;
exitFunc.call(document);
canvas.style = `width:${this.width};height:${this.height}`;
window.onresize = "";
} else {
const element = this.$refs.vcontainer;
const fullscreenFunc =
element.requestFullscreen || element.webkitRequestFullScreen;
fullscreenFunc.call(element);
canvas.style = "";
this.windowOnresize();
}
},
//添加全屏监控事件
windowOnresize() {
const canvas = document.getElementById(this.videoId);
let _this = this;
window.onresize = () => {
if (!_this.checkFull()) {
canvas.style = `width:${_this.width};height:${_this.height}`;
window.onresize = "";
}
};
},
// 判断全屏
checkFull() {
//判断浏览器是否处于全屏状态 (需要考虑兼容问题)
//火狐浏览器
let isFull =
document.mozFullScreen ||
document.fullScreen ||
//谷歌浏览器及Webkit内核浏览器
document.webkitIsFullScreen ||
document.webkitRequestFullScreen ||
document.mozRequestFullScreen ||
document.msFullscreenEnabled;
if (isFull === undefined) {
isFull = false;
}
return isFull;
}
}
};
</script>
<style scoped>
.video {
position: relative;
width: 100%;
height: 100%;
}
.video__player {
width: 100%;
height: 100%;
display: flex;
}
</style>
Node页
//导入子进程模块
const child_process = require('child_process');
const exec = child_process.exec;
const cors = require('cors')
const bodyParser = require('body-parser');
const express = require('express');
const onvif = require('node-onvif');
const app = express();
// 开启socket服务
let server = app.listen(3120, () => {
console.log("服务器3120启动");
})
// socket 初始化
const io = require("socket.io")(server, { cors: true })
app.use(cors());
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
var portId = 9000//起始的端口号
var openArr = []//正在工作的摄像机组
var onLinePort = []//在线的端口
//新增摄像机
app.post('/open', function (req, res) {
var videoArr = []
req.body.forEach((item) => {
let portNum = null
for (var i = 0; i < openArr.length; i++) {
if (openArr[i].ip == item.ip) {
portNum = openArr[i].portId
break
}
}
let port = portNum ? portNum : gainPortNum(item.id, item.ip, item.rtsp)
videoArr.push({ id: item.id, port: port + 1 })
})
res.json(videoArr);
});
//切换摄像机
app.post('/modify', function (req, res) {
let portNum = null
for (var i = 0; i < openArr.length; i++) {
if (openArr[i].ip == req.body.ip) {
portNum = openArr[i].portId
break
}
}
let port = portNum ? portNum : gainPortNum(req.body.id, req.body.ip, req.body.rtsp)
res.json({ id: req.body.id, port: port + 1 });
});
//connection为自带的方法,类似生命周期里面的创建,连接后就会触发
io.on("connection", function (socket) {
console.log('一个用户与服务器建立连接', socket.handshake.query.id.toString())
socket.join(socket.handshake.query.id.toString());
// 接收到移动摄像头指令
socket.on("move", function (deviceData, speed) {
deviceMove(deviceData, speed)
})
// 接收到停止移动摄像头指令
socket.on("stop", function (deviceData) {
deviceStop(deviceData)
})
// 旋转摄像头指令
socket.on("rotatePreset", function (deviceData, speedX, timeout) {
deviceRotate(deviceData, speedX, timeout)
})
//摄像头预置点设置
socket.on("setPreset", function (deviceData, data) {
let device = new onvif.OnvifDevice({
xaddr: deviceData.xaddr,
user: deviceData.user,
pass: deviceData.pass
});
device.init().then(() => {
let ptz = device.services.ptz;
if (!ptz) {
throw new Error('当前ONVIF网络摄像机不支持云台服务');
}
let profile = device.getCurrentProfile();
let params = {
'ProfileToken': profile['token'],
'PresetToken': data.$.token,
};
device.services.ptz.setPreset(params).then((result) => {
let params2 = {
'ProfileToken': profile['token']
};
device.services.ptz.getPresets(params2).then((result) => {
socket.emit('devicePresupposition', result['data'])
}).catch((error) => {
console.error(error);
});
}).catch((error) => {
console.error(error);
});
}).catch((error) => {
console.error(error);
});
})
//摄像头删除预置点
socket.on("removePreset", function (deviceData, data) {
removeDevicePreset(deviceData, data)
})
//摄像头前往预置点
socket.on("gotoPreset", function (deviceData, data) {
gotoDevicePreset(deviceData, data)
})
// 接收到获取预设点指令
socket.on("presupposition", function (deviceData) {
let device = new onvif.OnvifDevice({
xaddr: deviceData.xaddr,
user: deviceData.user,
pass: deviceData.pass
});
device.init().then(() => {
let ptz = device.services.ptz;
if (!ptz) {
throw new Error('当前ONVIF网络摄像机不支持云台服务');
}
let profile = device.getCurrentProfile();
let params = {
'ProfileToken': profile['token']
};
device.services.ptz.getPresets(params).then((result) => {
socket.emit('devicePresupposition', result['data'])
}).catch((error) => {
console.error(error);
});
}).catch((error) => {
console.error("大错误", error);
});
})
// 当关闭连接后触发 disconnect 事件
socket.on('disconnect', function () {
console.log(socket.handshake.query.id.toString(), '与服务器断开连接');
});
})
//新增视频流
function videoAdd(id, ip, rtsp, portNumber) {
var websocket = `node websocket-relay.js supersecret ${portNumber} ${portNumber + 1}`
var ffmpeg = `ffmpeg -rtsp_transport tcp -i ${rtsp} -s 1280x720 -c copy -q 0 -map 0:0 -f mpegts -codec:v mpeg1video http://127.0.0.1:${portNumber}/supersecret`
var websocket = execute('websocket', websocket, portNumber + 1);
var ffmpeg = execute('ffmpeg', ffmpeg, portNumber)
openArr.push({ ip: ip, id: id, portId: portNumber, ffmpeg: ffmpeg })
onLinePort.push(portNumber + 1)
onLinePort = onLinePort.sort((n1, n2) => { return n1 - n2; })
}
//获取使用的端口
function gainPortNum(id, ip, rtsp) {
var port = (portId - 9000) / 2
var portNum
if (onLinePort.length != port) {
var startNum = 8999
for (var i = 0; i < onLinePort.length; i++) {
if (startNum == onLinePort[i] - 2) {
startNum = onLinePort[i]
portNum = startNum + 1
} else {
portNum = startNum + 1
break
}
}
} else {
portNum = portId
portId = portId + 2
}
videoAdd(id, ip, rtsp, portNum)
return portNum
}
/**
* 执行cmd命令
* @param {*} cmd 传入的cmd
*/
function execute(type, cmd, port) {
var last = exec(cmd);
last.stdout.on('data', function (data) {
console.log(type + port + ' : ' + data);
if (data.length == 6) {
openArr.forEach((item, index) => {
if (item.portId == (port - 1)) {
item.ffmpeg.stdin.write('q');
openArr.splice(index, 1);
}
})
}
});
last.on('exit', function (code) {
console.log(type + port + '已关闭,代码:' + code);
if (type == 'websocket') {
onLinePort.forEach((item, index) => {
if (item == port) {
onLinePort.splice(index, 1);
}
})
console.log("在线端口", onLinePort)
if (onLinePort.length == 0) {
portId = 9000
}
}
});
return last
}
//移动和缩放摄像头
function deviceMove(deviceData, speed) {
let device = new onvif.OnvifDevice({
xaddr: deviceData.xaddr,
user: deviceData.user,
pass: deviceData.pass
});
device.init().then(() => {
let ptz = device.services.ptz;
if (!ptz) {
throw new Error('当前ONVIF网络摄像机不支持云台服务');
}
return device.ptzMove({
'speed': {
x: speed.x, // Speed of pan (in the range of -1.0 to 1.0)
y: speed.y, // Speed of tilt (in the range of -1.0 to 1.0)
z: speed.z // Speed of zoom (in the range of -1.0 to 1.0)
},
'timeout': 1 // seconds
});
}).catch((error) => {
console.error(error);
});
}
//停止摄像头移动
function deviceStop(deviceData) {
let device = new onvif.OnvifDevice({
xaddr: deviceData.xaddr,
user: deviceData.user,
pass: deviceData.pass
});
device.init().then(() => {
let ptz = device.services.ptz;
if (!ptz) {
throw new Error('当前ONVIF网络摄像机不支持云台服务');
}
device.ptzStop().catch((error) => {
console.error(error);
});
}).catch((error) => {
console.error(error);
});
}
//摄像头旋转
function deviceRotate(deviceData, speedX, timeout) {
let device = new onvif.OnvifDevice({
xaddr: deviceData.xaddr,
user: deviceData.user,
pass: deviceData.pass
});
device.init().then(() => {
let ptz = device.services.ptz;
if (!ptz) {
throw new Error('当前ONVIF网络摄像机不支持云台服务');
}
return device.ptzMove({
'speed': { x: speedX, y: 0, z: 0 },
'timeout': timeout
});
}).catch((error) => {
console.error(error);
});
}
//删除预置点
function removeDevicePreset(deviceData, data) {
let device = new onvif.OnvifDevice({
xaddr: deviceData.xaddr,
user: deviceData.user,
pass: deviceData.pass
});
device.init().then(() => {
let ptz = device.services.ptz;
if (!ptz) {
throw new Error('当前ONVIF网络摄像机不支持云台服务');
}
let profile = device.getCurrentProfile();
let params = {
'ProfileToken': profile['token'],
'PresetToken': data.$.token,
};
device.services.ptz.removePreset(params).catch((error) => {
console.error(error);
});
}).catch((error) => {
console.error(error);
});
}
//前往预置点
function gotoDevicePreset(deviceData, data) {
let device = new onvif.OnvifDevice({
xaddr: deviceData.xaddr,
user: deviceData.user,
pass: deviceData.pass
});
device.init().then(() => {
let ptz = device.services.ptz;
if (!ptz) {
throw new Error('当前ONVIF网络摄像机不支持云台服务');
}
let profile = device.getCurrentProfile();
let params = {
'ProfileToken': profile['token'],
'PresetToken': data.$.token,
'Speed': { 'x': 1, 'y': 1, 'z': 1 }
};
device.services.ptz.gotoPreset(params).catch((error) => {
console.error(error);
});
}).catch((error) => {
console.error(error);
});
}
websocket-relay页
// Use the websocket-relay to serve a raw MPEG-TS over WebSockets. You can use
// ffmpeg to feed the relay. ffmpeg -> websocket-relay -> browser
// Example:
// node websocket-relay yoursecret 9081 9082
// ffmpeg -i <some input> -f mpegts http://localhost:8081/yoursecret
var fs = require('fs'),
http = require('http'),
WebSocket = require('ws');
//判断输入格式是否正确
if (process.argv.length < 3) {
console.log(
'输入格式: \n' +
'node websocket-relay.js <secret> [<stream-port> <websocket-port>]'
);
process.exit();
}
var STREAM_SECRET = process.argv[2],//密码
STREAM_PORT = process.argv[3] || 8081,//输入地址
WEBSOCKET_PORT = process.argv[4] || 8082,//输出地址
RECORD_STREAM = false;//是否录像
// Websocket Server
var socketServer = new WebSocket.Server({ port: WEBSOCKET_PORT, perMessageDeflate: false });
socketServer.connectionCount = 0;
var timer = null
socketServer.on('connection', function (socket, upgradeReq) {
//一个新的socketServer加入
socketServer.connectionCount++;
if (timer != null) {
clearInterval(timer);
}
console.log(
'接入一个新WebSocket',
// (upgradeReq || socket.upgradeReq).socket.remoteAddress,
// (upgradeReq || socket.upgradeReq).headers['user-agent'],
'现连接数:' + socketServer.connectionCount
);
//一个socketServer链接断开
socket.on('close', function (code, message) {
socketServer.connectionCount--;
console.log(
'一个WebSocket断开 现连接数:' + socketServer.connectionCount
);
if (socketServer.connectionCount == 0) {
timer = setInterval(() =>
socketClose(), 3000);
}
});
});
//是否关闭socket
function socketClose() {
if (socketServer.connectionCount == 0) {
console.log('关闭流传输')
}
}
//socketServer广播
socketServer.broadcast = function (data) {
socketServer.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
};
//HTTP服务器接受来自ffmpeg的输入MPEG-TS流
var streamServer = http.createServer(function (request, response) {
var params = request.url.substr(1).split('/');
if (params[0] !== STREAM_SECRET) {//判断密码是否正确
console.log(
'流连接失败: ' + request.socket.remoteAddress + ':' +
request.socket.remotePort + ' - 密码错误'
);
response.end();
}
//连接流成功
response.connection.setTimeout(0);
console.log(
'传输的流: ' +
request.socket.remoteAddress + ':' +
request.socket.remotePort
);
//传输流数据
request.on('data', function (data) {
socketServer.broadcast(data);
if (request.socket.recording) {
request.socket.recording.write(data);
}
});
//传输流结束
request.on('end', function () {
console.log('传输流关闭');
if (request.socket.recording) {
request.socket.recording.close();
}
process.exit();
});
//将流记录到本地文件
if (RECORD_STREAM) {
var path = 'recordings/' + Date.now() + '.ts';
request.socket.recording = fs.createWriteStream(path);
}
})
//保持套接字打开以进行流式处理
streamServer.headersTimeout = 0;//不等待请求头
streamServer.listen(STREAM_PORT);//创建输出流服务器
// console.log('监听MPEG-TS流 http://127.0.0.1:' + STREAM_PORT + '/<secret>');
// console.log('正在等待上的WebSocket连接 ws://127.0.0.1:' + WEBSOCKET_PORT + '/');
网友评论