rtsp-relay

repository·main·Indexed 18 days ago

https://github.com/k-yle/rtsp-relay

A tool for streaming RTSP video feeds to web browsers via WebSockets and Canvas rendering. It integrates with Express.js servers to relay streams using ffmpeg and utilizes the js-mpeg decoder on the client side. Key features include auto-reconnection, one-to-many support to optimize performance, and support for dynamic routes and HTTPS/SSL configurations.

Tokens
4.1K
Snippets
14
Records
17
Agent score
58%

What's inside rtsp-relay

  1. How rtsp-relay works

    main

    rtsp-relay allows viewing RTSP streams in a web browser by creating a websocket endpoint in an Express.js server.

    Workflow:

    1. Server-side: The module uses ffmpeg to relay the RTSP stream through a websocket endpoint (e.g., /api/stream).
    2. Client-side: The js-mpeg decoder is used to decode the websocket stream and render it to a <canvas> element.

    Key Features:

    • Auto-reconnection: Automatically reconnects the server <=> RTSP stream connection if it drops, and ensures the client keeps trying to reconnect to the server.
    • One-to-Many Support: If multiple clients connect to the same stream, only one instance of the RTSP stream is consumed to optimize performance.
  2. Configure rtsp-relay for HTTPS/SSL

    main

    To use HTTPS, you must initialize rtsp-relay with both the Express app and the Node.js https server instance. Additionally, ensure the client-side loadPlayer uses the wss:// protocol.

    const rtspRelay = require('rtsp-relay');
    const express = require('express');
    const https = require('https');
    const fs = require('fs');
    
    const key = fs.readFileSync('./privatekey.pem', 'utf8');
    const cert = fs.readFileSync('./fullchain.pem', 'utf8');
    const ca = fs.readFileSync('./chain.pem', 'utf8'); // required for iOS 15+
    
    const app = express();
    const server = https.createServer({ key, cert, ca }, app);
    
    // Pass both app and server to rtspRelay
    const { proxy, scriptUrl } = rtspRelay(app, server);
    
    app.ws('/api/stream', proxy({ url: 'rtsp://1.2.3.4:554' }));
    
    app.get('/', (req, res) =>
      res.send(`
      <canvas id='canvas'></canvas>
    
      <script src='${scriptUrl}'></script>
      <script>
        loadPlayer({
          url: 'wss://' + location.host + '/api/stream',
          canvas: document.getElementById('canvas')
        });
      </script>
    `),
    );
    
    server.listen(443);
    const rtspRelay = require('rtsp-relay');
    const express = require('express');
    const https = require('https');
    const fs = require('fs');
    
    const key = fs.readFileSync('./privatekey.pem', 'utf8');
    const cert = fs.readFileSync('./fullchain.pem', 'utf8');
    const ca = fs.readFileSync('./chain.pem', 'utf8'); // required for iOS 15+
    
    const app = express();
    const server = https.createServer({ key, cert, ca }, app);
    
    const { proxy, scriptUrl } = rtspRelay(app, server);
    
    app.ws('/api/stream', proxy({ url: 'rtsp://1.2.3.4:554' }));
    
    app.get('/', (req, res) =>
      res.send(`
      <canvas id='canvas'></canvas>
    
      <script src='${scriptUrl}'></script>
      <script>
        loadPlayer({
          url: 'wss://' + location.host + '/api/stream',
          canvas: document.getElementById('canvas')
        });
      </script>
    `),
    );
    
    server.listen(443);
  3. Install rtsp-relay and express

    main

    To use rtsp-relay with an Express.js server, install both rtsp-relay and express via npm. Note that you do not need to install ffmpeg separately as the module handles it.

    npm install -S rtsp-relay express
  4. Improve video quality via proxy options

    main

    You can attempt to improve stream quality by passing specific options to the proxy function. Note that these methods will increase bandwidth usage.

    • Use ffmpeg quality flags: Pass additionalFlags: ['-q', '1'].
    • Use TCP transport: Pass transport: 'tcp'.
    // Try quality flags
    app.ws('/api/stream', proxy({ additionalFlags: ['-q', '1'] }));
    
    // Or try TCP transport
    app.ws('/api/stream', proxy({ transport: 'tcp' }));
    // try this:
    app.ws('/api/stream', proxy({ additionalFlags: ['-q', '1'] }));
    
    // or this:
    app.ws('/api/stream', proxy({ transport: 'tcp' }));
  5. Integrate rtsp-relay with Express

    main

    To use rtsp-relay, pass your Express app instance to the module export. If you are using HTTPS, you must also pass your server instance. The module returns an object containing a proxy function and a scriptUrl for client-side integration.

    const express = require('express');
    const rtspRelay = require('rtsp-relay');
    
    const app = express();
    const relay = rtspRelay(app);
    
    // Use relay.proxy(options) in your routes
    const express = require('express');
    const rtspRelay = require('rtsp-relay');
    
    const app = express();
    const relay = rtspRelay(app);
  6. Handle MaxListenersExceededWarning

    main

    If you are re-transmitting 10+ streams simultaneously or have 10+ clients watching, you may encounter a MaxListenersExceededWarning. This is expected behavior. You can silence this warning by adding the following to your code:

    process.setMaxListeners(0);
  7. Integrate rtsp-relay with Angular

    main

    To use rtsp-relay in an Angular application, use the loadPlayer function from the rtsp-relay/browser package. You must provide a <canvas> element via a @ViewChild reference and a WebSocket URL pointing to your relay server.

    1. In your component template, add a <canvas> element with a template reference variable (e.g., #videoPlayer).
    2. In your component class, use @ViewChild to access the ElementRef<HTMLCanvasElement>.
    3. Call loadPlayer within the ngAfterViewInit lifecycle hook to ensure the DOM element is available.
    4. The loadPlayer function returns a Player instance once the connection is established.
    import { Component, ElementRef, AfterViewInit, ViewChild } from '@angular/core';
    import { loadPlayer, Player } from 'rtsp-relay/browser';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
    })
    export class AppComponent implements AfterViewInit {
      player?: Player;
    
      @ViewChild('videoPlayer')
      videoPlayer?: ElementRef<HTMLCanvasElement>;
    
      async ngAfterViewInit() {
        this.player = await loadPlayer({
          url: 'ws://localhost:2000/api/stream/1',
          canvas: this.videoPlayer!.nativeElement,
          onDisconnect: () => console.log('Connection lost!'),
        });
      }
    }
  8. Use loadPlayer with ES6 Imports (React, Vue, etc.)

    main

    If you are using a bundler like Webpack or Babel, you can import loadPlayer directly from rtsp-relay/browser instead of using a <script> tag.

    import { loadPlayer } from 'rtsp-relay/browser';
    
    loadPlayer({
      url: `ws://${location.host}/stream`,
      canvas: document.getElementById('canvas'),
    
      // optional
      onDisconnect: () => console.log('Connection lost!'),
    });
    // client side code
    import { loadPlayer } from 'rtsp-relay/browser';
    
    loadPlayer({
      url: `ws://${location.host}/stream`,
      canvas: document.getElementById('canvas'),
    
      // optional
      onDisconnect: () => console.log('Connection lost!'),
    });
  9. Handle multiple cameras with dynamic routes

    main

    Instead of defining a separate route for every camera, you can use Express route parameters to create a dynamic websocket endpoint that proxies to different RTSP URLs based on the request.

    app.ws('/api/stream/:cameraIP', (ws, req) =>
      proxy({
        url: `rtsp://${req.params.cameraIP}:554/feed`,
      })(ws),
    );
  10. Basic usage of rtsp-relay with Express

    main

    To set up a basic relay, initialize rtsp-relay by passing your Express app instance. Use the proxy function to create a handler for a websocket route. The proxy function accepts an options object containing the url of the RTSP stream.

    On the client side, use the scriptUrl provided by the module to load the player, then call loadPlayer with the websocket URL and a target <canvas> element.

    const express = require('express');
    const app = express();
    
    const { proxy, scriptUrl } = require('rtsp-relay')(app);
    
    const handler = proxy({
      url: `rtsp://admin:admin@10.0.1.2:554/feed`,
      verbose: false,
    });
    
    // The endpoint our RTSP uses
    app.ws('/api/stream', handler);
    
    // Example HTML page to view the stream
    app.get('/', (req, res) =>
      res.send(`
      <canvas id='canvas'></canvas>
    
      <script src='${scriptUrl}'></script>
      <script>
        loadPlayer({
          url: 'ws://' + location.host + '/api/stream',
          canvas: document.getElementById('canvas')
        });
      </script>
    `),
    );
    
    app.listen(2000);
    const express = require('express');
    const app = express();
    
    const { proxy, scriptUrl } = require('rtsp-relay')(app);
    
    const handler = proxy({
      url: `rtsp://admin:admin@10.0.1.2:554/feed`,
      verbose: false,
    });
    
    // the endpoint our RTSP uses
    app.ws('/api/stream', handler);
    
    // this is an example html page to view the stream
    app.get('/', (req, res) =>
      res.send(`
      <canvas id='canvas'></canvas>
    
      <script src='${scriptUrl}'></script>
      <script>
        loadPlayer({
          url: 'ws://' + location.host + '/api/stream',
          canvas: document.getElementById('canvas')
        });
      </script>
    `),
    );
    
    app.listen(2000);
  11. Use loadPlayer() for browser-side streaming

    main

    The loadPlayer function initializes a connection to an RTSP stream via a WebSocket relay and renders the video onto a provided HTML <canvas> element. It is an asynchronous function that returns a Player instance.

    Parameters:

    • url (string): The WebSocket URL of the stream (e.g., ws://localhost:2000/api/stream/1).
    • canvas (HTMLCanvasElement): The canvas element where the video will be rendered.
    • onDisconnect (function, optional): A callback function executed when the connection to the stream is lost.

    Returns:

    • Promise<Player>: A promise that resolves to a Player instance once the connection is established.
    const player = await loadPlayer({
      url: 'ws://localhost:2000/api/stream/1',
      canvas: canvasElement,
      onDisconnect: () => { /* handle disconnect */ },
    });
  12. Configure RTSP transport protocol

    main

    When setting up a proxy, you can specify the RTSP transport protocol using the transport key in the options object. This ensures the -rtsp_transport flag is placed correctly in the FFmpeg command before the input URL.

    Supported values:

    • 'udp'
    • 'tcp'
    • 'udp_multicast'
    • 'http'

    Warning: Do not attempt to pass -rtsp_transport manually inside the additionalFlags array, as it may lead to incorrect command ordering.