Tuesday, July 8, 2025

How to Stream RTSP IP Cameras and USB Webcams to the Browser with JSMpeg and WebSockets

Commercial NVRs and cloud-based streaming services often impose high latency, heavy resource overhead, or recurring subscriptions. If you have an IP camera (such as a Hikvision IPC-B120 with an RTSP stream) or a USB webcam (such as a Logitech BRIO), you can stream live video directly to any modern browser with low latency and zero plugins.

Architecture Overview

This lightweight pipeline converts video on-demand using three core components:

  • FFmpeg: Pulls the RTSP feed or V4L2 webcam stream, encodes it as an MPEG-TS container with MPEG-1 video (mpeg1video) and MP2 audio, and pipes stdout to websocat.
  • websocat: Listens as an on-demand WebSocket server. When a browser client connects, websocat launches FFmpeg and streams the video chunks over WebSocket. When the browser tab closes, websocat terminates FFmpeg automatically—consuming 0% CPU when idle!
  • JSMpeg: A tiny JavaScript decoder that renders MPEG-1 video frames directly onto an HTML5 <canvas> element in the browser.

1. RTSP Camera Service (/etc/systemd/system/rtsp-ws.service)

Create a systemd service for your Hikvision or ONVIF RTSP camera:

[Unit]
Description=On-demand WebSocket->FFmpeg bridge for JSMpeg RTSP
After=network.target

[Service]
User=pi
Group=pi
ExecStart=/usr/local/bin/websocat \
  --binary \
  --exit-on-eof \
  -s 127.0.0.1:10000 \
  sh-c:'/usr/bin/ffmpeg -hide_banner -loglevel error -rtsp_transport tcp -i "rtsp://user:pass@192.168.1.100:554/Streaming/Channels/101" -f mpegts -codec:v mpeg1video -bf 0 -r 25 pipe:1'
Restart=on-failure

[Install]
WantedBy=multi-user.target

2. USB Webcam Service (/etc/systemd/system/webcam-ws.service)

Create a systemd service for a local V4L2 USB camera (e.g. Logitech BRIO) with ALSA microphone audio:

[Unit]
Description=On-demand WebSocket->FFmpeg bridge for Logitech Brio
After=network.target

[Service]
User=pi
ExecStart=/usr/local/bin/websocat \
  --binary \
  --exit-on-eof \
  -s 127.0.0.1:10001 \
  sh-c:'WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/1000 /usr/bin/ffmpeg -hide_banner -loglevel error \
    -f v4l2 -framerate 30 -video_size 1920x1080 -i /dev/video0 \
    -f alsa -ac 2 -ar 44100 -i default \
    -f mpegts -codec:v mpeg1video -bf 0 -r 30 -codec:a mp2 pipe:1'
Restart=on-failure

[Install]
WantedBy=multi-user.target

Enable and start the services:

sudo systemctl daemon-reload
sudo systemctl enable --now rtsp-ws webcam-ws

3. Frontend HTML5 Player (player.html)

Serve this minimalist HTML5 page with JSMpeg to view the stream from any desktop or mobile browser:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Live Camera Stream (JSMpeg)</title>
  <style>
    html, body {
      margin: 0; padding: 0;
      width: 100vw; height: 100vh;
      background: #000;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .player-container {
      width: 90vw;
      max-width: 1280px;
      max-height: 90vh;
    }
    .player-container canvas {
      width: 100%;
      height: auto;
      display: block;
    }
  </style>
</head>
<body>
  <div class="player-container">
    <canvas id="videoCanvas"></canvas>
  </div>

  <script src="/jsmpeg.min.js"></script>
  <script>
    const protocol = location.protocol === 'https:' ? 'wss://' : 'ws://';
    const url = protocol + location.host + '/my-ws-prefix';

    new JSMpeg.Player(url, {
      canvas: document.getElementById('videoCanvas'),
      autoplay: true,
      audio: false
    });
  </script>
</body>
</html>

How Google Antigravity Solved the Mysterious NVIDIA Sleep Reboot on My Dell Inspiron 7567 (Ubuntu Linux)

If you run modern Ubuntu or Linux on a Dell Inspiron 15 Gaming (7567) or a similar 7th-gen Intel laptop paired with an NVIDIA GeForce GTX ...