Commit Graph

196 Commits

Author SHA1 Message Date
Isaac Connor
bf82278b6e feat: integrate EventStream into montagereview with zms recovery and fixes
- Integrate EventStream.js into montagereview.js for persistent MJPEG
  streaming in replay mode (speeds >= 1x), falling back to per-frame
  mode=single for speeds < 1x where zms lacks slow-motion support
- Add EventStream recovery logic: detect zms death via AJAX failures,
  img.onerror, and error responses; auto-restart with new connkey and
  exponential backoff (max 5 retries)
- Fix zms crash on bulk/interpolated frames: add stat() check in
  sendFrame() for SaveJPEGs & 1 path, fall through to ffmpeg_input
  when JPEG file doesn't exist on disk; send "No frame available"
  text frame instead of terminating when no source available
- Center monitor canvases: text-align in CSS for scale mode,
  horizontal offset calculation in maxfit2() for fit mode
- Reduce console noise: comment out high-frequency debug logs,
  convert error-path logs to console.warn/error
- Fix ESLint sourceType to "script" for traditional non-module JS
  files, resolving false-positive no-unused-vars on global functions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:14:06 -05:00
Isaac Connor
4c42e736af fix: reset send_twice flag after use in EventStream::runStream
send_twice was set to true by zoom/pan/scale/seek commands when paused
but never reset to false. Once set, every subsequent frame was sent
twice forever, even after unpausing. This doubled bandwidth usage and
increased exposure to the processCommand race condition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:19:20 -05:00
Isaac Connor
d23860ab96 fix: hold mutex for entire processCommand to prevent frame vector race
processCommand runs on a separate thread but several command handlers
(CMD_PREV, CMD_NEXT, CMD_FASTREV, CMD_PAUSE, CMD_STOP, and zoom/pan/scale
commands) modified shared state (curr_frame_id, paused, replay_rate, etc.)
without holding the mutex. This allowed the main runStream loop to read a
corrupted curr_frame_id between its bounds check and the vector access in
sendFrame, causing a vector out-of-bounds assertion failure.

Move the mutex acquisition to the top of processCommand and remove the
redundant per-case scoped_locks that would otherwise deadlock.

maybe fixes #4644

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:19:20 -05:00
nic
d91556e1ff Revert "perf: skip FFmpeg video probe for JPEG-based event streams" 2026-02-10 20:08:48 -06:00
Nic Boet
f77ca859ed perf: skip FFmpeg video probe for JPEG-based event streams
When SaveJPEGs is enabled, sendFrame() reads JPEG files directly from
disk and never touches the FFmpeg input. However, loadEventData()
unconditionally called FFmpeg_Input::Open() on the event video file,
which runs avformat_find_stream_info() — a 2-5 second probe that was
pure overhead for JPEG-based streaming.

Gate the Open() call on !(SaveJPEGs & 1) so the expensive probe is
only performed when frame extraction from the video container is
actually needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 23:14:55 -06:00
Isaac Connor
f7ececbf83 Merge pull request #4607 from nabbi/event-playback-fixes-master
Fix event playback stability and optimize seek performance
2026-02-09 17:44:05 -05:00
Nic Boet
4b098fb760 perf: replace O(n) linear frame seeks with O(log n) binary search
Replace two linear scans in event playback seeking:

- seek(): O(n) walk from frame 0 replaced with std::lower_bound on
  timestamp. Falls back to last frame if target is past end.

- processCommand(CMD_SEEK): O(n) estimate-then-walk replaced with
  std::upper_bound on offset, then step back one. Handles edge cases
  where offset lands before first frame or past last frame.

For a 10-minute event at 30fps (~18,000 frames), worst case drops
from 18,000 comparisons to ~14 (log2).
2026-02-09 12:44:59 -06:00
Nic Boet
0ca3ed3155 fix: reset keepalive timer on event-end pause to prevent connection drop
When checkEventLoaded pauses at the end of an event in MODE_SINGLE/
MODE_NONE, the main loop relies on last_frame_sent to decide when to
send keepalive frames. If last_frame_sent is stale, the loop waits up
to 5 seconds (MAX_STREAM_DELAY) before sending anything. During that
gap the HTTP connection can time out, causing SIGPIPE on the next write.

Reset last_frame_sent to epoch so the next iteration sends a keepalive
immediately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 12:29:12 -06:00
Nic Boet
328d5c2a99 fix: move sendFrame inside mutex scope to prevent race condition
sendFrame() reads frames[curr_frame_id-1] but was called outside the
mutex scope. The command processor thread (running processCommand via
checkCommandQueue) can modify curr_frame_id or reload the event
(clearing/rebuilding frames) between the mutex unlock and the
sendFrame call, causing out-of-bounds access.

Move sendFrame into the first mutex scope so frames[] access is
protected from concurrent modification by the command thread.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 00:07:19 -06:00
Nic Boet
011defdad2 fix: harden eventstream against vector bounds, dangling pointers, and off-by-one
Three related safety fixes for event_data->frames vector usage:

