npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rtsp-streamer

v1.0.0

Published

Zero-dependency RTSP-to-browser streaming over WebSocket, with a WebCodecs <rtsp-player> element and React/Angular bindings — no ffmpeg, no native addons, no media server.

Downloads

129

Readme

rtsp-streamer

Stream an RTSP camera to the browser over WebSocket, with zero runtime dependencies — no ffmpeg, no native addons, no external media server. The package speaks RTSP/RTP directly (interleaved over TCP), forwards H.264 access units to browsers, and ships a <rtsp-player> custom element that decodes them with WebCodecs.

RTSP camera ──TCP──▶ streamRtsp() ──WebSocket──▶ <rtsp-player> ──WebCodecs──▶ <canvas>

Features

  • Zero dependencies. Only Node's built-in net/crypto/http.
  • Any HTTP server. Works with the raw http module, Express, Fastify, or NestJS — you hand it the underlying http.Server.
  • Connection sharing. One camera connection per unique RTSP URL, fanned out to every viewer; each streamRtsp() call returns a fresh access token.
  • Instant start. New viewers immediately receive the last cached keyframe, so video appears without waiting for the camera's next IDR.
  • Audio. AAC, G.711 (PCMU/PCMA) and Opus tracks are forwarded and played via WebCodecs + Web Audio; audio problems degrade to video-only, never a dead player.
  • Basic & Digest auth, automatic reconnect, and idle teardown (a session with no viewers for 60s disconnects from the camera).
  • Batteries-included client. The <rtsp-player> element is served straight from the package at /rtsp-player.js — no copy step, no bundler.
  • Dual CJS + ESM builds with full TypeScript types.

Install

npm install rtsp-streamer

Requirements

  • Server: Node.js 18+.
  • Browser: a WebCodecs-capable browser (Chrome/Edge 94+, Safari 16.4+).
  • Camera: an H.264 (AVC) RTSP video stream — HEVC/H.265 is not decoded. Audio is forwarded when the camera offers AAC (mpeg4-generic), G.711 (PCMU/PCMA) or Opus; other audio codecs are ignored.

Quick start

Server

Call streamRtsp(server, rtspUrl) to mint a token-protected WebSocket path, and serveRtspPlayer(req, res) to serve the browser element. streamRtsp() also attaches the WebSocket upgrade handler and starts serving /rtsp-player.js automatically the first time it runs.

import * as http from "node:http";
import { streamRtsp, serveRtspPlayer } from "rtsp-streamer";

const server = http.createServer((req, res) => {
  // Serve the <rtsp-player> element at /rtsp-player.js (before your routes).
  if (serveRtspPlayer(req, res)) return;

  // Mint a stream for a camera URL the client asks for.
  if (req.method === "POST" && req.url === "/api/stream") {
    let body = "";
    req.on("data", (c) => (body += c));
    req.on("end", () => {
      const { rtspUrl } = JSON.parse(body);
      const path = streamRtsp(server, rtspUrl); // -> "/stream/<token>"
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ path }));
    });
    return;
  }

  // ...serve your own HTML/assets here...
});

server.listen(8080);

Browser

Load the element and point it at a stream. The element POSTs the RTSP URL to your server (/api/stream by default), opens the returned WebSocket, and paints frames onto an internal <canvas>.

<script type="module" src="/rtsp-player.js"></script>

<rtsp-player
  src="rtsp://user:[email protected]:554/stream1"
  width="960"
  autoplay
  muted
></rtsp-player>

Or drive it from JavaScript:

const player = document.querySelector("rtsp-player");

await player.play("rtsp://user:pass@cam/stream1");
player.stop();

player.addEventListener("playing", () => console.log("live"));
player.addEventListener("error", (e) => console.error(e.detail.message));

Server API

streamRtsp(server, rtspUrl): string

Starts (or reuses) an RTSP session for rtspUrl and exposes it on server as a token-protected WebSocket endpoint. Returns the path, e.g. "/stream/6bd1…e2". Multiple calls with the same URL share one camera connection but each returns a distinct token.

The first call on a given server also:

  • attaches a single upgrade handler for the /stream/<token> WebSocket, and
  • wraps the server's request listener so /rtsp-player.js is served before your own routes.

Call streamRtsp() after your routes/handler are attached to the server, so they are captured by the request wrapper.

serveRtspPlayer(req, res, mountPath?): boolean

Serves the bundled <rtsp-player> element (and its source map) for a plain http.IncomingMessage. Returns true if it handled the request — a GET/HEAD for mountPath (default /rtsp-player.js) or mountPath + ".map" — so you can fall through to your own routing:

