A tiny Windows tray app that keeps every MediaMTX camera feed warm (decoding constantly, at near-zero CPU via GPU decode) so showing one fullscreen is instant - no RTSP re-open, no keyframe wait. Feeds can be triggered from the tray menu, a hotkey, or any other program.
It embeds libmpv with the exact low-latency options we tuned:
profile=low-latency, hwdec=d3d11va, analyzeduration=0.1, fflags=+nobuffer,
cache=no, audio=no, untimed=yes.
- .NET 8 SDK (Windows): https://dotnet.microsoft.com/download
- libmpv-2.dll (64-bit). This is not the same as
mpv.exe. Get the dev/libmpv package from the mpv Windows builds (e.g. thempv-dev-x86_64-*.7zfrom the sourceforge/shinchiro builds), extractlibmpv-2.dll, and drop it in the project folder (next to the.csproj). It will be copied next to the built.exeautomatically. It is not in this repository - at ~120 MB it exceeds GitHub's 100 MB file limit, so it's.gitignored and you must download it yourself (one time).
Nothing is hardcoded. Cameras live in cameras.json next to the exe, but you
normally manage them inside the app: tray icon → Settings… opens a
manager where you can add, edit, and remove feeds, Test a stream before
saving, then Save & Apply to re-warm everything live (no restart).
There are no cameras by default. On first run the app writes an empty
cameras.json and starts with just the tray icon - open Settings… and add
your first feed.
The full file looks like this (all fields optional except cameras):
{
"controlPort": 8899,
"exitKeys": [ "Escape" ],
"controllerButtons": [ "B", "Back", "Start" ],
"mpvOptions": {},
"cameras": [
{
"name": "door",
"url": "rtsp://127.0.0.1:8554/camera1",
"audio": false,
"options": { }
}
]
}name- what you use from the menu and the control commands.- Since the app runs on the MediaMTX host,
127.0.0.1is the best address. exitKeys- one or more keys that dismiss the current view. Any WinForms key name works (Escape,Q,Space,Backspace,F1, …). Add as many as you like.controllerButtons- gamepad (XInput) buttons that dismiss the view:A B X Y Start Back LB RB LThumb RThumb Up Down Left Right. Leave empty to disable the controller.mpvOptions- override or add any mpv option on top of the tuned defaults, e.g.{ "hwdec": "auto", "rtsp-transport": "udp" }. See the "Tuning" note below.options(per camera) - the same, but for one camera only; it wins over the globalmpvOptions. Editable in Settings… via the "Selected camera - mpv options" box (pick the camera, edit, Update, Save & Apply), or here in JSON.audio(per camera) - settrueto play this camera's sound, but only while it's the single fullscreen feed. It stays muted while warm and on the wall, and every other feed stays silent. Off by default (feeds are video-only). In Settings… it's the "Play audio (solo view)" checkbox. An audio feed uses normal timing (not the low-latencyuntimedpath), so it opens with a little more buffering than a silent one.
cd Glance
dotnet build -c Release
# put libmpv-2.dll next to the exe if the build didn't copy it:
# bin\Release\net8.0-windows\
.\bin\Release\net8.0-windows\Glance.exeThe app starts minimized to the system tray and immediately begins streaming all cameras off-screen (warm). Nothing shows on screen until you ask for a feed.
- Show wall - tray menu → Show wall (or
Glance.exe show wall) tiles every feed across the monitor your mouse is on. Pick a feed by clicking its tile; it opens fullscreen. A dismiss key returns to the wall, and again hides everything. - Single feed - tray menu → Show '' puts one feed fullscreen on the monitor your mouse is on. A dismiss key hides it. (Showing another swaps instantly.)
- Dismissing - the keys in
exitKeysand the buttons incontrollerButtonsboth dismiss the current view (default:Escape, and gamepadB/Back/Start). These are active only while a feed or the wall is on screen; nothing is captured otherwise. Edit them in Settings… - the dismiss-keys field has a Capture key button, and controller buttons + mpv options are there too. - Tray menu Hide hides whatever is on screen (feeds stay warm in the background).
- Tray menu Start with Windows - toggle to launch Glance automatically at login. It starts silently in the tray with nothing on screen (per-user, no admin needed).
- Tray menu Exit shuts everything down.
Feeds are never re-opened - the wall and every fullscreen view use the same warm mpv instances, so switching is instant.
The defaults are tuned for low-latency RTSP over a LAN
(hwdec=d3d11va, analyzeduration=0.1, cache=no, audio=no, etc.). You don't
need to touch them, but any can be overridden in the Settings… window (the
“mpv options” box) or in cameras.json via mpvOptions, or per camera via a
camera's options. Useful ones: hwdec (d3d11va / auto / no),
rtsp-transport (tcp / udp), demuxer-lavf-analyzeduration, profile. An
override wins over the matching default; an unknown or misspelled option is
ignored by mpv, so a typo just falls back to the default behaviour.
Adding cameras: tray → Settings… → type a Name and RTSP URL → Test stream (headlessly confirms real video arrives, ~6s max) → Add (or select a row and Update) → Save & Apply. The new feed starts warming immediately.
All three of these talk to the already-running instance over 127.0.0.1:<controlPort>:
Run the exe again with arguments (works from any language via a process call):
Glance.exe show wall
Glance.exe show camera1
Glance.exe toggle camera2
Glance.exe hide
Glance.exe listHTTP GET (browser, curl, anything):
http://127.0.0.1:8899/show/wall
http://127.0.0.1:8899/show/camera1
http://127.0.0.1:8899/toggle/camera2
http://127.0.0.1:8899/hide
http://127.0.0.1:8899/list
Raw TCP line (send a line like show camera1\n to 127.0.0.1:8899).
Commands: show wall, show <name|index>, toggle <name|index>, hide, list, exit.
Example from another C# app:
System.Diagnostics.Process.Start("Glance.exe", "show camera1");Example from Python:
import urllib.request
urllib.request.urlopen("http://127.0.0.1:8899/show/camera1")- Warmth: windows are parked off-screen while decoding, so the RTSP session and decoder stay live. Switching cameras never pays the ~2-4 s cold-start you saw opening a fresh stream.
- Resources: each feed is one
d3d11va(GPU) decode. Two 1080p Tapo feeds is a couple percent CPU and a little VRAM. Add cameras freely. - Multiple monitors: a feed opens on the monitor where the cursor is. To pin
a camera to a fixed monitor, that's a small change in
ShowCamera- ask and I'll add per-camera monitor config tocameras.json. - Autostart: drop a shortcut to the exe in
shell:startupto launch it warm at login. - Control port is loopback-only (
127.0.0.1), so it isn't exposed on the network.
| File | Purpose |
|---|---|
MpvPlayer.cs |
libmpv P/Invoke wrapper + the tuned low-latency options |
CameraForm.cs |
One borderless fullscreen window per camera; warm off-screen |
TrayController.cs |
Tray icon, menu, hotkey, and the TCP/HTTP/CLI control channel |
Config.cs |
Loads / saves cameras.json |
SettingsForm.cs |
Add/edit/remove cameras + Test stream |
Program.cs |
Entry point, single-instance, command forwarding |
ControllerWatcher.cs |
Polls XInput gamepad buttons (wall navigation + dismiss) |
StartupManager.cs |
"Start with Windows" toggle (HKCU Run entry) |
cameras.json |
Your camera list + control port (not committed; created on first run) |
Glance links libmpv dynamically at runtime. libmpv/mpv ships under its own
(L)GPL license and is not included in this repository - download it yourself
(see Prerequisites). If you're publishing the Glance source, add your own
LICENSE file to set usage terms.