├── index.js
├── test
├── client.js
├── server.js
└── index.js
├── src
├── configs.js
├── mpeg1muxer.js
└── videoStream.js
├── .npmignore
├── package.json
└── README.md
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./src/videoStream')
--------------------------------------------------------------------------------
/test/client.js:
--------------------------------------------------------------------------------
1 | const WebSocket = require('ws')
2 | const ws = new WebSocket('ws://localhost:5000')
3 |
4 | ws.on('open', () => {
5 | console.log('Connected to stream')
6 | })
7 |
8 | ws.on('message', (data, flags) => {
9 | console.log(data)
10 | })
--------------------------------------------------------------------------------
/test/server.js:
--------------------------------------------------------------------------------
1 | const Stream = require('../src/videoStream')
2 |
3 | const options = {
4 | name: 'streamName',
5 | url: 'rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov',
6 | wsPort: 5000
7 | }
8 |
9 | stream = new Stream(options)
10 |
11 | stream.start()
--------------------------------------------------------------------------------
/src/configs.js:
--------------------------------------------------------------------------------
1 | const genFfmpegFormatConfigs = (url, port, width, height) =>
2 | ["-rtsp_transport", "tcp", "-i", url, '-f', 'mpegts', '-codec:v', 'mpeg1video', '-s', `${width}x${height}`, '-b:v', '800k', '-r', '30', '-muxdelay', '0.4', `http://localhost:${port}/s1`];
3 |
4 | module.exports = {genFfmpegFormatConfigs}
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | const app = require('express')()
2 | const http = require('http').Server(app)
3 | const ioc = require('socket.io-client')
4 | const port = 5000
5 | const socket = ioc('http://localhost:' + port)
6 |
7 | app.get('/', (req, res) => {
8 | res.send('
Hello world
')
9 | })
10 |
11 | http.listen(80, () => {
12 | console.log('Listening on *:80')
13 | })
14 |
15 | socket.on('connect', () => { console.log('Connected !') })
16 |
17 | socket.on('stream', (data) => { console.log(data) })
--------------------------------------------------------------------------------
/src/mpeg1muxer.js:
--------------------------------------------------------------------------------
1 | const {genFfmpegFormatConfigs} = require('./configs');
2 | const child_process = require('child_process');
3 | const EventEmitter = require('events');
4 |
5 | class Mpeg1Muxer extends EventEmitter {
6 |
7 | constructor(options) {
8 | super(options)
9 |
10 | this.url = options.url;
11 | this.port = options.port;
12 | this.ffmpegPath = options.ffmpegPath;
13 | this.width = options.width;
14 | this.height = options.height;
15 |
16 | this.stream = child_process.spawn(this.ffmpegPath, genFfmpegFormatConfigs(this.url, this.port, this.width, this.height), {
17 | detached: false
18 | });
19 |
20 | this.inputStreamStarted = true
21 | this.stream.stdout.on('data', (data) => { return this.emit('mpeg1data', data) })
22 | this.stream.stderr.on('data', (data) => { return this.emit('ffmpegError', data) })
23 | }
24 | }
25 |
26 | module.exports = Mpeg1Muxer
27 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # Windows shortcuts
18 | *.lnk
19 |
20 | node_modules/
21 | dist/
22 |
23 | # =========================
24 | # Operating System Files
25 | # =========================
26 |
27 | # OSX
28 | # =========================
29 |
30 | .DS_Store
31 | .AppleDouble
32 | .LSOverride
33 |
34 | # Thumbnails
35 | ._*
36 |
37 | # Files that might appear in the root of a volume
38 | .DocumentRevisions-V100
39 | .fseventsd
40 | .Spotlight-V100
41 | .TemporaryItems
42 | .Trashes
43 | .VolumeIcon.icns
44 |
45 | # Directories potentially created on remote AFP share
46 | .AppleDB
47 | .AppleDesktop
48 | Network Trash Folder
49 | Temporary Items
50 | .apdisk
51 | database.sqlite
52 | src/back/db/database.sqlite
53 | npm-debug.log.1732676291
54 | *.sqlite
55 | src/back/db/database.sqlite-journal
56 | *.sqlite
57 | *.sqlite
58 | *.sqlite
59 | *.sqlite
60 | src/back/db/database.sqlite
61 | src/back/db/database.sqlite
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "_id": "node-rtsp-stream-jsmpeg@0.0.2",
3 | "_inBundle": false,
4 | "_location": "/node-rtsp-stream-jsmpeg",
5 | "_requested": {
6 | "type": "version",
7 | "registry": true,
8 | "raw": "node-rtsp-stream-jsmpeg@0.0.2",
9 | "name": "node-rtsp-stream-jsmpeg",
10 | "escapedName": "node-rtsp-stream-jsmpeg",
11 | "rawSpec": "1.0",
12 | "saveSpec": null,
13 | "fetchSpec": "0.0.2"
14 | },
15 | "author": {
16 | "name": "Zveroslav"
17 | },
18 | "bugs": {
19 | "url": "https://github.com/Zveroslav/node-rtsp-stream-jsmpeg/issues"
20 | },
21 | "dependencies": {
22 | "jsmpeg": "^1.0.0",
23 | "ws": "^1.1.1",
24 | "http-shutdown": "^1.2"
25 | },
26 | "description": "RTSP stream through WebSocket for jsmpeg decoder",
27 | "homepage": "",
28 | "licenses": [
29 | {
30 | "type": "MIT",
31 | "url": "https://github.com/brentertz/scapegoat/blob/master/LICENSE-MIT"
32 | }
33 | ],
34 | "main": "index.js",
35 | "name": "node-rtsp-stream-jsmpeg",
36 | "repository": {
37 | "type": "git",
38 | "url": "https://github.com/Zveroslav/node-rtsp-stream-jsmpeg.git"
39 | },
40 | "version": "0.0.2"
41 | }
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # node-rtsp-stream-jsmpeg
2 |
3 | First of all, it's a based on [**node-rtsp-stream-es6**](https://github.com/Wifsimster/node-rtsp-stream-es6) and [**node-rtsp-stream**]
4 |
5 | ## Differences with the original modules
6 |
7 | - Code based on official documentation of https://github.com/phoboslab/jsmpeg for server side decoding video
8 |
9 | ## Description
10 |
11 | Stream any RTSP stream and output to [WebSocket](https://github.com/websockets/ws) for consumption by [jsmpeg](https://github.com/phoboslab/jsmpeg).
12 | HTML5 streaming video!
13 |
14 | ## Requirements
15 |
16 | You need to download and install [FFMPEG](https://ffmpeg.org/download.html) in server-side.
17 |
18 | ##Installation
19 |
20 | ```
21 | npm i node-rtsp-stream-jsmpeg
22 | ```
23 |
24 | ## Server
25 |
26 | ```
27 | const Stream = require('node-rtsp-stream-jsmpeg')
28 |
29 | const options = {
30 | name: 'streamName',
31 | url: 'rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov',
32 | wsPort: 3333
33 | }
34 |
35 | stream = new Stream(options)
36 | stream.start()
37 | ```
38 |
39 |
40 | ## Client
41 |
42 | ```
43 |
44 |
45 |
46 |
47 | DEMO node-rtsp-stream-jsmpeg
48 |
49 |
50 |
51 |
52 |
54 |
55 |
56 |
61 |
62 | ```
63 |
64 | You can find a live stream JSMPEG example here : https://github.com/phoboslab/jsmpeg/blob/master/stream-example.html
65 |
--------------------------------------------------------------------------------
/src/videoStream.js:
--------------------------------------------------------------------------------
1 | const WebSocket = require('ws');
2 | const EventEmitter = require('events');
3 | const http = require('http');
4 | const STREAM_MAGIC_BYTES = "jsmp";
5 | const Mpeg1Muxer = require('./mpeg1muxer');
6 |
7 | class VideoStream extends EventEmitter {
8 |
9 | constructor(options) {
10 | super(options)
11 | this.name = options.name
12 | this.url = options.url
13 | this.width = options.width
14 | this.height = options.height
15 | this.wsPort = options.wsPort
16 | this.port = options.port || this.getPortForMiddlewareServer(options.wsPort)
17 | this.stream = 0
18 | this.httpServer = null
19 | this.server = null
20 | this.ffmpegPath = options.ffmpegPath || "ffmpeg"
21 |
22 | this.stream2Socket()
23 | }
24 |
25 | stream2Socket() {
26 | var socketServer = new WebSocket.Server({port: this.wsPort, perMessageDeflate: false});
27 |
28 | socketServer.connectionCount = 0;
29 | socketServer.on('connection', function(socket, upgradeReq) {
30 | socketServer.connectionCount++;
31 | console.log(
32 | 'New WebSocket Connection: ',
33 | (upgradeReq || socket.upgradeReq).socket.remoteAddress,
34 | (upgradeReq || socket.upgradeReq).headers['user-agent'],
35 | '('+socketServer.connectionCount+' total)'
36 | );
37 | socket.on('close', function(code, message){
38 | socketServer.connectionCount--;
39 | console.log(
40 | 'Disconnected WebSocket ('+socketServer.connectionCount+' total)'
41 | );
42 | });
43 | });
44 | socketServer.broadcast = function(data) {
45 | socketServer.clients.forEach(function each(client) {
46 | if (client.readyState === WebSocket.OPEN) {
47 | client.send(data);
48 | }
49 | });
50 | };
51 |
52 |
53 | // HTTP Server to accept incomming MPEG-TS Stream from ffmpeg
54 | var streamServer = http.createServer( function(request, response) {
55 | var params = request.url.substring(1).split('/');
56 |
57 | if (params[0] !== 's1') {
58 | console.log(
59 | 'Failed Stream Connection: '+ request.socket.remoteAddress + ':' +
60 | request.socket.remotePort + ' - wrong secret.'
61 | );
62 | response.end();
63 | }
64 | response.socket.setTimeout(0);
65 | console.log(
66 | 'Stream Connected: ' +
67 | request.socket.remoteAddress + ':' +
68 | request.socket.remotePort
69 | );
70 |
71 | request.on('data', function(data){
72 | socketServer.broadcast(data);
73 | if (request.socket.recording) {
74 | request.socket.recording.write(data);
75 | }
76 | });
77 | request.on('end',function(){
78 | console.log('close');
79 | if (request.socket.recording) {
80 | request.socket.recording.close();
81 | }
82 | });
83 |
84 | });
85 |
86 | streamServer = require('http-shutdown')(streamServer);
87 | streamServer.listen(this.port);
88 |
89 | this.server = socketServer;
90 | this.httpServer = streamServer;
91 | }
92 |
93 | onSocketConnect(socket) {
94 | let streamHeader = Buffer.alloc(8)
95 | streamHeader.write(STREAM_MAGIC_BYTES)
96 | streamHeader.writeUInt16BE(this.width, 4)
97 | streamHeader.writeUInt16BE(this.height, 6)
98 | socket.send(streamHeader, { binary: true })
99 | console.log(`New connection: ${this.name} - ${this.wsServer.clients.length} total`)
100 | return socket.on("close", function(code, message) {
101 | return console.log(`${this.name} disconnected - ${this.wsServer.clients.length} total`)
102 | })
103 | }
104 |
105 | start() {
106 | this.mpeg1Muxer = new Mpeg1Muxer({ url: this.url, port: this.port, ffmpegPath: this.ffmpegPath, width: this.width, height: this.height })
107 | this.mpeg1Muxer.on('mpeg1data', (data) => { return this.emit('camdata', data) })
108 |
109 | let gettingInputData = false
110 | let gettingOutputData = false
111 | let inputData = []
112 | let outputData = []
113 |
114 | this.mpeg1Muxer.on('ffmpegError', (data) => {
115 | data = data.toString()
116 | if (data.indexOf('Input #') !== -1) { gettingInputData = true }
117 | if (data.indexOf('Output #') !== -1) {
118 | gettingInputData = false
119 | gettingOutputData = true
120 | }
121 | if (data.indexOf('frame') === 0) { gettingOutputData = false }
122 | if (gettingInputData) {
123 | inputData.push(data.toString())
124 | let size = data.match(/\d+x\d+/)
125 | if (size != null) {
126 | size = size[0].split('x')
127 | if (this.width == null) { this.width = parseInt(size[0], 10) }
128 | if (this.height == null) { return this.height = parseInt(size[1], 10) }
129 | }
130 | }
131 | })
132 | this.mpeg1Muxer.on('ffmpegError', (data) => { return global.process.stderr.write(data) })
133 | return this
134 | }
135 |
136 |
137 |
138 | stop() {
139 | this.server.close();
140 | this.httpServer.shutdown(function() {})
141 | }
142 |
143 | getPortForMiddlewareServer(wsPort){
144 | let lustNumberPort = wsPort.toString().slice(-1),
145 | middlewareServerPort = wsPort.toString().slice(0, -1)
146 | lustNumberPort = Number(lustNumberPort) + 1 < 10 ? Number(lustNumberPort) + 1 : Number(lustNumberPort) - 1
147 | return middlewareServerPort + lustNumberPort
148 | }
149 | }
150 |
151 | module.exports = VideoStream
152 |
--------------------------------------------------------------------------------