fix(venue-crowd): generation-gate stinger handlers; recrop on video size change

Codex preflight round 5: (1) an ended/timeout handler orphaned by stop()
could fire into a later stinger's lifecycle on the reused element — handlers
now detach unconditionally and carry a generation token; (2) the renderer
only re-applied cover-crop on camera aspect changes, so a src swap with a
different intrinsic size kept stale repeat/offset — it now recrops when
videoWidth/Height change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
byrongamatos
2026-07-12 01:33:12 +02:00
co-authored by Claude Fable 5
parent de10e81259
commit 9178959dbd
2 changed files with 19 additions and 2 deletions
+9 -2
View File
@@ -110,6 +110,7 @@
let _loadingLoop = null; // loop currently waiting on canplaythrough
let _fadingLoop = null; // loop currently crossfading in (not yet active)
let _stingerUntilEnded = false;
let _stingerGen = 0; // identity for ended/timeout handlers
let _lastStingerAt = -Infinity;
let _prevStreak = 0;
let _lastAccuracyPct = null; // from perf events; stats:recorded carries none
@@ -276,10 +277,15 @@
_pendingLoop = null;
showLoop(pending, FADE_MS);
};
const myGen = ++_stingerGen;
const back = () => {
if (!_stingerUntilEnded) return;
_stingerUntilEnded = false;
// Always detach: a handler left behind by a stop()/manifest swap
// must not fire into a LATER stinger's lifecycle on this reused
// element (the gen check below guards that; the boolean alone
// would pass once a new stinger is active).
video.removeEventListener('ended', back);
if (_stingerGen !== myGen || !_stingerUntilEnded) return;
_stingerUntilEnded = false;
// Fade back to the loop layer (which kept playing underneath).
fadeMixTo(_activeLayer === 1 ? 1 : 0, STINGER_FADE_MS);
flushPending();
@@ -344,6 +350,7 @@
function stop() {
cancelFade();
_stopGen++;
_stingerGen++;
_stingerUntilEnded = false;
_pendingLoop = null;
_loadingLoop = null;