- Replace frame_count/last_frame_id with frames.size() for bounds checks,
  since synthetic frame interpolation inflates the vector beyond the DB
  row count, making the old fields unreliable for vector indexing.

- Replace dangling last_frame pointer with last_frame_idx index, since
  emplace_back can reallocate the vector and invalidate all pointers.

- Fix seek backward off-by-one in checkEventLoaded() that set
  curr_frame_id = event_data->frames.size() then accessed
  frames[curr_frame_id - 1] before the vector was populated.
2026-02-09 00:07:14 -06:00
Nic Boet
637fe24f0a perf: skip Frames table scan during event playback unless debugging
Remove the FramesDuration subquery from the event metadata query in
EventStream::loadEventData(). Previously every playback initiation
ran:

  SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=Events.Id

as an embedded subquery. Without a covering index on (EventId, Delta),
MySQL walks the EventId_idx to find matching rows then fetches each
table row to read the Delta column. For a 10-minute event in
Record/Mocord mode at 30fps with bulk_frame_interval=100, that is
~180 index lookups + row fetches. For alarm-heavy events where every
frame gets a DB row, it can reach thousands of row fetches. This
blocks playback start on every event view.

The FramesDuration value was only consumed by a Debug level 2 log
comparing it against Event Length. It was never used in any playback
computation, frame timing, seek logic, or client-facing output.
The frames_duration field has been removed from the EventData struct
entirely. The diagnostic query and its log are now colocated inside
a logLevel() >= DEBUG2 guard using a local variable.

Production impact: none. Default ZoneMinder log level is INFO (0).
DEBUG2 is only reached via explicit operator configuration
(Options > System > LOG_LEVEL_FILE or ZM_DBG_LEVEL env variable).
No production deployment runs at DEBUG2 as it generates thousands
of log lines per second per daemon. The subquery code path is
unreachable under default configuration. Operators who enable DEBUG2
get the same diagnostic output as before.

Theoretical gains on playback initiation per event:
- Eliminates 1 SQL subquery performing N row fetches from the
  critical path (N = Frames rows for the event)
- Record mode, 10min event: ~180 fewer row fetches
- Alarm-heavy 10min event: ~3,000-18,000 fewer row fetches
- Reduces MySQL buffer pool pressure from Frames page reads
- Reduces InnoDB row lock contention with concurrent frame INSERTs
  from active recording daemons hitting the same EventId range

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 16:18:51 -06:00
Isaac Connor
df6c4a4a13 fix: address multiple bugs across core source files
- zm_analysis_thread.cpp, zm_decoder_thread.cpp: Fix potential deadlock
  in Start() by calling Stop() before joining thread
- zm_camera.cpp: Add null check after avformat_alloc_context()
- zm_comms.cpp: Fix memory leak in InetSocket::bind(), fix error message
  typo in deleteReader(), fix clearReaders/clearWriters to recalculate
  mMaxFd properly
- zm_config.cpp: Fix potential buffer underrun when parsing config files,
  fix misplaced SERVER_ID check logic
- zm_db.cpp: Fix logger level not restored on early return in zmDbDo(),
  fix empty string access in DB_HOST parsing
- zm_event.cpp: Fix typo "foudn" -> "found", fix memory leak with Tag
  allocation, fix variable shadowing with video_file
- zm_event_tag.cpp: Fix null dereference when AssignedBy is NULL
- zm_eventstream.cpp: Fix dangling pointer bugs with emplace_back
  (use auto& instead of auto), fix memory leak in loadInitialEventData

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 10:18:49 -05:00
Isaac Connor
c6ab1c143b fix: address multiple bugs in eventstream, fifo, and monitor
zm_eventstream.cpp:
- Fix null pointer dereference when video_file or scheme column is NULL
- Fix out-of-bounds array access in CMD_SEEK handler when curr_frame_id
  decrements to 0
- Fix setStreamStart calling wrong loadInitialEventData overload (event_id
  was being truncated and used as monitor_id)

zm_fifo.cpp:
- Fix close(-1) call when file creation fails
- Fix use of uninitialized raw_fd when on_blocking_abort is false
- Reset outfile and raw_fd in close() to prevent use-after-close