if (serveRtspPlayer(req, res)) return;

Because streamRtsp() only wires this up on its first call (typically the first /api/stream request), call serveRtspPlayer() yourself in your request pipeline so the script is available on the very first page load.

Wire format

The first byte of each WebSocket binary message is the message type:

0  delta video   [Annex-B H.264 access unit]
1  key video     [Annex-B H.264 access unit]
2  audio config  [UTF-8 JSON: { codec, sampleRate, numberOfChannels,
                  description? (base64) } — a WebCodecs AudioDecoderConfig]
3  audio frame   [one encoded frame: raw AAC AU / G.711 bytes / Opus packet]

No cross-track timestamps are carried — it's a live view; both tracks are forwarded as they arrive. You only need this if you are writing your own client instead of using <rtsp-player>.

The <rtsp-player> element

A dependency-free custom element (shadow DOM, internal <canvas> and status overlay). Import it as a module — it registers rtsp-player on load.

Attributes

All attributes are reflected as properties (player.src, player.autoplay, …).

| Attribute | Type | Default | Description | | ---------- | ------- | ------------- | ------------------------------------------------------ | | src | string | — | RTSP URL to play (rtsp://…). | | width | string | — | CSS width of the video surface (px if unitless). | | height | string | — | CSS height of the video surface (px if unitless). | | autoplay | boolean | false | Play as soon as the element connects or src changes. | | muted | boolean | false | Mute audio output. Toggleable live, like <video>. | | api | string | /api/stream | Endpoint that mints a stream token from an rtspUrl. |

Browsers keep audio suspended until a user gesture: starting playback from a click works immediately, while autoplay streams stay silent until the first pointer interaction (video is unaffected).

Methods

  • play(src?) — resolve a token, open the WebSocket, and start decoding. Passing a URL adopts it into src. Resolves once the socket is open (frames arrive asynchronously after).
  • stop() — close the socket and decoder and blank the canvas.

Properties (read-only)

  • state"idle" | "connecting" | "waiting" | "playing" | "error".
  • playingtrue while in the playing state.

Events

| Event | detail | Fires when… | | ------------- | --------------------- | ------------------------------------ | | playing | — | the first frame is decoding. | | stopped | — | playback stops or the socket closes. | | error | { message: string } | a request/socket/decode error. | | statechange | { state } | state transitions. |

The element expects your server to answer its api endpoint with the shape streamRtsp() produces:

POST /api/stream  { "rtspUrl": "rtsp://…" }  ->  { "path": "/stream/<token>" }

React

The same player as a component, for apps that would rather not register a custom element. React is an optional peer dependency; nothing else is added.

import { RtspPlayer } from "rtsp-streamer/react";

<RtspPlayer src="rtsp://user:pass@cam/stream1" width={960} autoPlay muted />;

Props mirror the element's attributes — src, width, height, autoPlay, muted, api — plus className, style, hideStatus (drop the built-in status overlay) and children (rendered above the canvas). The element's events become callbacks: onPlaying, onStopped, onError(message), onStateChange(state). A ref exposes play(src?), stop(), state and playing.

For custom chrome, use the hook and render your own markup:

import { useRtspPlayer } from "rtsp-streamer/react";

function Camera({ src }) {
  const { canvasRef, state, status, play, stop } = useRtspPlayer({ src });
  return (
    <>
      <canvas ref={canvasRef} />
      <button onClick={() => (state === "playing" ? stop() : play())}>
        {status}
      </button>
    </>
  );
}

RtspEngine — the framework-free core both of the above are built on (socket, WebCodecs decoders, Web Audio; you supply the <canvas>) — is exported too.

Angular

The same player as a standalone Angular component. Angular is an optional peer dependency (>=17); nothing else is added.

import { Component } from "@angular/core";
import { RtspPlayerComponent } from "rtsp-streamer/angular";

@Component({
  standalone: true,
  imports: [RtspPlayerComponent],
  template: `
    <rtsp-player
      src="rtsp://user:pass@cam/stream1"
      [width]="960"
      autoPlay
      muted
    ></rtsp-player>
  `,
})
export class CameraComponent {}

Inputs mirror the element's attributes — src, width, height, autoPlay, muted, api, plus hideStatus (drop the built-in status overlay). Projected content (<ng-content>) renders above the canvas. The element's events become @Output()s: playing, stopped, error (emits the message), stateChange (emits the state). Grab the component with a template ref or @ViewChild for the imperative play(src?) / stop() methods and the read-only state.

@ViewChild(RtspPlayerComponent) player!: RtspPlayerComponent;
// this.player.play("rtsp://user:pass@cam/stream1"); this.player.stop();

The component's selector is rtsp-player — the same tag as the bundled custom element. Use one or the other in a given app, never both (don't also load /rtsp-player.js). For custom chrome, import RtspEngine from rtsp-streamer/angular and drive your own <canvas>.

