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>

Sunday, May 25, 2025

How to Configure an Nginx Reverse Proxy with WebSockets for Kodi Chorus2

This tutorial details how to configure Nginx as a secure reverse proxy for Kodi's Chorus2 web interface, enabling full remote media playback control over HTTPS under a subpath (e.g., https://domain.com/kodi/).

Before configuring Nginx, enable the Kodi Web Server in Settings → Services → Control and set a secure username and password, as documented in the Kodi Web Interface Wiki.

The Nginx URI Normalization Challenge

Kodi Chorus2 requires two distinct routing configurations:

  1. WebSocket JSON-RPC (port 9090): Real-time playback status and control events route through /jsonrpc without path prefixing.
  2. Web Assets & Album Art (port 10080 / 8080): Chorus2 fetches album artwork and movie posters using dynamic queries. Under Nginx's proxy_pass specification, naive subpath proxying will corrupt the image query string unless the normalized request URI is explicitly matched and forwarded.

Nginx Configuration (/etc/nginx/sites-available/kodi.conf)

Add the following location blocks to your Nginx HTTPS server block:

## Kodi Reverse Proxy Configuration

# 1) WebSocket channel for JSON-RPC
location ^~ /jsonrpc {
    proxy_http_version 1.1;
    proxy_set_header   Upgrade    $http_upgrade;
    proxy_set_header   Connection "Upgrade";
    proxy_set_header   Host       $host;
    proxy_set_header   X-Real-IP  $remote_addr;
    proxy_read_timeout 360s;
    proxy_pass         http://10.10.1.5:9090;
}

# 2) Redirect bare /kodi to canonical /kodi/
location = /kodi {
    return 301 /kodi/;
}

# 3) Proxy Chorus2 web interface and strip /kodi prefix
location /kodi/ {
    if ($request_uri ~ "^/kodi(?/[^?]*)") {
        set $new_path $after;
    }

    proxy_cache        off;
    proxy_set_header   Host      $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_read_timeout 300s;

    proxy_pass         http://10.10.1.5:10080$new_path$is_args$args;
}

Test your configuration and reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

In Chorus2 settings (in your browser), enable reverse proxy mode and set the port to 443. Album art, audio streaming, and WebSocket playback controls will now operate flawlessly.

Friday, April 18, 2025

How to Compile the Latest wf-recorder from Source on Raspberry Pi 5 (Debian 12 Wayland)

