Files
siftlode/frontend
peter 8a83bc0e9f fix(plex): a finished item is finished when the server says so, and a lost session is reclaimed a bounded number of times
Deep-review round 5, over round 4's own fixes.

- Round 4 added a `finish` latch so a played-through item records no checkpoint at the credits.
  On a NATURAL play-through it was raised a beat too late: per the HTML spec the browser sets
  `paused` and fires 'pause' BEFORE 'ended', so the pause checkpoint went out while the latch was
  still down and raced the watched mark — both requests read "not watched yet" from their own
  snapshot, so Plex counted one watch twice, and where Plex's reported duration runs more than the
  finish margin past the real media the checkpoint wrote a resume position AFTER the scrobble. The
  pause that ends the media no longer flushes. The regression test that was meant to cover this
  modelled the end as a bare 'ended' event — a browser that does not exist — and is now the real
  order.

- The watched mark was fired and forgotten (`.catch(() => {})`) while the latch patched the cached
  detail to watched / position 0 regardless, and round 4's once-per-id start guard meant the
  corrected detail arriving later was never used: an episode whose mark failed reopened at 0:00
  with a Watched badge the server disagreed with. `finish` now takes the in-flight write and takes
  itself back if the server refuses it — the real position is reported, the lying cache entry is
  dropped, and the failure is said out loud instead of swallowed. Both callers (the natural end
  and the credits auto-skip) hand it over.

- The reclaim of a 404'd HLS session had no bound. `parsed` cannot provide one — it belongs to one
  hls instance and every restart builds a fresh one — and the server answers 404 both for "the
  session is gone" and for "that segment is not there yet", so a transcode falling behind realtime,
  or two viewers of one item taking turns, respawned ffmpeg forever behind a spinner that never
  became an error. Two consecutive reclaims, reset by the first fragment that actually loads, then
  the honest failure.

Five regression tests, each mutation-checked red on the assertion it is named after.
2026-09-12 03:47:42 +02:00
..