mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-09-21 10:05:38 -04:00
cycleStart() took a fresh setInterval id straight into cycleIntervalId without clearing what was already there. Once overwritten the old id is unrecoverable, so the orphan keeps calling nextCycleView every second and cyclePause() can only ever stop the last one armed. Several callers reach cycleStart() with no cyclePause() in between: the play button, the are-you-still-watching modal closing, and startPage(). That last one is the routine path - it runs from visibilitychange, from resume and from pageshow, and a restore fires more than one of those, so a tab coming back while cycling was active armed two. The monitor restart in the same function is protected, since it nulls prevStateStarted on the way through, but the cycle branch below it never cleared prevStateCycle. Clear the interval at the top of cycleStart(), which makes every caller safe whatever order they arrive in, and clear prevStateCycle in startPage() the way prevStateStarted already is. cycle.js has the same shape in its own cycleStart() behind a play button, so it gets the same guard. Tests in tests/js/watch-cycle-interval.test.js drive watch.js under stubbed timers and count what is left running: two starts leave one interval, a pause after three starts leaves none, and a second startPage() does not re-arm. Three of the four fail against the unfixed file. Full JS suite passes, ESLint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UkQwahn9pi1y4wJe9BTxjM