◆ ForgeVis
Skip to content

Playback ​

The playback server answers one question: what was this camera recording at this moment? A request names a camera and an instant, and the answer starts there.

Its own listener, alongside HLS rather than inside it. Live and archive work differently: live is a playlist that keeps growing, the archive is a finished recording addressed by time. Playing back the archive does not depend on the muxer that serves live viewers.

Enabling it ​

yaml
playback:
  enabled: true
  # Address of the playback listener.
  address: :9996
  # Value of the Access-Control-Allow-Origin header, so a page served from
  # elsewhere may play recordings.
  allowOrigin: '*'

Behind the same read permission as HLS: whoever may watch a camera may watch what it recorded. The management API and its credentials stay out of this — a browser must never hold keys that reconfigure a recorder.

Requires fragmented recordings

Playback needs record.format: fmp4. A progressive recording keeps its header at the end of the file, written when the file is closed, so nothing at the front says how to reach a given moment — playing from an arbitrary instant would mean rebuilding that header, which is a remux rather than a seek. With record.format: mp4 the answer is an explicit error saying so; use export instead.

Asking for a moment ​

GET /playback/get?path=<camera>&start=<RFC 3339>&duration=<seconds>&format=<mp4|fmp4>
ParameterMeaning
pathcamera identifier — the archive folder the recorder writes into
startwhere to begin, RFC 3339 (2026-08-21T14:23:05+03:00)
durationhow much to serve, in seconds. Absent means to the end of the recording
formatmp4 for a browser, fmp4 for tools. Default fmp4
bash
curl -o clip.mp4 \
  'http://recorder:9996/playback/get?path=cam001&start=2026-08-21T14:23:05Z&duration=60&format=mp4'

The answer carries X-Playback-Start: the instant it actually begins. That is rarely the instant asked for — playback snaps back to the nearest key frame, and a request landing in a gap is answered with the next recording — so the caller is told rather than left to assume its timeline marker is right.

Which formats, and why two ​

format=mp4 is a plain file: one header describing every sample, then the media. This is what a <video> element plays. Nothing is decoded or re-encoded — the media bytes are copied as they are, and only the description in front of them is rebuilt from the sample tables the fragments already carry.

format=fmp4 is the recording's own fragments, streamed. Smaller to produce and what tools like ffplay expect, but a browser handed fragments directly reads the whole answer before showing a frame: there is no index in the stream to seek by, so it looks for the end. Served as a stream — chunked, no length, no ranges — a browser plays it as it arrives, which is what Chrome does and what Safari declines.

The short version: mp4 for browsers and players, fmp4 for scripts and tools.

Seeking ​

The seek happens on the node holding the file. Every archive file opens with a Segment Index Box the recorder fills in when it closes the file: the offset, size and length of every fragment, and whether it starts on a key frame. Answering "start at 14:23:05" is therefore a few kilobytes read from the head of the file, a lookup, and a seek. The recording itself is never scanned and nothing is decoded.

A client doing the same over HTTP byte ranges would need the same table, but would have to fetch it first and work the arithmetic out for itself before it could ask for the bytes it wanted.

A recording still being written has no index yet — it is filled in at close — so its fragments are walked instead, reading box headers and stepping over the media. That costs a pass over a few kilobytes per fragment and keeps the most recent stretch of every camera reachable, which is the part someone reviewing an incident asks for first.

Where the answer starts ​

Playback begins at the last key frame at or before the instant, never mid-GOP: a decoder started elsewhere produces a few seconds of smeared picture. Landing a second or two early is what every player does when it seeks.

An instant with nothing recorded — a click in a gap, or before the archive begins — is answered with the next recording rather than with an error. That is more useful: if footage resumes a minute later, the viewer lands on it instead of hitting an empty answer. X-Playback-Start says where it actually landed.

What it does not do ​

One file per request. A window spanning two recordings is answered from the first of them; the caller asks again for the next. Stitching them into one continuous stream is a separate feature.

No live. The playback server serves what was recorded. For what is happening now, see HLS streaming.

Proprietary software.