Files
RTSP-Streamer/README.md
2026-07-01 18:21:14 -05:00

203 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# rtsp-streamer
A full-screen RTSP video wall for UniFi Protect cameras, built for a Raspberry
Pi 4 (or any Linux box) plugged into a TV. No Chrome kiosk, no desktop — just
`mpv` tiles under a minimal Wayland compositor, driven by a single Go binary
you configure over SSH from the command line or an interactive TUI.
## Why this design
- **One `mpv` process per camera.** A dead or unreachable camera never takes
down the wall — the daemon restarts only the slot that failed, with backoff.
(Contrast with a single ffmpeg/`mpv` mosaic, where one stalled input can
freeze the whole picture.)
- **Compositor tiling, not fullscreen.** A tiny [sway](https://swaywm.org)
session hosts the windows; the daemon positions each one at an exact pixel
rectangle over sway's IPC. No X11, no browser, no GPU compositor tricks.
- **Hardware decode.** `mpv --hwdec=auto-safe` uses the Pi 4's V4L2/DRM H.264
decoder.
- **Stateful config.** A single hand-editable `config.yaml`, or edit it live
with `rtsp-streamer tui`. Switching layouts is instant and doesn't restart
anything you don't have to.
```
Pi 4 (headless, HDMI -> TV)
┌───────────────────────────────────────────────┐
│ tty1 autologin -> sway -> rtsp-streamer daemon │
│ ├─ reads active layout -> computes grid rects │
│ ├─ spawns 1 mpv per slot (--hwdec, IPC sock) │
│ ├─ swaymsg: float + position each to a cell │
│ └─ health-monitors mpv, restarts dead streams │
│ │
│ control socket ◀── rtsp-streamer layout set … │
│ ◀── rtsp-streamer tui (over SSH) │
└───────────────────────────────────────────────┘
▲ discover
UniFi Protect controller (login -> bootstrap -> RTSPS URLs)
```
## Requirements
On the display device:
- `sway` and `mpv`
- optional: a terminal like `foot` for the emergency keybinding
Debian / Raspberry Pi OS: `sudo apt install sway mpv foot`
Arch: `sudo pacman -S sway mpv foot`
To build: Go 1.23+ (only on your dev machine; the Pi just needs the binary).
## Build
```sh
make build # native binary -> bin/rtsp-streamer
make pi # Raspberry Pi 4, 64-bit OS (arm64) -> bin/rtsp-streamer-arm64
make pi32 # 32-bit Pi OS (armv7) -> bin/rtsp-streamer-armv7
make test
```
## Configure
```sh
rtsp-streamer config init # write a starter ~/.config/rtsp-streamer/config.yaml
export RTSP_STREAMER_PASSWORD=# controller password (kept out of the file)
$EDITOR "$(rtsp-streamer config path)" # set controller.host / username
rtsp-streamer discover # pull cameras + RTSPS URLs from UniFi Protect
rtsp-streamer tui # assign cameras to layout slots, pick active
```
See [`config.example.yaml`](config.example.yaml) for the full schema. The
config path is `$XDG_CONFIG_HOME/rtsp-streamer/config.yaml` (i.e.
`~/.config/rtsp-streamer/config.yaml`), overridable with `--config` or
`$RTSP_STREAMER_CONFIG`.
### Layouts (grid + spanning tiles)
A layout is a base grid (`COLSxROWS`, up to 8×8) on which each camera is a
**tile** that can span multiple cells. That covers a plain 2×2/3×3/4×4 as well
as security-wall arrangements — one big main view plus a column of small ones,
`2+6`, etc. A layout can show up to **16 cameras** (decoding more than that on a
Pi 4 isn't practical even with substreams). Tiles must stay inside the grid and
may not overlap. See `main-plus` in [`config.example.yaml`](config.example.yaml).
### The TUI
`rtsp-streamer tui` is a Bubble Tea configurator meant to be run over SSH:
- **Cameras** — review discovered cameras, `d` disable, `x` delete.
- **Layouts** — edit an arrangement on a live ASCII grid preview.
- **Set active layout** — choose what the wall shows.
- **Discover** / **Discover + enable RTSP (high)** — pull cameras from Protect.
- **Save** — writes the config and tells a running daemon to reload live.
In the **layout editor** the grid is drawn live as you edit:
```
+----------+----------+----------+----------+
|Front Door| · | · |Driveway |
|3x3 | · | · | |
+----------+----------+----------+----------+
| · | · | · |Back Yard |
...
```
- move cursor: arrows or `h`/`j`/`k`/`l`
- `enter` assign a camera to the cell (pick `(empty)` to clear) · `c` clear
- resize the tile under the cursor: `L`/`H` wider/narrower, `J`/`K` taller/shorter
- resize the base grid: `]`/`[` add/remove a column, `}`/`{` add/remove a row
- `esc` back
Elsewhere: arrows / `j`/`k` move, `enter` selects, `esc` goes back, `q` quits.
## Run
```sh
rtsp-streamer daemon # normally launched by sway, not by hand
rtsp-streamer status # slot health from the running daemon
rtsp-streamer layout ls # list layouts (* marks active)
rtsp-streamer layout set quad # switch live (persists the choice)
```
## Deploy to a Pi (boot straight to the wall)
```sh
make pi
scp bin/rtsp-streamer-arm64 pi@display:/tmp/
# on the Pi, put the binary where install.sh expects it and run the installer:
# mv /tmp/rtsp-streamer-arm64 <repo>/bin/rtsp-streamer
sudo ./deploy/install.sh kiosk
```
`install.sh` installs the binary, drops the kiosk sway config at
`/etc/rtsp-streamer/sway/config`, creates the `kiosk` user, enables tty1
autologin, and appends a launcher to the user's `~/.bash_profile` that starts
sway on the physical console only (SSH sessions still get a normal shell).
Reboot and the wall comes up.
Provide the controller password to the daemon by sourcing an env file from the
kiosk user's `~/.bash_profile` **before** the sway launcher, e.g.:
```sh
# ~/.bash_profile (kiosk user)
[ -f ~/.config/rtsp-streamer/env ] && . ~/.config/rtsp-streamer/env
# >>> rtsp-streamer kiosk launcher >>> (added by install.sh)
```
## Enabling RTSP in UniFi Protect
Protect does not expose RTSP until you turn it on **per camera**. You can do it
by hand (Protect → camera → Settings → Advanced → **RTSP**, enable a channel),
or let rtsp-streamer flip it on for you via the API:
```sh
rtsp-streamer discover --enable-rtsp=high # enable the High channel where missing
rtsp-streamer discover --enable-rtsp=low # or the Low substream
```
In the TUI, the menu item **"Discover + enable RTSP (high)"** does the same.
Enabling preserves each channel's other encoder settings — it only flips the
`isRtspEnabled` flag. Plain `discover` (no flag) never modifies your controller;
it just skips cameras with no RTSP-enabled channel and reports which ones.
Use `discover --low-res` (or `--enable-rtsp=low`) to prefer substreams for dense
grids (3x3+), which greatly cuts Pi decode load.
## Troubleshooting
- **Blank/black tile** — verify the stream directly:
`mpv --rtsp-transport=tcp 'rtsps://HOST:7441/ALIAS?enableSrtp'`. If that
fails, RTSP probably isn't enabled for that camera (see above).
- **`swaymsg not found` / windows not placed** — the daemon must run inside a
sway session (started by the kiosk launcher). Check `echo $SWAYSOCK`.
- **High CPU / dropped frames on a 3x3** — switch those cameras to substreams
(`discover --low-res`) and/or add `player.extra_args: ["--vf=fps=15"]`.
- **`login failed`** — use a *local* Protect account (not a Ubiquiti SSO
login) and confirm `controller.host` reaches the UniFi OS console.
## Caveats & scope
- **Unofficial API.** UniFi Protect has no public API; the local endpoints used
here (`/api/auth/login`, `/proxy/protect/api/bootstrap`) are the same stable
ones the Home Assistant integration relies on, but Ubiquiti could change them.
Manual RTSP URLs in `cameras:` work with any camera brand and need no
controller.
- **Linux/Wayland only.** "Other platforms" means other Linux devices (x86 mini
PCs, other SBCs) — anywhere sway + mpv run. It is not a macOS/Windows app.
- **First cut** ships preset grids. Auto-cycling/rotation and free-form
positioning are natural next steps (the daemon already re-tiles on demand).
## Layout
```
cmd/rtsp-streamer/ CLI (cobra): daemon, discover, layout, status, tui, config
internal/config/ YAML load/save/validate, schema, XDG paths
internal/protect/ UniFi Protect client (login, bootstrap, RTSPS URLs)
internal/player/ one supervised mpv per stream, JSON IPC, restart/backoff
internal/compositor/ sway control + NxM grid geometry
internal/daemon/ orchestrator + control socket + health loop
internal/ipc/ control-socket protocol shared by daemon and CLI/TUI
internal/tui/ Bubble Tea configurator
deploy/ sway kiosk config, autologin, install.sh
```