Fix invisible/flashing clock, map Protect's 8-camera preset, warn on gaps
Three separate faults made cameras "not load" and the clock misbehave. Clock rendered as nothing, or flashed ~200ms/second. The time was drawn as an ASS osd-overlay pushed over mpv IPC, but on mpv 0.35 + Mesa/V3D + sway an OSD overlay is rendered only on the frame where its content *changes*. Every layer reports success while this happens (mpv returns error:success, vo-configured is true, and sway reports the window visible at the right rect), so it looks like a stacking or font bug and is neither. Ruled out: pushing at 20Hz (identical content is ignored, so it still only redrew when the second flipped), osd-msg1, show-text, and --pause (mpv stops redrawing entirely). Fonts were never the issue. The time is now baked into every frame by a drawtext filter re-reading a small file the ticker rewrites once a second, with two constraints that cost real time to find and are pinned by tests: - The canvas alpha must be > 0. A fully transparent canvas (black@0.0 with --alpha=yes) makes the glyphs inherit alpha 0 and the compositor draws nothing -- this was the original invisible clock. New clock background_opacity (default 0.45) is clamped in config *and* in args() so no code path can produce an invisible clock. - Readahead must be off. drawtext stamps the time when a frame is *generated*, so buffering ahead makes the visible clock lag by the readahead and swallows text-file updates entirely. Since the text now arrives through a file, the clock needs no IPC socket: dropped --input-ipc-server, the ipcPath field, and the stale-socket removal. assEscape goes with the ASS path. `views import` produced layouts with holes. viewmap derived the grid from the slot count alone and ignored Protect's `layout` field, so Protect's asymmetric 8-camera preset (four 2x2 tiles plus a right column of four 1x1) landed as 8 tiles in a 3x3 grid -- the bottom-right cell was simply empty and rendered as a blank rectangle. That preset is now mapped exactly; other counts keep the uniform GridForSlots fallback rather than guessing at presets I have not observed. Import also warns when a mapping would leave empty cells or references a camera missing from the config, so a silent hole cannot reach the screen again. Also: - clock.corner gains bottom-center and top-center (centered horizontally, Margin still applies vertically). - placeClock no longer re-issues `resize set` every tick. Re-asserting geometry on a correctly-sized window makes sway send a configure event, which makes mpv reallocate buffers and blank for a frame. New compositor.Raise re-asserts z-order only, which is all the 2s tick needs; geometry is re-placed only when it has actually drifted. - README documents why the clock is drawn this way, the preset table and how to add another from `views dump`, and three troubleshooting entries for failure modes that all look like bugs: a blank tile whose mpv is running (a stale camera entry -- re-adopting a camera in Protect assigns a new id and often a slightly different name, and `discover` never prunes), cameras in `cameras:` not being on screen (only the active layout's tiles stream), and black bars inside tiles (non-16:9 grid cells; --panscan=1.0 crops to fill instead). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LYhTnkp7VzJ67THeicgfAQ
This commit is contained in:
96
README.md
96
README.md
@@ -157,24 +157,53 @@ saving an unrelated edit never blanks the screen.
|
||||
|
||||
## Clock overlay
|
||||
|
||||
An optional always-on-top clock can be rendered in a screen corner:
|
||||
An optional always-on-top clock can be rendered at a screen edge:
|
||||
|
||||
```yaml
|
||||
clock:
|
||||
enabled: true
|
||||
timezone: America/Denver # IANA name; "Local" uses the system zone. DST auto.
|
||||
format: "15:04:05" # Go time layout (24-hour w/ seconds)
|
||||
corner: bottom-right # bottom-right | bottom-left | top-right | top-left
|
||||
corner: bottom-center # bottom-right | bottom-left | top-right | top-left
|
||||
# | bottom-center | top-center
|
||||
background_opacity: 0.45 # alpha of the backing box; must be > 0 (see below)
|
||||
```
|
||||
|
||||
It's a tiny transparent mpv window (no extra dependencies) drawing the time as
|
||||
**white text with a black outline**, so it stays readable over both bright (day)
|
||||
and dark (night) camera scenes without measuring the picture. The time is
|
||||
formatted in the configured timezone via Go's zone database, so it's correct
|
||||
regardless of the host clock's zone and handles DST on its own. The daemon keeps
|
||||
it positioned and on top across layout switches. Size (`width`/`height`),
|
||||
`font_size`, and edge `margin` are configurable; enable/disable or retune it
|
||||
live with an edit plus `rtsp-streamer reload`.
|
||||
It's a tiny mpv window (no extra dependencies) drawing the time as **white text
|
||||
with a black outline** over a dimmed backing box, so it stays readable over both
|
||||
bright (day) and dark (night) camera scenes without measuring the picture. The
|
||||
time is formatted in the configured timezone via Go's zone database, so it's
|
||||
correct regardless of the host clock's zone and handles DST on its own. The
|
||||
daemon keeps it positioned and on top across layout switches. Size
|
||||
(`width`/`height`), `font_size`, and edge `margin` are configurable;
|
||||
enable/disable or retune it live with an edit plus `rtsp-streamer reload`.
|
||||
|
||||
The `*-center` positions center the window horizontally and ignore `margin` on
|
||||
that axis; `margin` still applies vertically.
|
||||
|
||||
### Why the clock is drawn the way it is
|
||||
|
||||
The time is baked into every video frame by an ffmpeg `drawtext` filter that
|
||||
re-reads a small text file (`$XDG_RUNTIME_DIR/rtsp-streamer/clock-text.txt`),
|
||||
which the daemon rewrites once a second. That looks roundabout — pushing an ASS
|
||||
`osd-overlay` over mpv's IPC would be the obvious approach — but on mpv 0.35 +
|
||||
Mesa/V3D + sway an OSD overlay is rendered **only on the frame where its content
|
||||
changes**, so an IPC-driven clock is visible for roughly 200ms per second and
|
||||
reads as a flashing clock. Every layer reports success while this happens
|
||||
(`error: success` from mpv, `vo-configured: true`, sway reporting the window
|
||||
visible at the right rect), so it looks like a stacking or font bug and is
|
||||
neither. Two constraints follow, and both are covered by tests:
|
||||
|
||||
- **`background_opacity` must be greater than zero.** A fully transparent canvas
|
||||
(`color=c=black@0.0` with `--alpha=yes`) makes the drawn glyphs inherit alpha
|
||||
0, and the compositor shows nothing at all. Values of `0` or below — including
|
||||
the field being absent — are clamped to the default.
|
||||
- **The canvas must not be buffered ahead** (`--cache=no`,
|
||||
`--demuxer-readahead-secs=0`). `drawtext` stamps the time when a frame is
|
||||
*generated*, so reading ahead makes the displayed clock lag by the readahead
|
||||
and swallows text-file updates entirely.
|
||||
|
||||
Because the text arrives through a file, the clock window needs no IPC socket.
|
||||
|
||||
## UniFi Protect live views
|
||||
|
||||
@@ -187,10 +216,29 @@ rtsp-streamer views import --all # import every view
|
||||
rtsp-streamer views dump # raw view JSON (for tuning odd layouts)
|
||||
```
|
||||
|
||||
Cameras are matched by Protect id, so run `discover` first. Grid size is
|
||||
inferred from the slot count (asymmetric "1 big + N" presets land as an even
|
||||
grid for now — send me `views dump` output to map exact sizing). An imported
|
||||
layout is **linked** to its view (`protect_view:` in the config).
|
||||
Cameras are matched by Protect id, so run `discover` first. An imported layout is
|
||||
**linked** to its view (`protect_view:` in the config).
|
||||
|
||||
Protect's arrangement for a view is not always an even grid. Known asymmetric
|
||||
presets are mapped exactly; any other slot count falls back to a near-square grid
|
||||
of equal tiles:
|
||||
|
||||
| Slots | Result |
|
||||
| --- | --- |
|
||||
| 8 | `5x4` — four 2x2 tiles plus a right-hand column of four 1x1 tiles |
|
||||
| anything else | near-square uniform grid (`GridForSlots`) |
|
||||
|
||||
To add another preset, run `views dump`, note the view's `layout` value and slot
|
||||
order, and add an entry to `presets` in `internal/viewmap/viewmap.go`. Slot order
|
||||
in the dump is the order tiles are filled.
|
||||
|
||||
Import **warns** whenever a mapping would leave grid cells empty (they show as
|
||||
blank rectangles on the wall) or references a camera that isn't in the config:
|
||||
|
||||
```
|
||||
! Office View: 1 of 9 cells in the 3x3 grid are empty and will show as blank areas
|
||||
! Office View: slot 4 camera 6a65…c6d not in config (run `discover`)
|
||||
```
|
||||
|
||||
### Auto-resync
|
||||
|
||||
@@ -321,6 +369,26 @@ pegged, work through:
|
||||
- **Blank/black tile** — verify the stream directly:
|
||||
`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 stays blank but its mpv process is running** — the camera entry is
|
||||
stale. Re-adopting a camera in Protect gives it a **new id and often a slightly
|
||||
different name** (`Livingroom` → `Living Room`), and `discover` only adds and
|
||||
updates, it never prunes — so the old entry lingers with an alias the controller
|
||||
no longer serves. Process counts look healthy because mpv is up and retrying.
|
||||
Diff your config's aliases against the controller:
|
||||
`curl -sk -b cookies.txt https://HOST/proxy/protect/api/bootstrap` and compare
|
||||
each camera's `channels[].rtspAlias`. Delete the dead entries, then fix every
|
||||
layout that referenced them (watch for a camera ending up twice after a rename).
|
||||
- **Cameras in `cameras:` aren't on screen** — only cameras placed as tiles in the
|
||||
**active layout** are streamed, one mpv per tile. The `cameras:` list is just an
|
||||
inventory. Check with `rtsp-streamer layout ls` (it prints each layout's tile
|
||||
count).
|
||||
- **Black bars inside every tile** — the base grid's cells aren't 16:9, so mpv
|
||||
letterboxes each feed to fit. On a 16:9 output only a *square* grid (`NxN`)
|
||||
yields 16:9 cells; e.g. a `5x4` grid on 3840x2160 gives 768x540 (1.42:1) cells.
|
||||
Some arrangements can't avoid this at all — two 16:9 tiles 1080px tall need the
|
||||
full 3840px width, leaving no room for a side column. To fill instead of pad,
|
||||
crop with `player.extra_args: ["--panscan=1.0"]`, at the cost of roughly the top
|
||||
and bottom 20% of each camera's field of view.
|
||||
- **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.
|
||||
|
||||
Reference in New Issue
Block a user