Commit Graph
2 Commits
Author SHA1 Message Date
dtourolle 954546434a docs(specs): record what shipped, and where the design bent
DR-242 … DR-247 are in. The spec now says so rather than reading as a proposal
for work that already exists.

One deviation is recorded rather than quietly absorbed: DR-246 called for the
engines to own seek strategy outright, and they cannot — re-negotiating a
stream needs the repository, which sits above them. The engine declares the
ability and the caller acts on it. `determine_video_seek_strategy` therefore
survives, correctly typed over a declared capability instead of over a guess,
because the defect was its input rather than its existence.
2026-08-22 22:23:20 +02:00
dtourolle f388777185 docs(specs): one player contract, three interchangeable engines
A day of debugging Linux native video produced four defects (DR-238 … DR-241)
and one regression from fixing them in the wrong place. None of them were mpv
bugs. All four trace to the same missing seam.

`PlayerBackend` abstracts a *device* — load, then seek — rather than an
*intent*. A start position is therefore not expressible, so every caller
sequences load-then-seek itself and each races the engine's asynchronous load
independently. That is why resume worked through the adapter, which seeks
after "file loaded", and silently failed through the command, which seeks
immediately: two callers, one intent, two behaviours.

The same gap put transport rules above the engines. Whether a stream can be
seeked in place was decided by a truth table in a command handler, on behalf
of engines it does not own, which is how `use_html5` came to mean both "who
renders" and "how do I seek". And nothing in the contract obliged an engine to
report its own state, so a handler for mpv's `pause` property sat unreachable
while the UI waited for an event that never came.

Supporting evidence for the diagnosis: commands/player/mod.rs is 3,561 lines
and is where "stop → rebuild URL → update queue → load → seek" lives;
player_play_item needed a cfg(not(linux)) guard; and the frontend carries
didStartNativePlayback, didStopBackendEarly and hasPerformedInitialSeek —
playback state in the UI, which contradicts the one-directional rule.

The proposal is a MediaPlayer contract whose `open` carries the start
position, whose `seek` states a destination and leaves in-place-versus-re-open
to the engine, whose `snapshot` is one coherent read, and whose `Phase`
includes `Opening` — the state the previous design could not express and the
window a seek was lost in.

Testability is the half that makes it worth doing: one conformance suite run
against every engine, and a FakePlayer that lets the controller, queue,
autoplay and session logic be tested with no engine at all. The suite is
written before the second engine on purpose, so it cannot encode whatever the
first happened to do.

Migration is a strangler in eight steps; the first three are pure addition.
2026-08-22 21:17:50 +02:00