Write a static m3u8 file with relative segment paths to the event
directory on event close. Set DefaultVideo to the m3u8 filename
instead of the first segment.
This fixes all existing code that assumes DefaultVideo is a single
file spanning the entire event:
- Frame extraction (ffmpeg -ss $delta -i $DefaultVideo) works because
ffmpeg reads m3u8 as input and handles cross-segment seeking
- file_exists() and file_size() work on the physical m3u8 file
- In-progress viewing still works (DefaultVideo is incomplete.mp4
during recording, updated to m3u8 on close)
Web playback detects .m3u8 extension in DefaultVideo and routes to
the dynamic view_event_hls.php (which adds auth tokens to segment
URLs) instead of view_video.php.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Events now record as multiple ~10-second MP4 segments instead of one
monolithic file. Each segment is remuxed to non-fragmented faststart on
close, giving browsers a proper moov atom with full sample tables.
The problem: fragmented MP4s (empty_moov+frag_keyframe) have no seek
table in the moov. Chrome probes fragment boundaries with dozens of
small range requests, each going through the full PHP stack (auth, DB,
fopen). This causes multi-second delays before playback starts.
The fix: short segments with faststart moov + HLS manifest for seamless
playback across segments. Chrome gets a complete seek table per segment
from a single moov read.
C++ changes:
- Event::openSegment()/closeSegment() manage VideoStore lifecycle per segment
- AddPacket_() rotates at keyframe boundaries when segment exceeds 10s
- Each segment remuxed to faststart via new remux_to_faststart() utility
- Segment metadata written to Event_Video_Segments table on event close
- DefaultVideo set to first segment for backward compatibility
Database:
- New Event_Video_Segments table (EventId, SegmentIndex, Filename,
StartDelta, Duration, Bytes) with cascade delete
Web playback:
- New view_event_hls.php generates VOD m3u8 manifest from segments table
- event.php detects segments, uses application/x-mpegURL source type
- VideoJS 8.23.4 handles HLS natively via built-in http-streaming
- view_video.php updated to serve individual segment files by name
- Old single-file events still play via direct MP4 (backward compatible)
Download/export:
- download_functions.php lists segment files in ffmpeg concat input
- Produces single merged MP4 with zero re-encoding (existing mechanism)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
FilterTerm.php:
- Replace eval() with safe compare() method for SystemLoad, DiskPercent,
and DiskBlocks filter conditions (RCE via crafted op/val)
- Validate operator against allowlist in constructor
- Sanitize collate field to alphanumeric/underscore only (SQLi)
onvifprobe.php:
- Use escapeshellarg() on interface, device_ep, soapversion, username,
and password arguments passed to execONVIF() (command injection)
Event.php:
- Use escapeshellarg() on all arguments to zmvideo.pl instead of
escapeshellcmd() on the whole command (command injection via format)
- Anchor scale regex with ^ and $ to prevent partial matches
image.php:
- Restrict proxy URL scheme to http/https only (SSRF via file:// etc)
filterdebug.php:
- Use already-sanitized $fid instead of raw $_REQUEST['fid'] (XSS)
MonitorsController.php:
- Use escapeshellarg() on token, username, password, and monitor id
in zmu shell command instead of escapeshellcmd() on whole command
HostController.php:
- Use escapeshellarg() on path in du command (command injection via mid)
- Remove space from daemon name allowlist (argument injection)
EventsController.php:
- Remove single quotes from interval expression regex (SQLi)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add RBAC checks to ConfigsController edit() and delete() requiring
System=Edit permission, matching the pattern used by other controllers.
Harden System/Readonly column checks with !empty() to handle missing
columns gracefully. Fix command injection in Event.php by using
ZM_PATH_FFMPEG constant with escapeshellarg() instead of hardcoded
unsanitized ffmpeg call. Add is_executable() validation at all exec()
sites using ZM_PATH_FFMPEG as defense-in-depth against poisoned config
values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(tag): Create tags on mobile
chore(tags): Change TagName to Name
chore(tags): eslint
chore(tags): dbFetchAll to dbQuery for removetag
chore(events): eslint (attempt 2)
feat(tags): Better handling of keyboard
fix(tags): Enter key for creating new tag
fix(tags): Don't allow space as a tag name
feat(tags): Delete tag if last assignment removed
fix(tags): Increase height of dropdown
in progress
fix(Tags): Use T.Id on the events page dropdown
fix(Tags): Remove $availableTags from events.php
chore(sql): Formatting sql statements
feat(Tags): Working OR on filters and events pages
fix(filter): Populate availableTags
chore(Tags): code formatting
fix(tag): Add tag on create tag
Fix(tags): Remove tag from available if last
feat(tags): Add zm_update.sql
fix(chosen): Undo css width
fix(chosen): tags dropdown width
fix(tags): dropdown over timeline
fix(tags): Full width input
fix(events): Refresh table on page show
chore(filter): Clean up availableTags
chore(event): Clean up available & selected Tags
fix(event): Update available tags on remove
fix(event): Remove hack for selected tags
feat(tags): Blur input after adding tag
doc(tags): Initial tags documentation
fix(tags): Dark theme dropdown
fix(tags): Dark theme for tags on input
fix(tags): Dark theme for highlight in dropdown
fix(tags): Populate filter tags droplist
chore(): Bump zm_update to 1.37.42
chore(tags): Move mobile check to skin.js
chore(tags): Comment debug statements
fix(tags): Enter key to create tag on mobile Chome
chore(tags): Space in 'All Tags' for translation
Temporary commit to handle cookie expiration times
chore(tags): Remove unnecessary Tag(s) from en_gb
chore(): Cleanup unnecessary Error and Debug
chore(): Resolve merge conflicts
chore(): Address merge conflicts with master