Make streaming work & much more elegant
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| node_modules/ | ||||
| @@ -10,7 +10,6 @@ router.get('/', getById); | ||||
| router.put('/', update); | ||||
| router.delete('/', _delete); | ||||
| router.post('/osd', _osd); | ||||
| router.post('/stream', _stream); | ||||
|  | ||||
| module.exports = router; | ||||
|  | ||||
| @@ -49,10 +48,3 @@ function _osd(req, res, next) { | ||||
|         .then((socket) => res.json(socket)) | ||||
|         .catch(err => next(err)); | ||||
| } | ||||
|  | ||||
| function _stream(req, res, next) { | ||||
|     console.log("Stream Requester ip: " + req.ip); | ||||
|     cameraService.stream(req.body) | ||||
|         .then((streamPort) => res.json(streamPort)) | ||||
|         .catch(err => next(err)); | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| /*jshint esversion: 6 */ | ||||
| const path = require('path'); | ||||
| const Stream = require('../stream/videoStream'); | ||||
| const db = require(path.resolve(__dirname, '../_helpers/db.js')); | ||||
| const mongoose = require('mongoose'); | ||||
| const cameraHelper = require(path.resolve(__dirname, '../_helpers/cameraHelper.js')); | ||||
| @@ -13,8 +12,7 @@ module.exports = { | ||||
|     getById: _getById, | ||||
|     update: _update, | ||||
|     delete: _delete, | ||||
|     osd: _osd, | ||||
|     stream: _stream | ||||
|     osd: _osd | ||||
| }; | ||||
|  | ||||
| async function _getAll() { | ||||
| @@ -101,27 +99,3 @@ async function _osd({id, option}) { | ||||
|     } | ||||
|     return await socket.sendCmd(id, osdHex); | ||||
| } | ||||
|  | ||||
| async function _stream({id, width = 1920, height = 1080}) { | ||||
|     const camera = await Camera.findById(id).lean(); | ||||
|  | ||||
|     // In case a seperate user wants to reach same camera stream | ||||
|     let inUse; | ||||
|     try { | ||||
|         inUse = await cameraHelper.checkPortUse(camera.streamPort); | ||||
|     } catch(err) { | ||||
|         throw err; | ||||
|     } | ||||
|  | ||||
|     if (!inUse) { | ||||
|         const stream = new Stream({ | ||||
|             name: camera.name || "stream: " + camera.rtsp, | ||||
|             url: 'rtsp://' + camera.rtsp, | ||||
|             port: camera.streamPort, | ||||
|             width: width, | ||||
|             height: height | ||||
|         }); | ||||
|         stream.start(); | ||||
|     } | ||||
|     return camera.streamPort; | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| { | ||||
|     "connectionString": "mongodb://your-mongo-address/db-name", | ||||
|     "connectionString": "mongodb://localhost:27017/ptzoptics", | ||||
|     "secret": "your db-secret" | ||||
| } | ||||
|   | ||||
| @@ -1,38 +0,0 @@ | ||||
| /*jshint esversion: 6 */ | ||||
| const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path; | ||||
| const child_process = require('child_process'); | ||||
| const EventEmitter = require('events'); | ||||
| const spawn = require('cross-spawn'); | ||||
|  | ||||
| class Mpeg1Muxer extends EventEmitter { | ||||
|  | ||||
|     constructor(options) { | ||||
|         super(options); | ||||
|  | ||||
|         this.url = options.url; | ||||
|         this.width = options.width; | ||||
|         // this.stream = child_process.spawn(ffmpegPath, ['-y', '-loglevel', 'quiet', "-rtsp_transport", "tcp", "-i", this.url, '-vf', 'yadif', '-f', 'mpegts', '-r', '30',  '-codec:v', 'mpeg1video', '-codec:a', 'mp2', '-b:a', '128k', '-b:v', '4096k', '-muxdelay', '0', '-', './app/stream/stream.ts'], { | ||||
|         //     detached: false | ||||
|         // }); | ||||
|         this.stream = child_process.spawn(ffmpegPath, ['-y', '-loglevel', 'quiet', "-rtsp_transport", "tcp", "-i", this.url, '-filter:v', 'scale=1280:-1', '-f', 'mpegts', '-r', '30',  '-codec:v', 'mpeg1video', '-codec:a', 'mp2', '-b:a', '128k', '-b:v', '1500k', '-', './app/stream/stream.ts'], { | ||||
|             detached: false | ||||
|         }); | ||||
|  | ||||
|         this.inputStreamStarted = true; | ||||
|         this.stream.stdout.on('data', (data) => { return this.emit('mpeg1data', data); }); | ||||
|         this.stream.stderr.on('data', (data) => { return this.emit('ffmpegError', data); }); | ||||
|     } | ||||
|  | ||||
|     stop() { | ||||
|         try { | ||||
|             this.stream.stdout.removeAllListeners(); | ||||
|         } catch(err) { | ||||
|             console.log("Muxer: " + err); | ||||
|         } | ||||
|  | ||||
|         this.stream.kill(); | ||||
|         this.stream = undefined; | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = Mpeg1Muxer; | ||||
							
								
								
									
										21
									
								
								app/stream/stream.controller.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/stream/stream.controller.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| const path = require('path'); | ||||
| const express = require('express'); | ||||
| const router = express.Router(); | ||||
|  | ||||
| const db = require(path.resolve(__dirname, '../_helpers/db.js')); | ||||
| const Camera = db.Camera; | ||||
|  | ||||
| const { proxy, scriptUrl } = require('rtsp-relay')(router); | ||||
|  | ||||
| console.log(`Connecting to camera: rtsp://${Camera.rtsp}`); | ||||
|  | ||||
| const handler = proxy({ | ||||
|     url: `rtsp://10.0.1.3:554/1`, | ||||
|     // if your RTSP stream need credentials, include them in the URL as above | ||||
|     verbose: false, | ||||
|     additionalFlags: ['-q', '1'] | ||||
|   }); | ||||
|  | ||||
| router.ws('/', handler); | ||||
|  | ||||
| module.exports = router; | ||||
| @@ -1,124 +0,0 @@ | ||||
| /*jshint esversion: 6 */ | ||||
|  | ||||
| // Thanks to:  https://github.com/Wifsimster/node-rtsp-stream-es6 | ||||
| const WebSocket = require('ws'); | ||||
| const EventEmitter = require('events'); | ||||
| const STREAM_MAGIC_BYTES = "jsmp"; | ||||
| const Mpeg1Muxer = require('./mpeg1muxer'); | ||||
|  | ||||
| class VideoStream extends EventEmitter { | ||||
|  | ||||
|     constructor(options) { | ||||
|         super(options); | ||||
|         this.name = options.name; | ||||
|         this.url = options.url; | ||||
|         this.width = options.width; | ||||
|         this.height = options.height; | ||||
|         this.port = options.port; | ||||
|         this.stream = void 0; | ||||
|         this.stream2Socket(); | ||||
|     } | ||||
|  | ||||
|     stream2Socket() { | ||||
|         this.server = new WebSocket.Server({ | ||||
|             port: this.port | ||||
|         }); | ||||
|         this.server.on('connection', (socket) => { | ||||
|             console.log(`New connection: ${this.name}`); | ||||
|             let streamHeader = new Buffer(8); | ||||
|             streamHeader.write(STREAM_MAGIC_BYTES); | ||||
|             streamHeader.writeUInt16BE(this.width, 4); | ||||
|             streamHeader.writeUInt16BE(this.height, 6); | ||||
|             socket.send(streamHeader); | ||||
|  | ||||
|             socket.on('close', () => { | ||||
|                 console.log(`${this.name} disconnected !`); | ||||
|                 if (this.server.clients.length <= 0) { | ||||
|                     this.mpeg1Muxer.stop(); | ||||
|                     this.server.close(); | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         this.on('camdata', (data) => { | ||||
|             for (let i in this.server.clients) { | ||||
|                 let client = this.server.clients[i]; | ||||
|                 if (client.readyState === WebSocket.OPEN) { | ||||
|                     client.send(data); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     onSocketConnect(socket) { | ||||
|         let streamHeader = new Buffer(8); | ||||
|         streamHeader.write(STREAM_MAGIC_BYTES); | ||||
|         streamHeader.writeUInt16BE(this.width, 4); | ||||
|         streamHeader.writeUInt16BE(this.height, 6); | ||||
|         socket.send(streamHeader, { | ||||
|             binary: true | ||||
|         }); | ||||
|         console.log(`New connection: ${this.name} - ${this.wsServer.clients.length} total`); | ||||
|         return socket.on("close", function(code, message) { | ||||
|             return console.log(`${this.name} disconnected - ${this.wsServer.clients.length} total`); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     start() { | ||||
|         this.mpeg1Muxer = new Mpeg1Muxer({ | ||||
|             url: this.url, | ||||
|             width: this.width | ||||
|         }); | ||||
|         this.mpeg1Muxer.on('mpeg1data', (data) => { | ||||
|             return this.emit('camdata', data); | ||||
|         }); | ||||
|  | ||||
|         let gettingInputData = false; | ||||
|         let gettingOutputData = false; | ||||
|         let inputData = []; | ||||
|         let outputData = []; | ||||
|  | ||||
|         this.mpeg1Muxer.on('ffmpegError', (data) => { | ||||
|             data = data.toString(); | ||||
|             if (data.indexOf('Input #') !== -1) { | ||||
|                 gettingInputData = true; | ||||
|             } | ||||
|             if (data.indexOf('Output #') !== -1) { | ||||
|                 gettingInputData = false; | ||||
|                 gettingOutputData = true; | ||||
|             } | ||||
|             if (data.indexOf('frame') === 0) { | ||||
|                 gettingOutputData = false; | ||||
|             } | ||||
|             if (gettingInputData) { | ||||
|                 inputData.push(data.toString()); | ||||
|                 let size = data.match(/\d+x\d+/); | ||||
|                 if (size != null) { | ||||
|                     size = size[0].split('x'); | ||||
|                     if (this.width == null) { | ||||
|                         this.width = parseInt(size[0], 10); | ||||
|                     } | ||||
|                     if (this.height == null) { | ||||
|                         return this.height = parseInt(size[1], 10); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         this.mpeg1Muxer.on('ffmpegError', (data) => { | ||||
|             return global.process.stderr.write(data); | ||||
|         }); | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     stop(serverCloseCallback) { | ||||
|         this.server.close(serverCloseCallback); | ||||
|         this.server.removeAllListeners(); | ||||
|         this.server = undefined; | ||||
|  | ||||
|         this.mpeg1Muxer.stop(); | ||||
|         this.mpeg1Muxer.removeAllListeners(); | ||||
|         this.mpeg1Muxer = undefined; | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = VideoStream; | ||||
| @@ -21,6 +21,7 @@ | ||||
|     "mongoose": "^5.2.1", | ||||
|     "nodemon": "^1.17.5", | ||||
|     "path": "^0.12.7", | ||||
|     "rtsp-relay": "^1.6.1", | ||||
|     "tcp-port-used": "^1.0.0", | ||||
|     "ws": "1.1.1" | ||||
|   } | ||||
|   | ||||
| @@ -120,7 +120,7 @@ ul.nav { | ||||
|     margin-left: auto; | ||||
| } | ||||
| #streamStage { | ||||
|     max-height: 100%; | ||||
|     /* max-height: 100%; */ | ||||
|     right: 0; | ||||
| } | ||||
| #msgStage { | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| <!DOCTYPE html> | ||||
|  | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|   | ||||
							
								
								
									
										4
									
								
								pub/js/jsmpeg.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								pub/js/jsmpeg.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -8,13 +8,5 @@ function changeStream() { | ||||
|  | ||||
| function startStream() { | ||||
|     const canvas = document.getElementById('streamStage'); | ||||
|     const payload = { | ||||
|         width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, | ||||
|         height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight | ||||
|     }; | ||||
|  | ||||
|     sendCmd('/camera/stream', 'POST', payload).then((camera) => { | ||||
|         const streamPort = JSON.parse(camera); | ||||
|         player = new JSMpeg.Player('ws://' + document.location.hostname + ':' + streamPort, {canvas:canvas}); | ||||
|     }); | ||||
|     player = new JSMpeg.Player('ws://' + document.location.host + '/stream', {canvas:canvas}); | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| const path = require('path'); | ||||
| const express = require('express'); | ||||
| const app = express(); | ||||
| const expressWs = require('express-ws')(app); | ||||
| const cors = require('cors'); | ||||
| const bodyParser = require('body-parser'); | ||||
| const errorhandler = require(path.resolve( __dirname, 'app/_helpers/error-handler.js')); | ||||
| @@ -20,8 +21,9 @@ app.get('/', (req, res) => { | ||||
| app.use('/camera', require(path.resolve( __dirname, 'app/camera/camera.controller.js'))); | ||||
| app.use('/ptz', require(path.resolve( __dirname, 'app/ptz/ptz.controller.js'))); | ||||
| app.use('/image', require(path.resolve( __dirname, 'app/image/image.controller.js'))); | ||||
| app.use('/stream', require(path.resolve( __dirname, 'app/stream/stream.controller.js'))); | ||||
| app.get('*', function(req, res){ | ||||
|   res.send('what???', 404); | ||||
|   res.status(404).send('404 not found'); | ||||
| }); | ||||
|  | ||||
| app.use(errorhandler); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user