The first cut used a fixed 1.05x drain, which measurement showed was too
gentle to matter: clearing a 6 s backlog would take two minutes, which a
viewer experiences as still broken.
Two changes, both measured on the netem satellite rig (loopback
impairment, gst -> ffmpeg HTTP relay -> mpv, matching the http:// URL
production actually serves):
1. Proportional drain. Speed now scales with buffer depth,
1 + 0.05*(cache - 0.5), clamped to 1.15x, keeping the hysteresis band
so it cannot oscillate. Deep backlogs recover in tens of seconds;
small excursions still get an inaudible nudge.
2. Bound the byte cache in Low latency. The demuxer cache is a *byte*
budget, so at a given bitrate it sets the worst-case backlog: 2 MiB
held ~6 s of a 2.5 Mbps share. Capping Low latency at 1 MiB halved the
standing buffer, 6.0 s -> 2.8 s, on its own. Smooth keeps the user's
value, since a deep buffer is that posture's whole point.
Measured effect with both: playback consumes 11.6% faster than realtime
while behind (ratio 1.1157 vs 0.9988 with catch-up off), i.e. ~9 s of
backlog cleared in 80 s where before it recovered nothing at all and the
viewer stayed behind for the rest of the call.
Rig caveat: its upstream queues hold an unbounded backlog, so the cache
never drops back through the low mark and the return-to-1x transition is
only covered by unit tests, not the rig.
601 lib tests green, clippy clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On a lossy link the reliable PixelPass transport turns every loss burst
into buffered latency that nothing trims back, so the viewer settles
seconds behind the host and stays there. Measured on a tc netem satellite
simulation: a viewer parks at a ~6 s standing buffer indefinitely.
--untimed (0.6.5) does NOT fix this and measured marginally worse (+1.38 s
vs +1.24 s): it only unpaces presentation, while audio still drains at 1x
the DAC rate, so an accumulated backlog never shrinks. Drop it.
Instead give mpv a JSON IPC socket in the Low latency posture and drive
playback slightly fast while the buffer is deep, returning to 1x once it
drains. Pitch correction keeps it inaudible and A/V sync is preserved,
because audio and video speed up together.
The control law and IPC message handling are pure functions with unit
tests; the only I/O is livesync::drive, which ends by itself when the
player exits. Smooth is deliberately excluded — its ~2 s readahead is the
point of that posture, and catch-up would fight it every poll.
Known limitation: 1.05x needs ~120 s to clear a 6 s backlog, so recovery
is slower than ideal. Tuning (a proportional law, or a seek-to-live for
large backlogs) is the follow-up.
598 lib tests green (+11), clippy clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>