zm_monitor.cpp:
- Fix shm_id being zeroed before use in shmctl() call
2026-02-02 23:24:08 -05:00
Isaac Connor
4863febff0 Split out seeking code into ::seek. Test for no rows returned when loading by monitor_id and time. Use SystemTimePoint instead of time_t. Add setStreamStart(event_id, time) 2026-01-05 16:37:30 -05:00
Isaac Connor
920d38b4cd include filesystem since we use std::filesystem::exists 2025-11-11 13:13:53 -05:00
Isaac Connor
1b2fd12f57 Fix warnings about unequal frame coutns 2025-10-11 10:56:05 -04:00
Isaac Connor
b66bfc8960 Prevent looking for a last frame that doesn't actually exist 2025-09-16 15:58:19 -04:00
Isaac Connor
e931f5ee40 Send an error image instead of ISE when we can't get the required frame 2025-08-26 08:08:06 -04:00
Isaac Connor
4757d711ef Change various cases of int img_buffer_size to size_t 2025-07-19 14:18:32 -04:00
Isaac Connor
9d0f2c25bb Small debug cleanup 2025-06-11 14:51:43 -04:00
Isaac Connor
0d34e4647a Log scale in debug about status command 2024-12-09 19:07:51 -05:00
Isaac Connor
02871576a5 Add fps to event status. Convert CmdTimer to Interval 2024-10-22 18:22:19 -04:00
Isaac Connor
33ede2f7d1 FIx floating point exception 2024-09-27 16:17:09 -04:00
Isaac Connor
c6e5ff43e7 Merge branch 'master' of github.com:ZoneMinder/zoneminder 2024-09-27 16:01:20 -04:00
Isaac Connor
3802ec17d3 Add test for 0 duration may fix floating point exceptions 2024-09-27 10:01:09 -04:00
Isaac Connor
bf7920bad8 Merge branch 'master' of github.com:ZoneMinder/zoneminder 2024-09-24 19:30:47 -04:00
Isaac Connor
37fef75eff Handle 0 frame_mod 2024-09-24 17:23:23 -04:00
Isaac Connor
a28d88880f Merge branch 'master' of github.com:ZoneMinder/zoneminder 2024-09-22 08:20:08 -04:00
Isaac Connor
312a15801e Using last_delta really doesn't work. Instead let's use remaining time/remaining frames. 2024-09-20 18:54:54 -04:00
Isaac Connor
3dd08cd213 Load duration from the db instead of calculating it from start and endtime. This gives us the decimal part as well. 2024-09-19 16:09:38 -04:00
Isaac Connor
7c8738e976 Correct log in debug statusment 2024-09-16 17:22:27 -04:00
Isaac Connor
e68fb77e4b Take frame_mod into account when calculating time to sleep. Fixes rate not really working. 2024-09-16 16:41:00 -04:00
Isaac Connor
1deccb8cf4 Instead of just exiting, set zm_terminate to true so we exit gracefully 2024-08-22 19:09:15 -04:00
Isaac Connor
d968e243ff Implement mode=single for events, so that we can efficiently just grab a single frame 2024-08-19 18:26:35 -04:00
Isaac Connor
5237641efe Fix calculation for when to send a keepalive image. Possible fixes #4101 2024-08-13 16:38:14 -04:00
Isaac Connor
9b1f3fd3e1 Use better format specifiers 2024-05-24 16:06:21 -04:00
Isaac Connor
5e1596d9f5 Fix panning not working when paused. Also fixes SCALE. Fixes #3947 2024-04-05 09:32:05 -04:00
Aaron Kling
c4683d90a9 Format code using astyle google format
Commands used:
astyle --style=google --indent=spaces=2 --keep-one-line-blocks src/*.cpp
astyle --style=google --indent=spaces=2 --keep-one-line-blocks src/*.h
2024-03-26 13:43:58 -05:00
Isaac Connor
2d73a083b5 curr_frame_id should never be 0. So set it to 1 instead 2024-01-29 13:30:17 -05:00
Isaac Connor
39e03e6a3c Fix infinite loop in the event there is only the initial frame in the db. 2024-01-18 14:16:38 -05:00
Isaac Connor
7280329e6e Split long line 2024-01-17 17:09:43 -05:00
Isaac Connor
bc11f5cdac Fix never gapless events not working. Put mutex around status sending because event_data may change at any moment. Fix 100% cpu use when at end of event and not moving to next event, hence paused. 2023-11-01 17:27:56 -04:00
Isaac Connor
5bb7529c3e Just code cleanups, but also add a mysqL-free_result which should fix a mem leak 2023-11-01 17:27:56 -04:00
Isaac Connor
b7424b5bea Test for seek beyond event length and frame_id > last_frame_id 2023-10-16 14:33:57 -04:00
Isaac Connor
61791c76a3 Adjust debug level of no event change required line as it repeats a billion times.. 2023-10-16 14:05:12 -04:00
Isaac Connor
277f856452 Move lock lower down to where it is crucial. When debugging offsets, use %.6f instead of %.2f 2023-10-16 12:14:38 -04:00
Isaac Connor
f39b3f0583 Reduce debug levels. Pass along Width and Height when instantiating Image from AVFrame so that it can scale to a lower res. 2023-08-02 13:56:06 -04:00
Isaac Connor
01b886ef49 Add scale to status and fix cast on elapsed 2023-04-06 15:15:36 -04:00
Isaac Connor
7e489f7fa2 When there are NO frames in the db, populate the list of frames by 1/fps. 2022-12-07 12:26:51 -05:00
Isaac Connor
c2680f7f77 Turn failure to send into a debug. When running under fpm etc we may not get SIGPIPE. 2022-10-17 17:22:23 -04:00