Skip to content

Repository files navigation

Glance

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.


1. Prerequisites

  • .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. the mpv-dev-x86_64-*.7z from the sourceforge/shinchiro builds), extract libmpv-2.dll, and drop it in the project folder (next to the .csproj). It will be copied next to the built .exe automatically. 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).

2. Configure your cameras

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.1 is 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 global mpvOptions. Editable in Settings… via the "Selected camera - mpv options" box (pick the camera, edit, Update, Save & Apply), or here in JSON.
  • audio (per camera) - set true to 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-latency untimed path), so it opens with a little more buffering than a silent one.

3. Build & run

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

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

4. Using it

  • 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 exitKeys and the buttons in controllerButtons both dismiss the current view (default: Escape, and gamepad B/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.

Tuning (mpvOptions)

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.

5. Controlling it from another program

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 list

HTTP 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")

Notes & tuning

  • 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 to cameras.json.
  • Autostart: drop a shortcut to the exe in shell:startup to launch it warm at login.
  • Control port is loopback-only (127.0.0.1), so it isn't exposed on the network.

File map

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)

License & third-party

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages