C.W.K.
Stream
Lesson 04 of 05 · published

Read Engine Truth, Don't Keep a Shadow

~12 min · embed-engine, observed-properties, state, libmpv

Level 0Reel Novice
0 XP0/39 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"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 engine is the single source of truth; your UI is a mirror, never a second copy. Don't track playback state in parallel and hope it stays in sync. Subscribe to the real thing and reflect it.

The subscription is small, and it inverts the direction of trust — you stop pushing your guess and start receiving the engine's fact:

This is the seed of the whole Observed-State track. Everything Track 4 says about seeks — pending versus applied, explicit time beating resume, controls reflecting reality — grows from this one habit: never store what you can observe, and never trust a value you set instead of one the engine confirmed.

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.

Code

Observe the engine's properties instead of shadowing them·swift
// Subscribe to the values the UI mirrors. From now on libmpv pushes a
// change event whenever the ENGINE's value changes -- not when we ask.
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE)
mpv_observe_property(mpv, 0, "pause",    MPV_FORMAT_FLAG)
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE)

// In the event loop, translate engine truth into published UI state.
func handle(_ event: UnsafePointer<mpv_event>) {
    guard event.pointee.event_id == MPV_EVENT_PROPERTY_CHANGE else { return }
    let prop = event.pointee.data.assumingMemoryBound(to: mpv_event_property.self)
    switch String(cString: prop.pointee.name) {
    case "time-pos":  publishedTime     = readDouble(prop)   // engine's now
    case "pause":     isPaused          = readFlag(prop)     // engine's state
    case "duration":  publishedDuration = readDouble(prop)   // engine's length
    default: break
    }
}
// Notice: no variable is set by a command. Every value is the engine's.

External links

Exercise

Find a place in your code where you keep a local variable that mirrors some external system's state — a connection's 'isConnected,' a download's 'progress,' a player's 'isPlaying.' Ask: what happens when that external thing changes WITHOUT going through your setter? If the answer is 'my variable goes stale,' you have a shadow. Sketch how you'd subscribe to the real value instead so your copy can't drift.
Hint
Shadow state is any variable you set yourself that's supposed to match a truth living somewhere else. The fix is always the same shape: stop setting it on the way out, start receiving it on the way in — subscribe, observe, mirror.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.