Under the bonnet
Boring technology, chosen deliberately
Screens outlive software fashions. Everything here was picked to still be maintainable, hostable and hirable-for in a decade.
| Layer | Built with | Why |
|---|---|---|
| Server | PHP 8.2 · Laravel | Runs on anything, including the cheapest shared hosting. Fifteen years of hosting compatibility is a feature, not a compromise. |
| Database | SQLite, MySQL or PostgreSQL | Start on SQLite with no server to install; move to MySQL with four lines of configuration when the fleet grows. |
| Admin panel | React · TypeScript · Tailwind | A single-page app served from the same origin as the API, so there is no cross-origin session to get wrong. |
| Android player | Kotlin · Media3 · Room | A native app rather than a wrapped web view: it can hold a foreground service, survive a reboot, and draw over other applications. |
| Web player | React · Cache API · PWA | The same layouts as the native player, sharing one set of rendering code so a card cannot look different on two screens. |
| Realtime | WebSocket, with polling as a first-class fallback | Corporate networks drop sockets. Polling is a supported delivery path here rather than a degraded one, which is why it works on hosting that has no socket at all. |
Design decisions
Three choices worth explaining
Polling is not a fallback we are embarrassed by. A great many office networks drop WebSockets, and a great many cheap hosting plans cannot run one at all. So the polling path is tested, measured and documented rather than bolted on — which is why the product works properly on a five-dollar host.
One renderer, two players. The announcement layouts are written once and drawn by both the web and the Android player. It would have been faster to write them twice, and they would have drifted within a month.
Content is addressed by checksum. A screen downloads a given file exactly once, however many playlists reference it, and knows without asking whether it already holds what a new plan needs. That is what makes a plan change instant on a screen that already has the media.
Operationally
Things that matter at three in the morning
A health page that tells the truth
- Database, cache, storage, queue, scheduler and broadcaster
- Says which scheduling mode is actually in use
- Warns about a missing broadcaster rather than reporting a healthy one
Failures that are visible
- A delivery report per screen, per alert
- Screens that cannot cover other apps are flagged
- An offline screen is a state in the panel, not a discovery
Nothing dangerous exposed
- No operating-system commands from the panel
- Player tokens stored only as hashes
- Configuration kept outside the web root
Want to see the architecture?
We are happy to walk a technical team through the whole thing before anyone signs anything.
No open demo, no sign-up wall — a person reads every request.

