From 4df2bf4f0b4c6f2eeacc3f55b63f07669a98f06b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 8 Mar 2026 15:44:49 -0400 Subject: [PATCH] feat: add analysis_image field to stream status for client-side verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a boolean analysis_image field to the CMD_QUERY status response that reports whether zms is actually sending analysis images (with motion zone overlays) or regular capture images. This lets MonitorStream.js detect when the stream state is out of sync with what the client requested and re-send the CMD_ANALYZE_ON/OFF command to correct it. The field is true only when frame_type is FRAME_ANALYSIS, shared memory is valid, and the monitor has analysis enabled — matching the same condition used to select the image in the streaming loop. Co-Authored-By: Claude Opus 4.6 --- src/zm_monitorstream.cpp | 9 +++++++-- web/ajax/stream.php | 2 +- web/js/MonitorStream.js | 10 ++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index f153a6ef3..59a01911d 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -255,6 +255,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { bool forced; int score; int analysing; + bool analysis_image; } status_data; status_data.id = monitor->Id(); @@ -302,7 +303,10 @@ void MonitorStream::processCommand(const CmdMsg *msg) { status_data.delay = FPSeconds(now - last_frame_sent).count(); status_data.zoom = zoom; status_data.scale = scale; - Debug(2, "viewing fps: %.2f capture_fps: %.2f analysis_fps: %.2f Buffer Level:%d, Delayed:%d, Paused:%d, Rate:%d, delay:%.3f, Zoom:%d, Enabled:%d Forced:%d score: %d", + status_data.analysis_image = (frame_type == FRAME_ANALYSIS) && + monitor->ShmValid() && + (monitor->Analysing() != Monitor::ANALYSING_NONE); + Debug(2, "viewing fps: %.2f capture_fps: %.2f analysis_fps: %.2f Buffer Level:%d, Delayed:%d, Paused:%d, Rate:%d, delay:%.3f, Zoom:%d, Enabled:%d Forced:%d score: %d analysis_image: %d", status_data.fps, status_data.capture_fps, status_data.analysis_fps, @@ -314,7 +318,8 @@ void MonitorStream::processCommand(const CmdMsg *msg) { status_data.zoom, status_data.enabled, status_data.forced, - status_data.score + status_data.score, + status_data.analysis_image ); DataMsg status_msg; diff --git a/web/ajax/stream.php b/web/ajax/stream.php index fd4ffb1d3..6f8c77593 100644 --- a/web/ajax/stream.php +++ b/web/ajax/stream.php @@ -151,7 +151,7 @@ default : $data = unpack('ltype', $msg); switch ( $data['type'] ) { case MSG_DATA_WATCH : - $data = unpack('ltype/imonitor/istate/dfps/dcapturefps/danalysisfps/ilevel/irate/ddelay/izoom/iscale/Cdelayed/Cpaused/Cenabled/Cforced/iscore/ianalysing', $msg); + $data = unpack('ltype/imonitor/istate/dfps/dcapturefps/danalysisfps/ilevel/irate/ddelay/izoom/iscale/Cdelayed/Cpaused/Cenabled/Cforced/iscore/ianalysing/Canalysisimage', $msg); $data['fps'] = round( $data['fps'], 2 ); $data['capturefps'] = round( $data['capturefps'], 2 ); $data['analysisfps'] = round( $data['analysisfps'], 2 ); diff --git a/web/js/MonitorStream.js b/web/js/MonitorStream.js index 815183dff..9e1d07e5c 100644 --- a/web/js/MonitorStream.js +++ b/web/js/MonitorStream.js @@ -1337,6 +1337,16 @@ function MonitorStream(monitorData) { } } // end if canEdit.Monitors + // Verify analysis image state matches what we requested + if (streamStatus.analysisimage !== undefined) { + const want_analysis = this.analyse_frames && streamStatus.analysing != ANALYSING_NONE; + const got_analysis = !!streamStatus.analysisimage; + if (want_analysis != got_analysis) { + console.log('Analysis image mismatch: want=' + want_analysis + ' got=' + got_analysis + ', re-sending command'); + this.streamCommand({command: this.analyse_frames ? CMD_ANALYZE_ON : CMD_ANALYZE_OFF}); + } + } + if (this.status.auth) { if (this.status.auth != auth_hash) { // Don't reload the stream because it causes annoying flickering. Wait until the stream breaks.