Skip to content

Commit

Permalink
performance improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
k-yle committed Feb 6, 2021
1 parent fbfc794 commit 50e5ffd
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 13 deletions.
16 changes: 16 additions & 0 deletions .github/renovate.json
@@ -0,0 +1,16 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"labels": ["dependencies"],
"rangeStrategy": "bump",
"dependencyDashboard": false,
"timezone": "Pacific/Auckland",
"schedule": ["every 2 weeks"],
"packageRules": [
{
"matchDepTypes": ["devDependencies"],
"groupName": "devDependencies",
"automerge": true
}
]
}
19 changes: 19 additions & 0 deletions README.md
Expand Up @@ -3,6 +3,7 @@
[![Build Status](https://github.com/k-yle/rtsp-relay/workflows/build/badge.svg)](https://github.com/k-yle/rtsp-relay/actions)
[![Coverage Status](https://coveralls.io/repos/github/k-yle/rtsp-relay/badge.svg?branch=main)](https://coveralls.io/github/k-yle/rtsp-relay?branch=main)
[![npm version](https://badge.fury.io/js/rtsp-relay.svg)](https://badge.fury.io/js/rtsp-relay)
![npm bundle size](https://img.shields.io/bundlephobia/minzip/rtsp-relay)

This module allows you to view an RTSP stream in your web browser using an existing express.js server.

Expand Down Expand Up @@ -58,6 +59,24 @@ app.listen(2000);

Open [http://localhost:2000](http://localhost:2000) in your web browser.

### Usage with many cameras

If you have hundreds of cameras and don't want to define a seperate route for each one, you can use a dynamic URL:

```js
app.ws('/api/stream/:cameraIP', (ws, req) =>
proxy({
url: `rtsp://${req.params.cameraIP}:554/feed`,
})(ws),
);
```

### Usage with many clients

You may see a `MaxListenersExceededWarning` if the relay is re-transmitting 10+ streams at once, or if 10+ clients are watching.

This is expected, and you can silence the warning by adding `process.setMaxListeners(0);` to your code.

## Contributing

We have end-to-end tests to ensure that the module actually works. These tests spin up a RTSP server using [aler9/rtsp-simple-server](https://github.com/aler9/rtsp-simple-server) and create several different streams for testing. These tests are far from complete.
Expand Down
38 changes: 26 additions & 12 deletions index.js
Expand Up @@ -13,6 +13,7 @@ const ps = require('ps-node');
*
* @typedef {import("express").Application} Application
* @typedef {import("ws")} WebSocket
* @typedef {import("child_process").ChildProcessWithoutNullStreams} Stream
*/

class InboundStreamWrapper {
Expand All @@ -25,9 +26,9 @@ class InboundStreamWrapper {
[
'-i',
url,
'-f',
'-f', // force format
'mpegts',
'-codec:v',
'-codec:v', // specify video codec (MPEG1 required for jsmpeg)
'mpeg1video',
'-r',
'30', // 30 fps. any lower and the client can't decode it
Expand Down Expand Up @@ -61,7 +62,7 @@ class InboundStreamWrapper {
get(options) {
this.verbose = options.verbose;
if (!this.stream) this.start(options);
return this.stream;
return /** @type {Stream} */ (this.stream);
}

/** @param {number} clientsLeft */
Expand Down Expand Up @@ -89,6 +90,13 @@ module.exports = (app) => {
if (!wsInstance) wsInstance = ews(app);
const wsServer = wsInstance.getWss();

/**
* This map stores all the streams in existance, keyed by the URL.
* This means we only ever create one InboundStream per URL.
* @type {{ [url: string]: InboundStreamWrapper }}
*/
const Inbound = {};

return {
killAll() {
ps.lookup({ command: 'ffmpeg' }, (err, list) => {
Expand All @@ -101,12 +109,13 @@ module.exports = (app) => {

/** @param {Options} props */
proxy({ url, additionalFlags = [], verbose }) {
const Inbound = new InboundStreamWrapper();
if (!url) throw new Error('URL to rtsp stream is required');

// TODO: node15 use ||=
if (!Inbound[url]) Inbound[url] = new InboundStreamWrapper();

/** @param {WebSocket} ws */
function handler(ws) {
if (!url) throw new Error('URL to rtsp stream is required');

// these should be detected from the source stream
const [width, height] = [0, 0];

Expand All @@ -117,18 +126,23 @@ module.exports = (app) => {
ws.send(streamHeader, { binary: true });

if (verbose) console.log('[rtsp-relay] New WebSocket Connection');
const streamIn = Inbound.get({ url, additionalFlags, verbose });

const streamIn = Inbound[url].get({ url, additionalFlags, verbose });

/** @param {Buffer} chunk */
function onData(chunk) {
if (ws.readyState === 1) ws.send(chunk);
}

ws.on('close', () => {
const c = wsServer.clients.size;
if (verbose)
console.log(`[rtsp-relay] WebSocket Disconnected ${c} left`);
Inbound.kill(c);
Inbound[url].kill(c);
streamIn.stdout.off('data', onData);
});

// @ts-expect-error will fix later
streamIn.stdout.on('data', (data, opts) => {
if (ws.readyState === 1) ws.send(data, opts);
});
streamIn.stdout.on('data', onData);
}
return handler;
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "rtsp-relay",
"version": "1.1.1",
"version": "1.1.2",
"author": "Kyle Hensel",
"license": "MIT",
"description": "馃摻 Relay an RTSP stream through an existing express.js server",
Expand Down
7 changes: 7 additions & 0 deletions test/setupTests.js
Expand Up @@ -4,6 +4,13 @@ const { path: ffmpegPath } = require('@ffmpeg-installer/ffmpeg');
const { join } = require('path');
const rtspRelay = require('..');

process.on('warning', (err) => {
if (err.name === 'MaxListenersExceededWarning') {
console.log(err.stack);
console.trace();
}
});

console.log(`Setting up RTSP server on ${process.platform} (${process.arch})`);

const rtspServer = spawn(
Expand Down

0 comments on commit 50e5ffd

Please sign in to comment.