Build note. This binding is compiled with plain tsc (like the React one), not the Angular compiler, so it is not Ivy/AOT-precompiled. It works under Angular's JIT compiler; consuming it from a default AOT build needs the component compiled from source (e.g. a path mapping into src/angular). A proper partial-Ivy package build (ngc --compilationMode partial / ng-packagr) is the long-term fix but currently blocked by the repo's bleeding-edge TypeScript version, which the Angular compiler rejects.

Framework examples

Runnable servers for the raw http module, Express, Fastify, and NestJS live in examples/ — see the examples index for the full overview. Each hands its underlying http.Server to streamRtsp() and serves the shared demo page from examples/public.

Build once (npm run build), then run any server and open http://localhost:8080:

npm run examples:http      # plain node:http  → examples/http
npm run examples:express   # Express          → examples/express
npm run examples:fastify   # Fastify          → examples/fastify
npm run examples:nest      # NestJS           → examples/nest

Each folder has its own walkthrough: http · express · fastify · nest · react · angular.

examples/react and examples/angular are the odd ones out: SPA apps using rtsp-streamer/react / rtsp-streamer/angular instead of the element. Neither ships a server — each reuses one of the servers above for the API and WebSocket, so it runs as two processes (the backend on :8080, the client dev server on :5173 / :4200) and needs its own npm install.

The key detail across frameworks: the WebSocket upgrade is handled on the raw http.Server, so build the server explicitly (e.g. http.createServer(app) for Express, app.server for Fastify, app.getHttpServer() for Nest) and pass that to streamRtsp() — the framework keeps ownership of ordinary routes.

Module formats

The package ships both CommonJS and ES modules with type declarations, resolved via the exports map:

const { streamRtsp } = require("rtsp-streamer"); // CommonJS -> dist/index.js
import { streamRtsp } from "rtsp-streamer"; //        ESM -> dist/esm/index.js

The compiled <rtsp-player> element is also available as a subpath import if you prefer to bundle it yourself instead of serving it via serveRtspPlayer():

import "rtsp-streamer/html"; // registers <rtsp-player>

Local testing

No camera handy? Publish your webcam as an RTSP stream with MediaMTX and ffmpeg, then point the player at rtsp://localhost:8554/webcam.

Start the RTSP server:

mediamtx

Publish the webcam (Windows / DirectShow — adjust the device name for your OS):

ffmpeg -f dshow -i video="Integrated Camera" \
  -c:v libx264 -preset ultrafast -tune zerolatency \
  -f rtsp rtsp://localhost:8554/webcam

Lower latency and reduced input buffering:

ffmpeg -f dshow -rtbufsize 256M -vcodec mjpeg -i video="Integrated Camera" \
  -c:v libx264 -preset ultrafast -tune zerolatency \
  -f rtsp rtsp://localhost:8554/webcam

For Audio support in windows, use the following command:

ffmpeg -f dshow -i video="Integrated Camera":audio="Microphone Array (Realtek(R) Audio)" -c:v libx264 -preset ultrafast -pix_fmt yuv420p -b:v 2000k -c:a aac -b:a 128k -f rtsp rtsp://localhost:8554/webcam

Development

npm run build        # build:html + inline asset, then CJS and ESM
npm run typecheck    # type-check every project (Node + browser)
npm run lint         # ESLint
npm run format       # Prettier --write

src/index.ts is the Node library; src/html/rtsp-player.ts is the browser element (compiled with DOM/WebCodecs types via tsconfig.html.json and inlined into the library by scripts/build-assets.mjs).

Releasing

Publishing is automated by .github/workflows/publish.yml: every push to main runs lint + type-check, then publishes to npm only if package.json's version is not already on the registry. So a release is just a version bump:

npm version patch   # or minor / major — commits and tags
git push origin main --follow-tags

One-time setup: add an npm automation access token as the repository secret NPM_TOKEN (Settings → Secrets and variables → Actions). The workflow publishes with provenance, which needs a public repository; drop --provenance from the workflow if the repo is private. Feature branches and PRs are checked by .github/workflows/ci.yml.

License

ISC