On Debian 12 Bookworm (Raspberry Pi OS), the desktop environment runs natively on Wayland. Consequently, traditional X11 screen capture utilities (like FFmpeg's -f x11grab) no longer work.

The standard tool for recording Wayland sessions is wf-recorder. However, the stock wf-recorder v0.3 package provided in Debian's default repository lacks several modern enhancements, most notably the --overwrite flag. Compiling the latest version from source gives you full feature support, hardware-accelerated encoding, and audio capture.

1. Install Build Dependencies

Install the required C++ compilers, Meson build system, PipeWire, and FFmpeg development headers:

sudo apt-get update
sudo apt-get install -y g++ meson ninja-build libavutil-dev libavcodec-dev     libavformat-dev libswscale-dev libpulse-dev wayland-protocols libpipewire-0.3-dev wget

2. Download, Configure, and Build wf-recorder

mkdir -p ~/src && cd ~/src
wget https://github.com/ammen99/wf-recorder/releases/download/v0.5.0/wf-recorder-0.5.0.tar.xz
tar -xf wf-recorder-0.5.0.tar.xz
cd wf-recorder-0.5.0

# Configure release build with Meson
meson setup build --prefix=/usr --buildtype=release --reconfigure

# Compile with Ninja
ninja -C build

# Install binary to /usr/local/bin
sudo cp build/wf-recorder /usr/local/bin/

3. Recording Desktop Video and Audio

To record your full Wayland desktop screen with system audio output:

wf-recorder --overwrite -a default -f capture.mp4
Flag Breakdown:
  • --overwrite: Automatically overwrites existing destination files without prompting.
  • -a default: Captures audio from the default PulseAudio / PipeWire monitor source.
  • -f capture.mp4: Output file path. Stop recording at any time by pressing Ctrl + C.

Friday, February 21, 2025

How to Set Up Kodi 21 with YouTube Add-on and HDMI-CEC on Raspberry Pi 5

This guide covers how to set up Kodi 21 (Omega) with the unofficial YouTube Add-on, Chorus2 web interface, and intelligent HDMI-CEC control on a Raspberry Pi 5 running Raspberry Pi OS (Debian 12/13).

1. Installing Kodi 21

Raspberry Pi repositories provide both kodi (v20) and kodi21 (v21 Omega). Install Kodi 21 along with the adaptive streaming library to prevent playback stuttering:

sudo apt-get update
sudo apt-get install kodi21 kodi21-inputstream-adaptive -y

2. Installing the Unofficial YouTube Plugin

cd ~/Downloads
wget https://ftp.fau.de/osmc/osmc/download/dev/anxdpanic/repositories/repository.yt.testing_unofficial-2.0.7.zip
  1. In Kodi: Settings → Add-ons → Install from zip file → Home folder → Downloads → repository.yt.testing_unofficial-2.0.7.zip.
  2. Under Add-ons, open the context menu / options at bottom left and select Check for updates.
  3. Select Install from repository → YouTube Test Repo (Unofficial) → Video add-ons → YouTube → Install.

3. YouTube Personal API v3 Keys

To avoid hitting shared API quota limits, generate personal Google Cloud credentials (Setup Guide):

  • Create a project in Google Cloud Console with YouTube Data API v3 enabled.
  • Create an OAuth 2.0 Client ID (Application type: TV and Limited Input devices) and an API Key. Ensure the Publishing Status in OAuth Consent Screen is set to Production.
  • In Kodi: YouTube → Settings → API and enter your API Key, Client ID, and Client Secret.

4. Installing the Chorus2 Web Interface

Install the latest Chorus2 web control interface:

tmp="$(mktemp -d)"
curl -L -o "$tmp/chorus2.zip" https://github.com/xbmc/chorus2/archive/refs/heads/master.zip
unzip -q "$tmp/chorus2.zip" -d "$tmp"

mkdir -p ~/.kodi/addons
rm -rf ~/.kodi/addons/webinterface.default.new
cp -a "$tmp"/chorus2-master/dist ~/.kodi/addons/webinterface.default.new

if [ -d ~/.kodi/addons/webinterface.default ]; then
  mv ~/.kodi/addons/webinterface.default ~/.kodi/addons/webinterface.default.bak.$(date +%Y%m%d-%H%M%S)
fi

mv ~/.kodi/addons/webinterface.default.new ~/.kodi/addons/webinterface.default
rm -rf "$tmp"

5. Fine-Tuning HDMI-CEC Power Behavior

To prevent your Raspberry Pi from turning on your TV every time it reboots, add this parameter to /boot/firmware/config.txt:

hdmi_ignore_cec_init=1

Configure automatic playback pausing in Kodi when you switch TV inputs or turn off the screen:

  • Navigate to: Kodi → Settings → System → Input → Peripherals → CEC Adapter
  • Set Action when switching to another source → Stop playback
  • Set When the TV is switched off → Stop playback

6. Wayland Autostart / Remote Launch Script (/scripts/youtube.py)

To launch Kodi remotely over SSH, DBus, or a Telegram bot under Wayland without display collisions:

#!/usr/bin/env python3
import os

os.chdir('/home/pi')
os.environ["DISPLAY"] = ":0"
os.environ["WAYLAND_DISPLAY"] = "wayland-0"
os.environ["XDG_RUNTIME_DIR"] = "/run/user/1000"
os.environ["DBUS_SESSION_BUS_ADDRESS"] = "unix:path=/run/user/1000/bus"

# Terminate competing browser windows before launching media center
os.system("pkill -9 -f 'chrome|chromium|google-chrome|kodi|kodi21.bin'")
os.system('/usr/bin/kodi &')

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 ...