nodejs vue rtsp流网页播放方案

您所在的位置:网站首页 web页面播放rtsp nodejs vue rtsp流网页播放方案

nodejs vue rtsp流网页播放方案

2023-03-13 12:11| 来源: 网络整理| 查看: 265

支持摄像头多开,点击全屏播放,窗口拖拽,操控摄像头上下左右旋转放大缩小等操作 1安装

Ffmpeg,用来解码视频,下载完后需添加环境变量 https://ffmpeg.org/releases/ffmpeg-4.0.1.tar.bz2 Node.js,搭建webSocket服务器,下载完后需添加环境变量 https://nodejs.org/dist/v8.11.3/node-v8.11.3-x64.msi jsmpeg,运行主程序 https://codeload.github.com/phoboslab/jsmpeg/zip/master

2使用

2-1.运行jsmpeg

运行jsmpeg内部的websocket-relay.js

在运行websocket-relay.js之前node需要安装webSocket模块 在cmd控制台输入:

npm install ws -g

jsmpeg所在路径,执行:

node websocket-relay.js supersecret 8081 8082

Supersecret是密码

8081是ffmpeg推送端口

8082是前端webSocket端口

2-2.运行ffmpeg

ffmpeg -rtsp_transport tcp -i rtsp://admin:[email protected]:554/h264/ch1/sub/av_stream -c copy -q 0 -map 0:0 -f mpegts -codec:v mpeg1video http://127.0.0.1:9991/supersecret

关键点:

-rtsp_transport tcp:使用tcp强解码rtsp流,防止防火墙之类的问题造成推流中断 -c copy 操作rtsp流,直接复制推流,不写会报找不到rtsp解码器的错(因为ffmpeg不知道用什么处理rtsp) -map 0:0:-map指定哪些流做为输入, 0:0 表示第0个输入文件的第0个流(解决10秒延迟问题) -f mpegts -codec:v mpeg1video:编码方式(必须这样写jsmpeg才能识别)

3:html

通过使用node-onvif操作onvif协议的摄像头 https://github.com/futomi/node-onvif $ npm install -s node-onvif

4:实战项目请看下一篇

技术需求请看上一篇文章,这篇使用vue实现视频监控(可直接复制代码运行) 支持摄像头多开,点击全屏播放,窗口拖拽,操控摄像头上下左右旋转放大缩小等操作 vue父组件页面

当前摄像头 {{ item }} {{ item.ip }} --摄像头{{ playMonitor(item.id) }} {{ item }} {{ item }} {{ item.Name }} 请添加摄像头{{ item }} 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` }; } } } }; .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; } } } .searchInputBox .el-input__inner { background: #fff; border-radius: 0.206rem; height: 1.67rem; } .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; }

vue子组件页面

您的浏览器暂不支持Canvas,请更换浏览器后再试 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; } } }; .video { position: relative; width: 100%; height: 100%; } .video__player { width: 100%; height: 100%; display: flex; }

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 -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 [ ]' ); 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 + '/'); // console.log('正在等待上的WebSocket连接 ws://127.0.0.1:' + WEBSOCKET_PORT + '/');


【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3