Appearance
Changelog
[0.4.5] - 2026-10-04
Added
- Changes to users in
security.usersapply without a restart: a removed user or a changed password stops working at once. - Protection against password guessing on every server, RTSP included: after 10 failed sign-ins in a row the user is locked out from that address for 5 minutes (
security.lockout). allowOriginin every section takes several origins separated by commas; a value that is not an origin is ignored with a warning in the log.
Changed
- The packaged service runs as the
forgevisuser under systemd isolation, and the default archive is/var/lib/forgevis/recordings. An installation that ran as root stays on root after the upgrade; moving it over is described in the security guide. - Publishing over WHIP takes the
publishpermission rather thanread. /cluster/proposeis removed./api/health/readywithout credentials answers with the status and version only; the details of the checks go to a caller allowed into the management API.
Fixed
- A user with access to one camera (
path) opens it over WebSocket and sees no other camera through it. websocket.allowOrigintakes effect: the WebSocket opens only from the pages on the list.- A camera whose id is not a single directory name (contains
/,\or is..) is not recorded and fails the configuration check.
[0.4.4] - 2026-10-03
Added
- The
reconnectsection sets the pauses between connection attempts to a camera: by default one attempt at once, then pauses of 5, 10, 15 and 30 seconds with a random spread; one schedule serves recording, HLS, the restream and viewers. /api/archive/{camera_id}/segmentstakestz_offset_minutesand dates thedayson the viewer's clock.
Changed
- A request for the stream of a camera that does not answer is refused at once between attempts, with the cause and the time of the next attempt.
- The log carries one line per failed connection attempt to a camera, with the cause and the time of the next attempt; a recording that comes back is marked by a line with how long it was down.
- On shutdown the service first stops taking API, playback and stream requests, then closes recording and exits as soon as the segments are closed.
/api/healthis removed: process liveness is/api/health/live, readiness and version are/api/health/ready./api/archive/{camera_id}/segmentsanswers400to a window wider than a day or with wrong or inverted bounds, and503when the database holding the archive index is unavailable, rather than an empty listing.
Fixed
- A camera that keeps its connection open and stops sending video is reconnected after 5 seconds — for recording, HLS, the RTSP restream and the substream.
- A segment closed when the service stops is visible in the archive.
- A cluster node with the streamer role serves the substream of a camera another node records.
- A segment cut short by losing the camera's stream reaches the archive in the database, and
runOnSegmentCompleteruns for it. - A configuration change applied on the fly goes through the same checks as startup; a node with a database and an empty
nodeNamekeeps recording its cameras.
[0.4.3] - 2026-09-30
Added
- The
/api/cameras/{id}/snapshotendpoint serves a camera's still image from itssnapshotURL in the config orsnapuriin the database, and keeps it for 15 seconds for every request to that camera.
Changed
- The recording path is set for the whole node in
cameraDefaults.record_path; a configuration that setsrecord_pathon an individual camera fails the startup check.
Fixed
- An on-demand stream, such as a substream, connects for the next viewer even when the previous one left before the connection was up.
- Restarting a stream closes the previous connection to the camera at once.
- PTZ works for cameras added after the node started and for cluster cameras.
[0.4.2] - 2026-09-29
Added
- The
variants.m3u8playlist lists a camera's variants for a quality choice in the player: the source stream, a 720p step and the substream. The step is encoded, and the substream connected, only when a player picks it;master.m3u8stays the source stream. - The
hls.transcodeMaxConcurrentsetting limits how many 720p steps are encoded at once.
Changed
- HLS segment times follow the wall clock, and every segment of every HLS playlist, Low-Latency included, carries
EXT-X-PROGRAM-DATE-TIMEwith the moment it starts.
Fixed
- In HLS, audio and video start together.
- The RTSP restream carries video with the camera's own timestamps, so a stream at any frame rate plays in real time.
- Low-Latency HLS lists every part with its real duration, within the bounds Safari accepts, and a finished segment enters the playlist at once.
- HLS cuts segments and parts by the camera's own time.
- An HLS playlist always spans at least three target segment durations.
- G.711 audio in HLS keeps time with the video.
- The first request for the playlist of a substream or a 720p step waits for its first segment, so the player stays on the variant chosen.
[0.4.1] - 2026-09-24
Added
- A recording names the ForgeVis version that wrote it, in its metadata (
©too).
Fixed
- Recordings with AAC audio describe the audio track exactly as ISO/IEC 14496-1 defines it, and a progressive MP4 states the track's bitrate.
[0.4.0] - 2026-09-11
Added
forgevis --retentiondeletes recordings older thanretention_days— set incameraDefaultsand, where a camera differs, on the camera itself. With no value, and withretention_days: 0, recordings are kept indefinitely. Recordings of cameras the configuration no longer has are kept for thecameraDefaultsvalue.forgevis --retention --dry-runreports what would be deleted, camera by camera: how many recordings go, how many stay, and the archive depth each camera ends with.- A camera read from the database, and one handed over by the cluster, carries its archive depth: the value set on the camera reaches its settings on whichever node records it.
- Retention runs on a node with a database and inside a cluster too: a node deletes only the recordings it wrote itself, and a file's row in the recordings table goes with the file.
Changed
- A recording error's type names the cause:
connectfor the network or the camera,streamfor a camera that answered and went quiet,writefor this node's disk,internalfor a fault in the service. An error the service does not recognise is labelledunclassifiedand written to the log in full. - The segment listing answers with an object:
archiveholds the segments of the requested window,daysevery day the camera has recordings for. A calendar and a timeline are built from one request. - Without
fromandtothe segment listing covers today. - A segment that began before the window and runs into it is part of the listing.
Fixed
- A camera's metrics go when the camera does: the last-frame, reconnect and recording-error series are dropped as it leaves the configuration.
- A connect timeout for a camera is written to the log once.
- Export and timelapse end at the end of the selected window, even when the recording has a gap inside it.
- A stuck ffmpeg in an export or timelapse is stopped by the
api.archiveJobTimeoutSectimeout (one hour by default); the job fails without holding up the ones after it. - Finished export files are kept in
api.archiveExportDir(/var/lib/forgevis/exportsby default), accessible to the service alone, and are removed an hour after they are ready — including ones left from a previous run. The parts an assembling node downloads from its peers are gathered there too. - With
mp4recording, export and timelapse skip the segment still being written — it cannot be read until it closes. Withfmp4it is included up to its last written fragment.
[0.3.10] - 2026-09-10
Fixed
- Cameras that send the SSRC in a non-standard form in the SETUP response (e.g. Rubetek) connect and record.
[0.3.9] - 2026-08-29
Fixed
- Audio samples in fragmented recordings are marked as independently decodable, matching how third-party tools actually decode them.
[0.3.8] - 2026-08-28
Fixed
- A time-lapse covers the window that was asked for: the range is cut before the footage is sped up.
- An export or time-lapse job that encodes no frames ends as a failure that says why.
[0.3.7] - 2026-08-27
Changed
- A hot reload reports its result in one line: how many camera tasks were started, stopped, removed, left unchanged, and are running. Per-camera lines are written only for cameras that changed.
[0.3.6] - 2026-08-27
Added
- HTTPS for every HTTP listener on the node — the management API, HLS, WebRTC signalling, archive playback and metrics. Turned on through the
tlssection: one certificate for the node and one switch for every listener at once, because a browser that loaded the interface overhttpswill not fetch an HLS playlist overhttp. Credentials stop crossing the wire in the clear; whether a client can also verify who it is talking to depends on whether it knows the issuing authority.tls.enabled: truewithout a readablecertandkeyfails the startup. The certificate is read at startup, so replacing it needs a restart.
Changed
- TLS is configured once for the whole node: the
encryption,serverCertandserverKeykeys inside theapiandwebrtcsections are replaced by a top-leveltlssection. - The cluster certificate documentation now covers how long a certificate lasts and how to issue one for an installation without the platform.
[0.3.5] - 2026-08-26
Added
- Archive playback server: a request names a camera and a moment, and the recording starts there. The node finds the frame through the index every file keeps in its header, so a player never has to parse the file or work out byte offsets for itself. Playback begins at the nearest key frame, so the first seconds after a seek hold together, and a moment with nothing recorded is answered with the next recording there is. A browser is served a plain file (
format=mp4), tools are served the fragments. A listener of its own, off by default — see theplaybacksection of the configuration. - A node can leave a cluster from any node, not only from the leader. Membership is changed by the leader, and the departing node asks it over the consensus channel with the certificate it was issued. Only the departing node's own id travels: the leader works out the new voter set from its own membership, because the leaver's view may be behind.
- Demoting the current leader out of the voter set reaches a new election. The remaining voters are asked to elect over the consensus channel, where nodes present certificates. The prompt is needed because a demoted leader stays a learner and keeps sending heartbeats: on its own, the others' election timers never expire.
- A cluster-state request that reaches a node other than the leader is answered with a redirect to the leader: the caller repeats it with its own credentials. Nodes reach one another over the consensus channel alone, where they present certificates.
- A node reports its own name on
/api/metrics. The platform reads it there when a server is registered rather than taking it from a form: cameras are handed to a recorder by name, and a mismatch of one character raises no error anywhere — the node reads an empty camera list and goes on reporting itself healthy. - The platform owns the database structure: a node creates nothing in it and needs no rights to change the schema — reading
camerasand writingrecordingsis the whole grant. The trigger that announces camera changes is installed with the schema. For an installation with its own database and no platform, the schema is published as a plain SQL file. - The recording in progress is available in the archive listing: a node answers which file it is writing for a camera and from what moment, so a timeline shows the last few minutes as well as the closed segments. Requires
record.format: fmp4— a progressive recording cannot be played before it is closed. - Cluster nodes authenticate each other with certificates. The Control Plane issues them: a node creates its own private key and a signing request, and only the request leaves the node. Until a certificate arrives the consensus port stays closed, which the node reports in its log.
Changed
POST /cluster/initrequirescluster_id, and it has to be a UUID. That identity is what a camera row points at when it names the cluster that owns it, and the cluster reads exactly the rows carrying it. A cluster without one would read every enabled camera in the database as its own, and it cannot be re-initialised to acquire one later — so a request withoutcluster_idis refused. A value that is not a UUID is refused at the same point:cameras.cluster_idis a uuid column, and otherwise the refusal would surface later and look like an unreachable database.- A camera with recording switched off does not build a recorder: the journal carries an INFO line saying the camera is streamed but not recorded. The level matches what happened — that configuration is a deliberate one, not a fault.
- The node name is set by the top-level
nodeNamesetting rather than inside theclustersection: a single node with a database needs it too, and there is no cluster there at all. An empty value means the system hostname. There is no built-in default — a shared one would have two nodes report the same name and record the same cameras; with a database configured, a node that can resolve no name refuses to start. A name too long is refused at startup as well: the database matches it against a 64-character column. The node logs the name it resolved and where it came from. - The consensus channel is always protected;
cluster.tls.enabledandcluster.tls.require_client_authhave been removed. Paths to your own certificates are still set incluster.tls. - A management request that reaches a node other than the leader is answered with a redirect to the leader: the caller repeats it with its own credentials, so nodes need no credentials for one another.
- Node liveness travels over the consensus channel, so cluster state does not depend on whether the management API is behind a password.
- An invalid
cluster.rpc_addris rejected at startup. - The segment listing at
/api/archive/{camera}/segmentsincludes the recording in progress, marked with anis_openfield, so one request returns a camera's whole archive up to the current minute. Every other segment carriesis_open: false. - The Raft state directory is set in
cluster.dataDir; it defaults to/var/lib/forgevis/data. The path is absolute, so it does not depend on the directory the service is started from. The node's certificates live there too, undertls. The default matches the directory a running installation already uses, so an upgrade need not set the key. - A node configured for a cluster warns at startup when its state directory is empty.
- The shipped configuration sets
record.formattofmp4. The playback server and the recording-in-progress endpoint work only with it: a progressivemp4keeps its header at the end of the file and cannot be played until it closes. - A camera identifier in an archive request holds letters, digits,
_and-and nothing else; anything else is refused with400. - An archive request for a camera in a cluster is answered with a redirect to the node recording it when it arrives anywhere else.
/api/archive/{camera}/segmentsand/api/archive/{camera}/currenttherefore answer the same way on any node of the cluster, and no particular node has to be addressed.
Fixed
- A cluster keeps recording when a node cannot read the camera list from the database: an unavailable list is not taken for an empty one, and assignments stay in place until a read succeeds.
- A failed camera read does not stop the cameras on a single node: the reload is skipped and the current camera set is kept.
- A node removed from a cluster can be added back without restarting the service: leaving erases its local cluster state right away, and the node starts recording the cameras assigned to it as soon as it is part of a cluster again.
- A failure of the cluster machinery is detected automatically: the node reports itself as not ready and recovers on its own.
- Removing a node that is no longer part of the cluster succeeds.
- A camera that names a specific cluster node is recorded by that node, including when the camera list comes from the database.
- The
hls.allowOriginsetting is applied: a page reaches HLS from an address the configuration allows.
[0.3.4] - 2026-08-13
Fixed
- Fixed seeking in fMP4 recordings: fragments are cut at keyframes, so jumping to a position lands on a frame playback can actually start from.
- Fixed the audio track duration in MP4 recordings.
- Fixed the H.265 profile and level advertised in recordings: it did not match the stream, which could make strict decoders and in-browser playback refuse the file.
- Recordings now start on a keyframe, so playing a file from the beginning no longer shows artefacts for the first second.
- A segment interrupted by a lost camera connection or by a stalled stream is now closed properly: in MP4 format such a file previously would not open at all.
- Frame durations in recordings follow the camera's actual stream: archive time no longer drifts from real time (about 80 seconds a day on a camera running at 25.02 fps), and dropped frames no longer collapse the timeline.
- A camera clock restart no longer corrupts a recording: the segment is closed and the next one starts on a fresh timeline.
- A segment's start time in the database now matches its first recorded frame, so time-range export lands on file boundaries more accurately.
- Added timeouts to camera connections and internal network calls: an unreachable camera or node no longer holds a task until the system TCP timeout expires.
- Fixed a duplicate camera connection when several consumers subscribed at the same time — the connection stays single.
- An RTSP restream client that stops reading no longer holds the camera connection open.
Changed
- An oversized
record.segmentDurationis now rejected at startup: the segment index would not fit in the file, silently disabling time-based seeking.
[0.3.3] - 2026-08-05
Fixed
- Fixed camera settings (recording, audio, sub-stream) not being applied when a camera moves to another cluster node.
[0.3.2] - 2026-08-04
Fixed
- Improved cluster state storage resilience to sudden power loss.
- Fixed audio/video synchronization in archive recordings; audio frame durations now account for the codec (AAC, G.711, Opus).
- Fixed RTSP restreaming for cameras with G.711 and Opus audio.
- Improved node address registration in the cluster at startup; node status changes no longer reset node details.
[0.3.1] - 2026-08-04
Changed
- Cluster State Sync Streamlined: Recorder liveness and address metadata now travels through a single Raft state entry per node instead of six separate ones. After a node has been running for a minute, its recurring heartbeat payload shrinks by roughly 6×, and Raft snapshots carry fewer keys per member. No externally-visible behaviour change — the reduction is entirely in background inter-node traffic. Most noticeable on deployments with many recorders.
[0.3.0] - 2026-06-03
Added
- Cross-Node Archive Export: When a camera moved between recorder nodes during the requested export window, the resulting MP4 is now seamlessly stitched together from fragments living on different recorders. The platform/backend orchestrates: it reads the
recordingstable, partitions the requested window into per-node runs, submits each run as a stream-copy export to its owning recorder, then delegates the final concat to one chosen recorder ("assembler") to keep heavy I/O off the backend host. The final MP4 streams to the client through the backend as a thin proxy. Canonical migration patterns are supported — single migration, multi-hop, even repeat-visits to the same node (e.g.S1 → S2 → S3 → S2becomes one playable MP4 with no internal gaps). See Multi-node archive guide. - Cross-Node Time-Lapse: Same fan-out as plain export, with a final
setpts=PTS/Npass on the assembler. Speed factor is an integer in[2, 120]; output duration ≈raw_duration / speed. - Resilient Job State: Multi-node export jobs survive recorder restarts (each sub-export is resubmitted with a fresh id on
404, up to 3 attempts per run) and backend restarts (in-flight jobs inpending/exporting/assemblingresume their orchestrator tasks on the next backend startup and pick up at exactly the recorded state). DELETE /api/archive/jobs/{id}: Lets operators (and the backend orchestrator on cleanup) explicitly evict a finished archive MP4 from a recorder rather than waiting for its 1-hour TTL sweep. Returns204on success,404if the job is already gone.
Changed
recordings.node_idRenamed torecordings.node_name: The column that names the recorder behind a segment now stores a human-readable identifier (config.cluster.node_name, e.g.S1) instead of the numeric Raft id. This makes joiningrecordingsto the platformserversregistry a single column equality, with no live-cluster query required. The Alembic migration runs automatically on backend upgrade.
0.2.x
[0.2.11] - 2026-05-31
Changed
- Database Connection Pool Sized Up: The Postgres backend's max connection count was raised from 5 to 10. Old value queued metadata inserts behind any long-running query (e.g. an archive scan) on a busy node.
Fixed
- Cluster State Events Drop Under Bursts: The internal channel that carries cluster state-change events (camera assignments, role updates, storage transitions) had a 100-event capacity. During a burst — e.g. mass camera reassignment after a node demotion — the slowest subscriber could fall behind and silently miss events. Capacity is now 1000.
- Corrupted Snapshot Index Silently Masked: When applying a snapshot from the leader, an unparseable
snapshot_idfield used to default to0, which looked indistinguishable from a freshly initialised store and could mask state divergence between leader and followers. The bad value is now logged loudly and the existing snapshot index is preserved until the next valid snapshot arrives. - Less Cryptic RocksDB Recovery Errors: If the embedded key-value store can't be opened (path not writable, locked by another process, corrupted column family), the error message now points the operator at the specific cause instead of a generic "failed to open" or "cf not found".
Added
- Strict Configuration Validation at Startup: The server now refuses to start if
config.yamlcontains any of the following problems, aggregating every issue into a single error message instead of failing on the first one. Catches misconfigurations at boot rather than at first request / first recording / first encrypted handshake.- Two services bound to the same port (e.g. RTSP and API both on 8554).
- A camera with
record: truebut no resolvablerecord_path(neither on the camera itself nor incameraDefaults.record_path). - A camera
sourceURL that is empty, lacksrtsp:///rtsps://scheme, or fails URL parsing. Credentials are masked in error messages. encryption: trueon the API or WebRTC server with missing or unreadableserverCert/serverKeyfiles.
[0.2.10] - 2026-05-30
Fixed
- Hot-Reload Robustness: A configuration reload triggered while an editor is mid-write (yielding a truncated YAML file) is now retried up to three times with 300ms between attempts before being abandoned. Previously a transient parse error silently kept the old config active without explanation.
- Database Schema Auto-Setup: When
database.enabled=true, the schema/notification trigger is now installed at startup (previously a commented-out path). If thecamerastable does not yet exist, the failure is logged as a warning instead of being silently skipped — hot-reload via DB notification only works after the schema is in place. - Archive Query Bound: Database-backed archive segment lookups (driven by the
database:backend) now cap results at 10,000 rows per request and log a warning when the cap is hit. Guards against accidental multi-month time ranges pulling huge result sets over a small connection pool. - ONVIF SOAP Credentials Escaping: Usernames and topic-filter strings in SOAP requests are now XML-escaped. A password or username containing
&,<, or"no longer corrupts the WS-Security envelope. - ONVIF Response Hardening: SOAP responses are now read with a 10 MiB hard cap and rejected if they contain inline
<!DOCTYPEor<!ENTITYdeclarations. Defends against a compromised camera attempting to exhaust memory or probe for XXE. - WebRTC SDP Size Limit: WHEP/WHIP offer bodies larger than 128 KiB are rejected with a clear error. Real-world SDPs are an order of magnitude smaller; this stops malicious or malformed clients from streaming huge bodies.
- Frame Timestamp Hardening: An edge case in the per-frame timestamp normalization could theoretically cause the recorder to abort if an audio frame arrived before any video frame in the same session. The path was unreachable in practice but the fix removes the latent risk.
[0.2.9] - 2026-05-30
Added
- Per-Camera Health Metrics: New Prometheus series for alerting on stale or unstable cameras.
camera_last_frame_timestamp_seconds{camera="..."}is updated on every video frame, so a Grafana alert ontime() - camera_last_frame_timestamp_seconds > Nfires when a camera goes quiet.camera_reconnects_total{camera="..."}counts stream-reconnect attempts per camera. Existingrecording_errors_totalnow carries acameralabel in addition totype.
Changed
- Graceful Shutdown — Full Coverage: On SIGTERM / SIGINT, all background services (recorders, RTSP, WebRTC, management API, metrics server) are now stopped cleanly. Recorders get a 3-second drain window to finalize in-flight segments; the whole sequence is capped at 15 seconds so systemd never has to escalate to SIGKILL. Previously only camera tasks and HLS server were signalled.
Fixed
- WebRTC Session Leak: WHEP and WHIP sessions are now evicted after 30 minutes of inactivity (no ICE candidate exchange, no state transitions). A background sweep runs every 60 seconds, closes the underlying peer connection, and removes the entry. Prior to this fix, abandoned sessions (client crashed, network dropped before DELETE) accumulated in memory for the process lifetime.
[0.2.8] - 2026-05-30
Added
- Async Archive Operations (Export & Time-Lapse, unified job API):
POST /api/archive/{camera}/export?from&toandPOST /api/archive/{camera}/timelapse?from&to&speedboth return202 Acceptedwith ajob_id. Job lifecycle is observed viaGET /api/archive/jobs/{job_id}(pending→running→done/failed). Result is fetched viaGET /api/archive/jobs/{job_id}/download(inline by default;?download=1forces attachment). Time range capped at 4 hours; completed jobs expire after 1 hour. Concurrency: 4 parallel exports (stream-copy) and 2 parallel time-lapses (re-encode) per node. - Periodic Camera Hook: New
record.runPeriodicallyblock lets operators run an arbitrary script per camera at a fixed interval (default 60s). A global concurrency limit (maxConcurrent, default 50) bounds CPU spend across all cameras; cameras stagger their first tick at startup to avoid stampedes. Designed for live-preview thumbnail generation via ffmpeg without blocking the recorder. - Health Probes: New
/api/health/live(process up) and/api/health/ready(all dependencies reachable: storage, cluster store, recordings directory, license). Existing/api/healthretained for backward compatibility. - API Rate Limiting: Per-IP token-bucket throttling on the management API. Configurable via
api.rateLimit(enabled,perMinute,burst); defaults to 600 req/min, burst 60. Returns429 Too Many Requestson excess. - Panic-to-File Crash Dumps: On process panic, a backtrace is written to
<logging.directory>/crashes/<unix_ts>_<thread>.logfor post-mortem analysis. Stderr/journald output is preserved.
Known Limitations
- All archive operations (
/archive/...,runPeriodically) work only with files local to the node. In clustered deployments, if a camera was hosted on another node previously, those segments are not yet accessible through the current node's API. Tracked as future work.
[0.2.7] - 2026-05-28
Added
- Archive Byte-Range Seek: Recorded fragmented MP4 archives now embed a segment index, enabling fast in-file seeking via HTTP byte ranges in players and analysis tools.
- Argon2id Password Hashing:
security.users[].passaccepts a newargon2:prefix (PHC format), recommended for production deployments. Comparison runs in constant time.
Changed
- HLS Master Playlist Bandwidth:
BANDWIDTHis now derived from real segment sizes instead of a static estimate, giving adaptive bitrate clients accurate input. - HLS Media Playlist Timing: Each segment's
EXTINFreports its actual duration, andTARGETDURATIONadapts to the observed maximum, reducing player stalls on variable-duration segments. - HLS Low-Latency Compatibility: Parts at segment boundaries now declare
INDEPENDENT=YESso Safari and other strict players can join a live stream mid-segment. - Credential Format Tightening:
security.users[].passnow requires an explicit prefix (argon2:,sha256:,plain:). Bare values without a prefix are rejected (fail-closed) to prevent typos from silently demoting a hashed credential to plaintext.
Fixed
- ONVIF PTZ Connection Reuse: PTZ now inherits endpoint, port, and credentials from the camera's RTSP source when no explicit ONVIF overrides are configured, including non-default ports.
- Large MP4 Recordings: Recordings larger than 4 GiB no longer silently truncate internal chunk offsets — files now grow past the 32-bit limit correctly.
- Audio Tail at Session End: Audio RTP frames are no longer dropped when the video track ends slightly earlier in the same streaming session.
- Standalone License Camera Limit: An empty or missing camera limit no longer behaves as unlimited; the unlicensed default is applied instead.
Breaking Changes
- Password Prefix Required:
security.users[].passmust use one ofargon2:,sha256:,plain:. Migration: addplain:to existing unprefixed values, or rehash toargon2:/sha256:.
[0.2.6] - 2026-04-10
Added
- WebRTC Session Management API: Added
/api/webrtcendpoints to list active WHEP sessions, inspect a session by ID, and force-close sessions from the management API. - Built-in WebRTC Test Pages: Added packaged HTML diagnostics pages (
webrtc-test.html,webrtc-test-ptz.html) for quick browser-side validation of WebRTC playback and PTZ control.
Changed
- WHEP/WHIP Signaling Routes: WebRTC signaling routes were aligned with standardized endpoint style (
/{camera_id}/whep,/{camera_id}/whip) and now include OPTIONS/PATCH handling and ICELinkheaders for clients. - Frontend Static HTML Serving: Management API now serves top-level
*.htmlpages from packaged/dev HTML locations with filename safety validation.
[0.2.5] - 2026-04-05
Added
- Native WebRTC Playback (WHEP): Added built-in WebRTC server with a WHEP endpoint and session lifecycle handling (
POSToffer +DELETEsession close). - WebRTC Audio Codec Coverage: Added audio delivery for AAC, G.711 A-law (PCMA), and G.711 μ-law (PCMU) in WebRTC playback.
- PTZ in Main API: Added ONVIF PTZ control endpoints in the main ForgeVis API (
/api/ptz/move,/api/ptz/stop) with capability checks.
Changed
- Control Plane Camera Contract: Extended camera model/DTO fields in Control Plane to include stream behavior flags and node assignment input.
Fixed
- ONVIF Credentials Source for PTZ: PTZ ONVIF access now derives endpoint/auth data from RTSP camera source consistently.
[0.2.4] - 2026-03-20
Added
- Standalone Recorder Watchdog: Added a periodic supervisor that detects unexpectedly stopped recorder tasks and restarts them automatically in standalone mode.
Changed
- Cluster Health API Consolidation: Cluster node health, roles, and storage status are now provided via
/cluster/metrics. - Control Plane Compatibility: Cluster and license views were updated to consume the consolidated cluster metrics response.
Fixed
- Standalone License Camera Count: Corrected licensed camera counting logic in standalone deployments.
- Recorder Recovery Reliability: Reduced cases where recording remained stopped after long upstream outages by restarting dead recorder tasks on watchdog interval.
Breaking Changes
- Removed Endpoint:
GET /cluster/statuswas removed. UseGET /cluster/metricsinstead.
[0.2.3] - 2026-03-13
Fixed
- Hot-Reload Audio Enablement: Changing a camera from
audio: falsetoaudio: truenow reapplies stream settings without deleting and re-creating the camera configuration. - Live Streaming Consistency on Reload: Stream parameter updates now restart active live stream sessions at the hub layer, including non-recording cameras used only for HLS/RTSP delivery.
- RTCP Resilience: Improved handling of malformed short RTCP packets from unstable camera firmware to reduce unnecessary stream interruptions.
[0.2.2] - 2026-03-08
Added
- Storage-Aware Cluster Failover: Nodes now report disk usage and automatically switch to
Drainingon low free space (<=8%), returning toNormalafter recovery (>=18%). - Manual Drain/Resume Controls: Added API and dashboard actions for forcing
Draining/Normalwith manual override behavior. - Extended Cluster Metrics API:
/cluster/metricsnow includes per-node storage usage and storage lifecycle state. - CentOS 10 Packaging Support: Added build support for CentOS/RHEL 10 packaging pipeline.
- TLS for Inter-Node Cluster Traffic: Added TLS protection for internal communication between cluster nodes.
Changed
- Cluster Placement Policy: Scheduler excludes
Drainingrecorder nodes from new camera assignments and rebalances to available healthy nodes. - Cluster Topology UI: Node cards now show lifecycle badge (
Online/Draining/Offline), storage usage progress bar, and a draining node summary. - Metrics Architecture: Refactored metrics into modular collector/registry/server components, and moved
cluster_nodes_*updates to on-demand evaluation during/metricsscrapes.
Fixed
- Leader Election on Demotion: Improved leader transition behavior when demote operations occur.
- Offline Safety Checks: Cluster API now rejects role and storage-state updates for offline nodes.
[0.2.1] - 2026-03-04
Added
- Cluster Node Roles: Added role-based node behavior (
recorder/streamer) with runtime updates through cluster API and visibility in cluster status. - Credential Hashing for Internal Auth: Added support for
sha256:-prefixed credentials insecurity.users(userandpass). - Cluster Stream Placement Controls: Added role-aware stream handling with per-camera cluster assignment and protocol-specific serving decisions.
Changed
- Cluster HLS/RTSP Policy Propagation: Scheduler and runtime now consistently apply per-camera
hls,rtsp, andalwaysRemuxsettings in cluster mode. - HLS Behavior Controls: Clarified precedence between global and per-camera remux options and enforced camera-level HLS disable rules.
Fixed
- Recorder Restart Reliability: Improved restart behavior after runtime camera state changes and reassignment cycles.
- License Reload Safety: Added stronger validation during hot-reload to prevent invalid license state activation.
[0.2.0] - 2026-01-10
Added
- High Availability Clustering: Completely new distributed architecture based on the Raft consensus algorithm with an embedded key-value store for replicated state.
- Shared State: All nodes maintain a consistent state of camera assignments and node health.
- Automated Failover: Leader automatically detects dead nodes (heartbeat timeout > 15s) and redistributes their cameras to healthy nodes.
- Internal Proxying: API requests sent to Follower nodes are transparently proxied to the Leader using internal HTTP client.
- Auto-Join: New nodes with empty state and configured
peersautomatically discover and join the cluster as Learners. - Zero-Downtime Migration: Cameras can be moved between nodes dynamically via API.
- Cluster Monitoring:
- New
/cluster/statusendpoint showing real-time health (Online/Offline) of all nodes. - Integration with Frontend Dashboard to visualize cluster health.
- New
- Storage Backend:
- Embedded key-value store: High-performance local persistence for Raft log and state machine snapshots.
- Compaction: Automatic log purging and snapshotting to prevent disk overflow.
0.1.x — earlier releases
[0.1.16] - 2026-02-21
Fixed
- Progressive MP4 Playback: Improved standard MP4 segment structure so playback starts immediately without waiting for full file download.
- fMP4 Atom Compatibility: Aligned fMP4 atom layout and fragment headers with ffmpeg-style output (
ftyp,moovordering,tfhd/trunflags) for better player compatibility.
Improved
- fMP4 Validation Accuracy: Refined box-level verification workflow to compare atom order and key fragment metadata against ffprobe reference output.
[0.1.15] - 2026-02-20
Added
- Dual Recording Format: Added configurable recording format selection via
record.formatwith support for both fragmented MP4 (fMP4) and standard MP4. - Standard MP4 Recording: Added standard MP4 output mode for archive recording with support for H.264/H.265 video and AAC/G.711 audio tracks.
Changed
- Recorder Architecture: Unified recorder implementation into module-based writers for cleaner format handling and easier extension.
[0.1.14] - 2026-02-20
Added
- Fast Seeking: Implemented
mfra(Movie Fragment Random Access) atom at the end of MP4 files for instant seeking in supported players (VLC, QuickTime, Web).
Optimized
- File Finalization: Improved file closing process to correctly append the Random Access Table.
[0.1.13] - 2026-02-20
Fixed
- Seeking & Progressive Playback: Fixed FMP4 structure (
trunandtfhdatoms) to correctly support seeking and progressive playback. - MP4 Compatibility: Changed
brandfromiso5toisomfor broader player compatibility. - File Duration: Fixed duration update (
mvhdatom) even when recording terminates unexpectedly.
[0.1.12] - 2026-02-14
Optimized
- Performance: Disabled high-frequency metrics to reduce lock contention under high load (>1000 cameras).
- Disk I/O: Optimized MP4 header updates to reduce disk I/O operations during recording.
[0.1.11] - 2026-02-10
Fixed
- HLS Stability: Fixed infinite playlist restart loop when upstream RTSP stream reconnects.
- Logging: Reduced log verbosity for cleaner production logs.
[0.1.10] - 2026-02-05
Fixed
- Audio Support: Fixed G.711 (PCMU/PCMA) audio support by updating the RTSP client library.
- Error Handling: Improved error reporting for RTSP connection failures.
[0.1.9] - 2026-01-28
Added
- Audio Codecs: Added support for G.711u (PCMU) and G.711a (PCMA) codecs.
Fixed
- Video Synchronization: Fixed A/V desync issues on streams with unstable timestamps.
[0.1.8] - 2026-01-15
Fixed
- Timestamp Handling: Improved handling of non-monotonic timestamps from certain camera models.
[0.1.7] - 2025-12-30
Added
- Stream Security (Internal Auth): New
securityconfig block with internal user definitions, IP/CIDR filters and per-action permissions (publish,read,api,metrics) for unified access control across the system. - Protected Live Streams & Metrics: When
security.usersis not empty, all live-read endpoints (RTSP, HLS, WebSocket) now requirereadpermission and the/metricsendpoint requiresmetricspermission, with support for anonymoususer: anyand IP-based exemptions (for example, local Prometheus scrapers).
Changed
- Backwards-Compatible Defaults: If the
securityblock is omitted or theuserslist is empty, all endpoints continue to work without authentication, preserving behavior from previous versions.
[0.1.6] - 2025-12-28
Added
- Per-process Disk I/O Metrics (Linux): New Prometheus metrics sourced from
/proc/self/ioto track read/write bytes per ForgeVis process:process_io_read_bytes_total,process_io_write_bytes_total,process_io_cancelled_write_bytes_total. - Grafana I/O Panel: Updated default Grafana dashboard with a new "Process Disk IO (Read/Write MB/s)" panel for visualizing per-process disk throughput and correlating it with
Frames Lostand broadcast lag.
[0.1.5] - 2025-12-24
Added
- Low-Latency HLS (LL-HLS): Native Low-Latency HLS mode with fMP4 segments and HLS parts. New
hls.variant: "lowLatency"with configurablesegmentDurationandpartDuration, optimized for hls.jslowLatencyMode.
Fixed
- B-Frame Support: Implemented B-frame support in fMP4. Added
ctts(Composition Time to Sample) atom and frame reordering logic (reordering buffer) to correctly calculate Presentation Time Stamp (PTS) and Decode Time Stamp (DTS). This eliminates video stuttering on streams with B-frames. - Synchronization (tfdt drift): Fixed time drift in
tfdtatom. Fragment decode base time is now derived from the actual DTS of the first frame instead of accumulated durations, preventing desync during long recordings.
[0.1.4] - 2025-12-24
Fixed
- Archive Recording: Fixed issue with zero duration in
mvhdheader of fMP4 files. Players now correctly display duration and support seeking. - Synchronization: Fixed hardcoded frame duration issue. Duration is now calculated dynamically based on timestamps, eliminating A/V desync.
- Performance: Moved file metadata updates to a background process to prevent delays between segment recordings.
[0.1.3] - 2025-12-23
Fixed
- Archive Seeking: Critical fix for fMP4
trunatom flags and per-sample flags. Browsers can now correctly seek to keyframes within fragments. - WebSocket Stability: Fixed infinite reconnection loops in the React player when switching between Live and Archive modes.
[0.1.2] - 2025-12-22
Added
- Frontend Redesign: Migrated UI components (CameraList, ArchiveList) to Bootstrap 5 for a cleaner, responsive look.
- Player Improvements:
- Improved fullscreen behavior with aspect ratio preservation.
- Custom timeline control.
Fixed
- Archive Seeking: Fixed precise seeking in archive playback.
[0.1.1] - 2025-12-21
Added support for Low-Latency Streaming via WebSocket (MSE).
Streaming
- Low-Latency MSE (WebSocket)
- Direct streaming of fMP4 fragments to browser via WebSocket
- Ultra-low latency (< 1 second)
- Media Source Extensions (MSE) support
[0.1.0] - 2025-12-20
Major release introducing Management API, Graceful Shutdown, and Legacy System Integration.
Management API
- HTTP Control Interface
- New Management API server running on port 9997 (configurable)
/healthendpoint for system monitoring/cameras/startendpoint to bulk start all configured cameras/cameras/stopendpoint to bulk stop all cameras- CORS support for web integration
Reliability
- Graceful Shutdown
- Proper handling of SIGINT (Ctrl+C) and SIGTERM signals
- Ensures all active recordings are finalized and saved to disk before exit
- Clean shutdown of RTSP and HLS servers
[0.0.10] - 2025-12-19
Added support for ONVIF events integration.
ONVIF Integration
- Event Monitoring
- Subscribe to camera events via ONVIF PullPoint
- Support for Motion Detection, Digital Inputs, Line Crossing, and more
- Configurable polling interval and event topics
- Automatic credential extraction from RTSP URL
- Per-camera ONVIF configuration in
camera.yaml
[0.0.9] - 2025-12-16
HLS compatibility fixes for working with hls.js default settings.
HLS Improvements
- Dynamic Playlist Parameters
#EXT-X-TARGETDURATIONnow derived fromsegmentDurationconfig#EXTINFuses config value instead of hardcoded duration- Removed
#EXT-X-STARTtag for compatibility with default hls.js settings - Support for configurable segment durations (1s, 4s, etc.)
Bug Fixes
- Playlist Compatibility
- Fixed playlist generation to work with hls.js without custom configuration
- Playlists now match ffmpeg format
- Removed tags causing issues with default hls.js configuration
Configuration
hls.segmentDurationparameter now affects:#EXT-X-TARGETDURATIONin playlist#EXTINFfor each segment- Actual duration of created segments
[0.0.8] - 2025-12-12
Production monitoring and metrics system with Grafana dashboard.
Monitoring & Observability
Prometheus Metrics
- 14 aggregate metrics for system monitoring (scales to 2000+ cameras)
- HTTP
/metricsendpoint (default port 9998) - Automatic hostname labeling for multi-server deployments
- Camera status: registered, recording, errors
- Stream metrics: active connections (main/sub streams)
- Performance: video frame rate, broadcast buffer status
- HLS metrics: active muxers, segment size
- Error tracking: recording errors by type, broadcast lag events
- System metrics: process memory (RSS)
- CORS support for cross-origin access
File Logging
- Daily log rotation with
tracing-appender - Separate files:
forgevis-info-YYYY-MM-DD.log,forgevis-error-YYYY-MM-DD.log - Configurable log levels (trace, debug, info, warn, error)
- Console output with proper filtering
- Cleaned up duplicate logs from internal components
- Daily log rotation with
Grafana Dashboard
- Pre-built JSON dashboard with 8 visualization panels
- 4-row layout: Camera Status, Performance, Resources, Errors
- Multi-server support with hostname filter dropdown
- 5-second auto-refresh for real-time monitoring
- Memory usage in MB (proper unit conversion)
- Complete metrics reference in documentation
Bug Fixes
HLS Muxer Metrics
- Fixed HLS muxer counter not decrementing on cleanup
- Implemented proper lifecycle tracking with Drop trait
- Added shutdown channel to stop tasks cleanly
- Prevented duplicate metric decrements with coordination flag
- Ensures accurate count of active HLS muxers
Memory Management
- HLS muxer tasks now properly terminate on cleanup
- Fixed memory leak from orphaned background tasks
- Shutdown signal propagates to all muxer components
Documentation
- Monitoring Guide (English & Russian)
- Complete Prometheus setup instructions
- Grafana dashboard import guide
- All metrics with descriptions
- Multi-server deployment patterns
- Troubleshooting memory and performance
Configuration
- New Settingsyaml
logging: directory: /var/log/forgevis level: info console: true metrics: enabled: true address: :9998 allowOrigin: '*'
[0.0.7] - 2025-12-11
HLS playlist naming convention changed for better API consistency.
HLS Improvements
- Playlist Naming
- Video playlist:
video.m3u8→media_0.m3u8 - Audio playlist:
audio.m3u8→media_1.m3u8 - Video init segment:
init_video.mp4→init_media_0.mp4 - Audio init segment:
init_audio.mp4→init_media_1.mp4 - Video segments:
video_{N}.m4s→seg_media_0_{N}.m4s - Audio segments:
audio_{N}.m4s→seg_media_1_{N}.m4s - Centralized HLS naming constants in a single internal module
- Video playlist:
[0.0.6] - 2025-12-11
RTSP UDP transport support.
RTSP Improvements
- UDP Transport
- UDP transport support alongside TCP (interleaved)
- Configurable UDP ports for RTP/RTCP (
udp_rtp_port,udp_rtcp_port) - Shared UDP sockets for multiple clients (efficient resource usage)
- Automatic transport detection from client SETUP request
- Works with VLC, ffplay, and other RTSP clients
Configuration
- New RTSP Settings
rtsp.udp_rtp_port(default: 8000) — UDP port for RTP packetsrtsp.udp_rtcp_port(default: 8001) — UDP port for RTCP packets
[0.0.5] - 2025-12-09
AAC audio support for recording and streaming.
Video Codecs
- H.265/HEVC Support
- Full H.265 codec support alongside H.264
- Automatic codec detection (H.264 or H.265)
- H.265 RTP packetization (RFC 7798)
- VPS/SPS/PPS parameter extraction
- HVCC format recording
Audio Recording
AAC Audio Codec
- Automatic AAC audio stream detection via RTSP
- AudioSpecificConfig parsing (sample rate, channels)
- Dual-track fMP4 recording (separate video and audio tracks)
- Perfect audio/video synchronization in recordings
Audio Configuration
- Per-camera audio enable/disable (
audio: true/false) - Global default audio setting
- Automatic fallback to video-only if audio unavailable
- Per-camera audio enable/disable (
Audio Streaming
RTSP Audio Restreaming
- Multi-track RTSP sessions (video + audio)
- RFC 3640 AAC RTP payload format
- RTCP Sender Reports for both tracks
- Session reuse for SETUP requests (VLC compatibility)
- Works with VLC, ffplay, and other RTSP clients
HLS Audio Streaming
- Multi-variant playlist architecture (master playlist)
- Separate video and audio playlists
- Individual video/audio segment files (video_N.m4s, audio_N.m4s)
- Separate initialization segments (init_video.mp4, init_audio.mp4)
- Standard HLS.js compatibility
Technical Implementation
Audio Pipeline
- retina RTSP client with audio depacketization
- AAC frames broadcast through StreamHub
- Dual-channel architecture (video_receiver + audio_receiver)
- Sample duration calculation (1024 samples per AAC frame)
FMP4 Writer Enhancement
- Dual-track mdat/moof box writing
- Separate audio trak in moov box
- Audio sample table (stbl) with proper timescale
- AudioSampleEntry (mp4a) with esds descriptor
Code Quality
- Removed unused variables and functions
- Cleaned up dead code (kept public API functions)
- Zero compilation warnings
- Refactored CameraRecorder (removed unnecessary parameters)
[0.0.4] - 2025-12-09
Hot reload and advanced stream management.
Hot Reload System
Configuration Watcher
- Automatic monitoring of
config.yamlandconf.d/*.yamlfiles - 500ms debouncing for rapid file changes
- Real-time camera configuration updates without restart
- Automatic monitoring of
Dynamic Camera Management
- Add/remove cameras on-the-fly
- Update camera sources (URL changes)
- Enable/disable recording per camera
- Graceful stream cleanup on camera removal
Stream Management Architecture
Centralized Camera Registry
- StreamHub as single source of truth for camera configurations
- Automatic registration/unregistration during hot reload
- Support for main stream and substream configurations
On-Demand Streaming
- HLS muxers created only when client requests stream
- RTSP streams for registered cameras only
- Validation before muxer creation (eliminates unnecessary resource allocation)
- Proper 404 responses for non-existent cameras
Stream Lifecycle Management
- CancellationToken-based stream termination
- Immediate cleanup when camera is removed from config
- Automatic stop of both main and substream on camera deletion
- RTSP client disconnection on stream cancellation
Technical Improvements
- Removed configuration duplication from HLS/RTSP servers
- Unified stream validation through StreamHub
- Better error handling for missing cameras
- Thread-safe camera registry
Fixes
- Fixed RTSP streams continuing after camera deletion
- Fixed HLS muxer creation for non-existent cameras
- Fixed substream (/sub) on-demand creation
- Eliminated all compilation warnings
[0.0.3] - 2025-12-08
Automation and integration through Event Hooks.
Event Hooks
Segment Event Hooks
runOnSegmentCreate- Triggered when segment recording startsrunOnSegmentComplete- Triggered when segment recording finishes- Environment variables: SF_PATH, SF_SEGMENT_PATH, SF_SEGMENT_DURATION, RTSP_PORT
Integration Capabilities
- Discord/Slack/Telegram notifications
- Cloud storage upload (S3, GCS, Azure)
- Thumbnail and preview generation
- Prometheus metrics export
- Automatic cleanup of old recordings
- Custom video processing workflows
Technical Implementation
- Asynchronous hook execution without blocking recording
- Success/error logging for hook execution
- Shell command and script support
- Comprehensive documentation with integration examples
[0.0.2] - 2025-12-07
HLS streaming and web interface implementation.
[0.0.1] - 2025-12-04
Initial development release of ForgeVis video recorder and restreaming server.
Recording Features
Fragmented MP4 Recording
- Full ISO BMFF (Base Media File Format) implementation
- fMP4 format with proper box structure (ftyp, moov, moof/mdat)
- AVCC format for H.264 (4-byte length prefixes)
- Files playable while recording
- 2-second fragment intervals for low-latency access
Segment Management
- Time-based segmentation (configurable, default 60 seconds)
- Automatic directory creation
- Path templates with
{camera_id}and{timestamp}placeholders - Sequential numbering with 6-digit padding
Streaming & Distribution
StreamHub Architecture
- Single RTSP connection per camera
- Broadcast (fan-out) pattern for distributing frames to multiple consumers
- 1000-frame buffer for multiple consumers
- Automatic SPS/PPS extraction
- Transparent frame distribution to recorder and restream server
RTSP Restreaming Server
- Full RTSP protocol implementation (OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN)
- SDP generation with H.264 parameters (sprop-parameter-sets)
- RTP over TCP (interleaved mode)
- H.264 RTP packetization (RFC 6184 compliant)
- Multiple clients per camera supported
- Automatic NAL unit parsing from AVCC format
Configuration
- YAML Configuration System
- Storage settings (segment duration, path template, format)
- RTSP server configuration (port)
- Per-camera configuration (source URL, enabled flag)
- Credentials embedded in RTSP URLs
Technical Implementation
- Pure Rust: No system dependencies required
- Zero-Copy: Efficient frame handling
- Proper H.264 Handling: AVCC format storage with correct flags
Performance
- Single connection per camera prevents overload
- Broadcast channel supports unlimited subscribers
- Lag handling for slow consumers (skip frames if needed)
- Efficient RTP streaming (90kHz clock, proper timing)
Known Limitations
- H.264 only (no H.265/HEVC support yet)
- Video only (no audio recording)
- TCP interleaved only (no RTP over UDP)
- No DASH output
- No metrics/monitoring UI
Tested Configurations
- ✅ Real RTSP IP camera (1920x1080, 25fps, H.264)
- ✅ Recording with proper MP4 structure
- ✅ Restreaming verified with ffplay and VLC
- ✅ Concurrent recorder + multiple RTSP clients
- ✅ Files playable in VLC/ffplay while recording