"Implement observed properties: duration, time, pause, tracks, chapters, cache, and errors."
The Shadow-State Temptation
When you wrap an engine, the obvious move is to keep your own variables: var isPlaying, var currentTime, var duration. You flip them when you send a command, the UI binds to them, done. And it works — until the engine and your shadow disagree. The user pauses via a media key the engine handled directly; the file ends on its own; a seek clamps to a shorter duration than you assumed. Now your variables say one thing and the video does another, and there is no bug more maddening than a UI that lies.
Subscribe to the Engine Instead
libmpv offers a better contract: observed properties. You tell it which values you care about — time-pos, pause, duration, track lists, chapters, cache, errors — and it pushes you a change event whenever the engine's value actually changes. Your UI stops being a set of variables you flip and becomes a mirror of what the engine reports.
The subscription is small, and it inverts the direction of trust — you stop pushing your guess and start receiving the engine's fact:
Why This Scales to Everything Hard
Once the pattern is in place, the hard cases get easy. Media keys? The engine changed pause, you observed it, the UI updated — you didn't even have to route the key yourself. File ended? time-pos stopped and an end event fired. A seek landed short? time-pos reports where it actually is, not where you asked. You never write reconciliation code, because you never had two copies to reconcile. That's the quiet superpower of observing instead of shadowing: the drift bugs simply don't have a place to live.