From 9da5ace150146b4cb48afd8199926fb02653772b Mon Sep 17 00:00:00 2001 From: Levi Woodard Date: Wed, 1 Jul 2026 21:58:52 -0500 Subject: [PATCH] docs: update README for tiles, mouse, per-tile quality, deploy, roadmap Document the grid+tiles model, mouse/quality editing, git-based deploy (run as your user, reboot not getty), Pi 4 hwdec/H.265/substream notes, and the real troubleshooting lessons. Add a Roadmap incl. importing pre-made UniFi Protect views. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 133 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 0a5b8eb..3fe316f 100644 --- a/README.md +++ b/README.md @@ -116,13 +116,25 @@ In the **layout editor** the grid is drawn live as you edit: ... ``` +- **mouse (over SSH):** click a cell to assign a camera; **press on a tile and + drag to resize** its span, tmux-style. Click to select on the menus/lists too. - move cursor: arrows or `h`/`j`/`k`/`l` - `enter` assign a camera to the cell (pick `(empty)` to clear) · `c` clear +- `q` cycle this tile's **stream quality** (auto → high → low …); the choice is + shown in the tile. Small tiles → low substream, big tiles → high. - 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. +Elsewhere: arrows / `j`/`k` move (or click), `enter` selects, `esc` back, `q` quits. + +### Stream quality per tile + +Discovery records every RTSP-enabled channel per camera (`streams: {high, low, …}`), +and each tile picks which to pull. Put big tiles on `high` and small tiles on the +`low` substream — that's the main lever for keeping a dense wall smooth on a Pi 4. +Enable both channels at once with `discover --enable-rtsp=high,low` (or the TUI's +"Discover + enable RTSP (hi+lo)"). ## Run @@ -131,33 +143,43 @@ 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) +rtsp-streamer version # baked-in git commit / build date ``` ## 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 /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.: +First-time setup (run `install.sh` **as the user you'll run the wall as** — pass +your own username; letting it default to a `kiosk` user is a common footgun, +because the daemon's control socket lives in that user's runtime dir and must +match your SSH user): ```sh -# ~/.bash_profile (kiosk user) -[ -f ~/.config/rtsp-streamer/env ] && . ~/.config/rtsp-streamer/env -# >>> rtsp-streamer kiosk launcher >>> (added by install.sh) +# get the binary onto the Pi — build there (needs Go 1.23, see Build), or copy: +git clone ~/RTSP-Streamer && cd ~/RTSP-Streamer && make build +sudo ./deploy/install.sh "$USER" ``` +`install.sh` installs the binary to `/usr/local/bin`, drops the sway config at +`/etc/rtsp-streamer/sway/config`, adds you to the `video/render/input` groups, +enables **tty1 autologin**, and appends a launcher to `~/.bash_profile` that +starts sway on the physical console only (SSH still gets a normal shell). The +running wall needs **no controller password** — the config already holds the +resolved stream URLs; the password is only for `discover`. + +Then `sudo reboot` and the wall comes up on boot. + +### Updating + +```sh +git pull && make install && sudo reboot +``` + +`make install` builds natively and copies to `/usr/local/bin` (auto-sudo — run +it *without* `sudo` so `go build` keeps your `PATH`). Use **`sudo reboot`**, not +`systemctl restart getty@tty1`: a getty restart tends to leave the old sway + +daemon running alongside the new one (duplicate mpv, windows fighting). Confirm +what's live with `rtsp-streamer version`. + ## Enabling RTSP in UniFi Protect Protect does not expose RTSP until you turn it on **per camera**. You can do it @@ -177,17 +199,49 @@ 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. +## Performance & latency (Pi 4) + +The Pi 4 hardware-decodes **H.264 only** (no HEVC). If streams lag or CPU is +pegged, work through: + +- **Confirm hardware decode is on.** Probe a running player: + ```sh + S=$XDG_RUNTIME_DIR/rtsp-streamer/mpv-slot-0.sock + printf '{"command":["get_property_string","hwdec-current"]}\n' | socat - "$S" + printf '{"command":["get_property_string","video-codec"]}\n' | socat - "$S" + ``` + `hwdec-current: no` means software decoding. `--hwdec=auto-safe` often won't + pick the Pi's decoder — set `player.hwdec: v4l2m2m-copy` explicitly. +- **H.265 cameras can't hardware-decode on a Pi 4.** If `video-codec` is + `hevc`, set that camera to H.264 in Protect, or use its (often H.264) low + substream. +- **Use substreams for dense walls.** Full-res (4K/4MP) × many tiles exceeds the + decoder's budget → fallback to software → growing latency. Give small tiles + the `low` quality (per-tile `q` in the TUI, or `quality: low` in config). +- **Latency drift** is handled by `--framedrop=decoder+vo` + low-delay demuxer + flags (built in), so a briefly-behind stream drops frames to catch up instead + of accumulating a backlog. Capping `player.max_fps` trims render load too. + ## Troubleshooting +- **`status` can't reach the daemon** (`control.sock: no such file`) — the wall + is running as a *different user* than your SSH session (check + `ps -o user= -p "$(pgrep -x sway)"` vs `id`). Re-run `install.sh ` + and reboot so autologin/sway/daemon all run as you. +- **Duplicate windows / one stream floating loose** — two sway sessions are + running (from a getty restart). `sudo reboot` for one clean session. - **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. + `mpv --rtsp-transport=tcp 'rtsps://HOST:7441/ALIAS?enableSrtp'`. If it fails, + RTSP isn't enabled for that camera (`discover --enable-rtsp=high,low`). +- **A tile's video is cut off / overflows** — should not happen (mpv is told + `--keepaspect-window=no` and the daemon re-squares tiles every second); if it + does, confirm `rtsp-streamer version` is a recent build. +- **`login failed` / `403 settings:edit` on enable** — use a *local* Protect + account; enabling RTSP needs an **admin** account (view-only 403s). +- **`swaymsg not found` / windows not placed** — the daemon must run inside the + sway session; check `echo $SWAYSOCK`. +- **Build error `package log/slog is not in GOROOT`** — you're on Debian's Go + 1.19; install Go 1.23 into `~/.local/go` and prepend it to `PATH` (see Build). ## Caveats & scope @@ -198,19 +252,28 @@ grids (3x3+), which greatly cuts Pi decode load. 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). + +## Roadmap + +- **Import views from the UniFi Protect UI** — read Protect's pre-made + multi-camera views/layouts and copy them into rtsp-streamer layouts, so you + don't rebuild an arrangement you already made in Protect. +- Layout rotation / cycling on a timer (the daemon already re-tiles on demand). +- TUI create / delete / rename layouts (today the TUI only *edits* existing + ones; new layouts are added in YAML). +- `rtsp-streamer reload` CLI (so hand-edits apply live without the TUI or a + restart); optional git tags so `version` shows a release rather than a hash. ## 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) +cmd/rtsp-streamer/ CLI (cobra): daemon, discover, layout, status, tui, config, version +internal/config/ YAML load/save/validate, schema (tiles, streams), XDG paths +internal/protect/ UniFi Protect client (login, bootstrap, enable RTSP, URLs) internal/player/ one supervised mpv per stream, JSON IPC, restart/backoff -internal/compositor/ sway control + NxM grid geometry +internal/compositor/ sway control + tile geometry internal/daemon/ orchestrator + control socket + health loop internal/ipc/ control-socket protocol shared by daemon and CLI/TUI -internal/tui/ Bubble Tea configurator +internal/tui/ Bubble Tea configurator (grid editor, mouse, quality) deploy/ sway kiosk config, autologin, install.sh ```