Observation Deck Server
The Windows half of a one-household Plex replacement: it indexes a movie and TV folder and serves the API, artwork, subtitles and video stream.
What it does
- Scans a Movies folder and a TV folder, reading Kodi-style
.nfofiles for titles, plots, ratings and posters, probing each file withffprobeand keeping a SQLite library. - Plays what the Chromecast can play directly, with range requests. Anything else is remuxed on the fly by
ffmpegto fragmented MP4 with stereo AAC, transcoding video only when it has to. - Serves WebVTT subtitles, converted from embedded text tracks and external
.srtfiles and cached. - Remembers watched state and resume positions, reported by the receiver or the app's own player. Library responses include recently added movies and shows.
- Tidies titles: release-style file names are cleaned to a title and year, and a title with no cover art gets a drawn poster.
- Runs as a tray app with a settings window and a tray menu (open settings, rescan, copy the address or token, start with Windows). A dot on the icon shows playing, scanning or stopped, and it advertises itself over mDNS (
_observationdeck._tcp) so the Android app can list it.


Screenshots use a made-up library of generated test clips and artwork. Conan the Barbarian (1982) stands in as the film being played; the artwork and frames are drawn for the page, not taken from the film.
Under the hood
- C# on .NET 10: a Core library (scanner, NFO parser, title cleaner, ffprobe/ffmpeg wrapper, SQLite, playback planner) and a Windows server project (ASP.NET Core Kestrel endpoints, tray, settings window).
- A playback planner encodes the findings of the feasibility spike: stereo AAC only, HEVC retagged
hvc1, Matroska never played directly. It also falls back to a transcode when a file's video timestamps are in decode order, which would stutter if copied. - Piped fragmented MP4 cannot seek, so seeking restarts the stream at an offset and the receiver adds the offset back to the displayed time.
- HTTPS uses a hostname whose DNS record points at the PC's private LAN address. A Let's Encrypt certificate is obtained and renewed by ACME DNS-01 through the Cloudflare API, and hot-swapped on renewal. The Cloudflare token is stored DPAPI-encrypted.
- For use away from home it can add a second hostname on the same certificate, with a connection guard in front of that port: a country allow-list, strikes for bad or missing tokens, escalating temporary blocks, tarpitted failures, tight Kestrel limits and no
Serverheader. Every API call except/pingneeds an access token. - If Windows holds the mDNS port exclusively, the server cannot hear queries, so it re-announces periodically instead of failing to start.
- Tested with xUnit across the planner, library, probe, title cleaner, posters, mDNS, certificates, secrets, connection guard and country filter.
The server is for a personal library and is not meant to be shared.