Make streaming work & much more elegant
This commit is contained in:
parent
76c8876bd8
commit
507d4232f0
|
@ -0,0 +1 @@
|
||||||
|
node_modules/
|
|
@ -10,7 +10,6 @@ router.get('/', getById);
|
||||||
router.put('/', update);
|
router.put('/', update);
|
||||||
router.delete('/', _delete);
|
router.delete('/', _delete);
|
||||||
router.post('/osd', _osd);
|
router.post('/osd', _osd);
|
||||||
router.post('/stream', _stream);
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
||||||
|
@ -49,10 +48,3 @@ function _osd(req, res, next) {
|
||||||
.then((socket) => res.json(socket))
|
.then((socket) => res.json(socket))
|
||||||
.catch(err => next(err));
|
.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 */
|
/*jshint esversion: 6 */
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const Stream = require('../stream/videoStream');
|
|
||||||
const db = require(path.resolve(__dirname, '../_helpers/db.js'));
|
const db = require(path.resolve(__dirname, '../_helpers/db.js'));
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const cameraHelper = require(path.resolve(__dirname, '../_helpers/cameraHelper.js'));
|
const cameraHelper = require(path.resolve(__dirname, '../_helpers/cameraHelper.js'));
|
||||||
|
@ -13,8 +12,7 @@ module.exports = {
|
||||||
getById: _getById,
|
getById: _getById,
|
||||||
update: _update,
|
update: _update,
|
||||||
delete: _delete,
|
delete: _delete,
|
||||||
osd: _osd,
|
osd: _osd
|
||||||
stream: _stream
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function _getAll() {
|
async function _getAll() {
|
||||||
|
@ -101,27 +99,3 @@ async function _osd({id, option}) {
|
||||||
}
|
}
|
||||||
return await socket.sendCmd(id, osdHex);
|
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"
|
"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;
|
|
|
@ -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",
|
"mongoose": "^5.2.1",
|
||||||
"nodemon": "^1.17.5",
|
"nodemon": "^1.17.5",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
|
"rtsp-relay": "^1.6.1",
|
||||||
"tcp-port-used": "^1.0.0",
|
"tcp-port-used": "^1.0.0",
|
||||||
"ws": "1.1.1"
|
"ws": "1.1.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,7 @@ ul.nav {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
#streamStage {
|
#streamStage {
|
||||||
max-height: 100%;
|
/* max-height: 100%; */
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
#msgStage {
|
#msgStage {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -8,13 +8,5 @@ function changeStream() {
|
||||||
|
|
||||||
function startStream() {
|
function startStream() {
|
||||||
const canvas = document.getElementById('streamStage');
|
const canvas = document.getElementById('streamStage');
|
||||||
const payload = {
|
player = new JSMpeg.Player('ws://' + document.location.host + '/stream', {canvas:canvas});
|
||||||
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});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
|
const expressWs = require('express-ws')(app);
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const errorhandler = require(path.resolve( __dirname, 'app/_helpers/error-handler.js'));
|
const errorhandler = require(path.resolve( __dirname, 'app/_helpers/error-handler.js'));
|
||||||
|
@ -14,19 +15,20 @@ app.use(cors());
|
||||||
|
|
||||||
app.use(express.static(__dirname + '/pub'));
|
app.use(express.static(__dirname + '/pub'));
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.sendFile(path.resolve(__dirname + '/pub/index.html'));
|
res.sendFile(path.resolve(__dirname + '/pub/index.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use('/camera', require(path.resolve( __dirname, 'app/camera/camera.controller.js')));
|
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('/ptz', require(path.resolve( __dirname, 'app/ptz/ptz.controller.js')));
|
||||||
app.use('/image', require(path.resolve( __dirname, 'app/image/image.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){
|
app.get('*', function(req, res){
|
||||||
res.send('what???', 404);
|
res.status(404).send('404 not found');
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(errorhandler);
|
app.use(errorhandler);
|
||||||
|
|
||||||
const port = process.env.NODE_ENV === 'production' ? 80 : 4000;
|
const port = process.env.NODE_ENV === 'production' ? 80 : 4000;
|
||||||
const server = app.listen(port, function() {
|
const server = app.listen(port, function() {
|
||||||
console.log('Server listening on port ' + port);
|
console.log('Server listening on port ' + port);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue