When a console or stream tab is backgrounded or the machine sleeps past the
auth hash TTL, the baked-in auth= on nph-zms <img> URLs expires. The browser
keeps reconnecting with the stale hash, producing a burst of 403s from zms
while the session-backed page still renders.
Detect the tab becoming visible again (visibilitychange/pageshow) and probe
auth once against the navBar status endpoint before letting streams reconnect:
on success refresh the global auth_hash and repaint; on a dead session (401)
go straight to login instead of retrying. Route the navBar poll, console table
query, and per-stream error paths through a shared decision so 401/403 ends in
a single login redirect rather than a retry storm.
Put the auth functions (goToLogin, revalidateAuth, onAuthVisible) and the pure
authFailureAction/loginRedirectUrl helpers in web/js/auth-helpers.js as named
globals; skin.js only wires the visibility listeners. Node unit tests cover the
pure helpers (tests/js/auth-helpers.test.js).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- playerPriority is now a stream object, not a global constant.
- To assign an error listener, use this.handlerEventListener['errorStream'] instead of the erroneous this.handlerEventListener['playStream'] and clear it when the stream stops.
- New algorithm for iterating over players in selectNextPlayer()
- For Janus, use monitorStream.streamErrorRegistration() instead of the erroneous this.streamErrorRegistration()
This PR doesn't fix the root cause of the issue, but addresses the consequences.
I spent time investigating the problem, but I couldn't figure out the root cause because it's difficult to debug.
Perhaps I'll revisit this issue later.
For now, let's leave it at that.
zms returns 403 on a stale auth hash (default TTL 2h), but the live
<img src=nph-zms?...> keeps the original src and reconnects forever
with the expired hash, generating one zms warning per retry until the
user reloads the page.
On img_onerror, fetch a fresh auth hash from the existing navBar
status endpoint, splice it into the img src, and reconnect. Capped at
3 attempts with 2s/4s/8s backoff so a genuinely-down camera doesn't
loop indefinitely. img_onload resets the attempt counter on success.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When CMD_STOP is sent via AJAX using ZMS MJPEG streaming, the response
was incorrectly returning paused=true instead of indicating a stopped
state (paused=false).
Changes:
- Add `stopped` boolean to StreamBase (zm_stream.h)
- MonitorStream: CMD_STOP now sets stopped=true, paused=false instead
of paused=true; run loop skips frame sending when stopped
- EventStream: CMD_STOP sets stopped=true (was already setting
paused=false); run loop skips frame sending when stopped
- All other play/pause commands reset stopped=false
- Both streams include stopped field in the status response struct
- stream.php unpacks the new stopped field from MSG_DATA_WATCH and
MSG_DATA_EVENT responses
- MonitorStream.js handles stopped status in UI (shows 'Stopped' mode)
- EventStream.js tracks stopped state from server response
Fixes issue: CMD_STOP response paused=true should be paused=false
Agent-Logs-Url: https://github.com/ZoneMinder/zoneminder/sessions/ba9cb47a-a3e8-4e13-aec7-c9cd258e2a3d
Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
When auth is disabled or auth_relay is empty, appending '&'+auth_relay
produces a trailing '&' which results in double '&&' when the next
parameter is appended (e.g. ?monitor=2&&scale=41&mode=single).
Guard all 4 places in MonitorStream.js where auth_relay is concatenated
into URLs, consistent with EventStream.js which already guards this.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Clear IconMute when executing destroyVolumeSlider()
- Added the ability to clear the icon state to changeStateIconMute()
MonitorStream.js
- Set #volumeControls to "disabled" instead of "hidden"
- Don't execute controlMute() when executing createVolumeSlider()
Added:
audioMotionAnalyzer.js
- Add user= parameter to get_auth_relay() so zms can use the indexed
Username column instead of iterating all users to validate the hash
- Apply the same fix to Event.php getStreamSrc() and getThumbnailSrc()
- Tighten Monitor.php from isset() to !empty() for consistency
- In MonitorStream.js start(), check if the auth hash in the img src
matches the current auth_hash before resuming via CMD_PLAY. If stale,
fall through to rebuild the URL with fresh auth_relay. This prevents
long-running montage pages from spawning zms with expired credentials.
- Downgrade zms auth failure from Error to Warning
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- getAudioStream has been renamed to getAVStream
- Clear this.handlerEventListener['volumechange'] when the stream is stopped
- Check volumeSlider.noUiSlider after assigning the listener
- This.handlerEventListenerStream does not require an argument (stream), but if we have already received a stream, we will pass it as an argument to avoid searching for the stream again.
When zms reports it is not sending analysis frames (e.g. monitor is not
analysing), update the analyse button appearance and MonitorStream's
analyse_frames state to reflect reality. Register the analyseBtn via
setButton in both watch and zone views so MonitorStream can update it.
View-level toggle handlers now read the current state from MonitorStream
before toggling to stay in sync with server-reported changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>