DVR / Archive¶
Since version 1.13, Perfect Streamer provides a built-in DVR — a persistent on-disk stream archive that operates in parallel with regular OTT delivery (HLS / DASH). The archive is written automatically, without a separate process, and is played back through the same OTT URLs as live — the only difference is the query parameter.
Features:
Recording of each OTT stream to the archive on the selected storage.
HLS, DASH and Low-Latency HLS playback of the archive (VOD) on the same URLs as live (DASH and LL-HLS — on CMAF, in OTT/HLS/LL-HLS/LL-Dash).
Subtitle support (WebVTT) — written alongside TS chunks.
Multiple storages — a stream is bound to one; different streams may write to different disks.
Automatic cleanup by retention time and by disk usage.
EPG-aligned VOD — archive delivery by EPG-event reference.
Adaptive VOD — supported for adaptive groups.
DVR requires no separate license. It is enabled per stream by adding a storage binding.
DVR does not replace live broadcasting. If a stream has an archive, the client receives a live playlist with the same behaviour as without DVR. The archive starts playing only when the client explicitly requests VOD mode via a URL query parameter (see below).
Storage configuration¶
A storage is a record in the Configuration / DVR Storage section. Each record describes one on-disk directory into which PSS writes archive files. A stream uses one storage.
When adding a storage, the following are configured:
Name — display name.
Dir Path — path to the on-disk directory. After record creation, the path cannot be changed — to move the archive to another disk, delete the record and add a new one with the new path. Existing files are not touched on disk when the record is deleted.
Max Usage, % — disk usage threshold (default 90 %). When exceeded, size-based cleanup activates (see below). Minimum 1 %, maximum 100 %.
Cleanup Interval, sec — cleanup task period (default 10 sec). On each tick, everything older than the stream retention depth is trimmed first; then, if Max Usage is exceeded, old chunks are removed.
Disk Pressure Grace, sec — how many seconds Used % must continuously exceed Max Usage before Size-based cleanup starts (default 60 sec). Filters out short spikes.
Disk Pressure Cut, sec — upper bound for one cleanup tick: how many seconds of video per stream may be deleted at once (default 300 sec). The remainder is carried over to the next tick.
Disk Emergency Bytes — free-space threshold below which the storage transitions to Error state and recording stops (default 2 GiB). Automatic recovery occurs when free space ≥ 2 × this value.
Alarm Disk-Full Hysteresis, % — the margin below Max Usage down to which size-based cleanup lowers the fill, so that Used % does not fluctuate right at the threshold (2 % by default).
Most default values suit typical installations; adjustments are typically required only for Max Usage and Dir Path.
It makes sense to create one storage per disk. If several records with different subdirectories are set on the same disk, they will compete for free space — the disk is shared, but cleanup is per storage.
Binding a stream to a storage¶
In the Stream / OTT settings, a DVR section appears:
Storage — drop-down list of available storages; 0 means “archive disabled for this stream”.
Storage Hours — archive depth for this stream, in hours, from 1 to 2160 (up to 90 days). Chunks older than this value are deleted on each tick of the cleanup task (Rolling cleanup).
Storage Min Hour — lower protection threshold (hours). The cleanup task never deletes chunks younger than this, even under Max Usage pressure. Use it when business logic requires a guaranteed fresh recording, e.g. “the last 2 hours are always present”.
Changing Storage on the fly:
setting 0 — disables archiving; on-disk chunks are preserved, new ones are not written. VOD sessions on this stream begin returning 404;
selecting a different storage — the stream is detached from the old one and starts writing to the new one. Files from the old disk are not migrated.
Changes to Storage Hours and Storage Min Hour apply immediately — the cleanup task picks up the new value on the next tick.
After configuration, the stream begins writing the archive automatically as soon as it enters Running state.
VOD: archive playback¶
The archive is played through the same URLs as live HLS / DASH broadcasting (see the OTT service section) — only the query parameter differs.
URLs and parameters¶
URLs for HLS, DASH and Low-Latency HLS (DASH and LL-HLS — on CMAF, require OTT/HLS/LL-HLS/LL-Dash mode):
Without query parameters — regular live with a sliding window. When the t parameter is present — VOD mode.
Parameter |
Purpose |
|---|---|
|
VOD start time (Unix epoch, sec). t=0 — from archive start. The presence of t (even t=0) enables VOD mode. |
|
VOD window duration, sec. d=0 or parameter absent — “up to the current moment”. Meaningful only together with t. |
|
EPG-aligned VOD: the server itself locates the EPG event active at the given moment and takes its start and duration as window bounds. Incompatible with t and d (server-side override). See below. |
|
Standard live parameters (see OTT service); |
Behavior by t and d:
t |
d |
Window |
404 condition |
|---|---|---|---|
none |
— |
live (sliding window) |
— |
0 |
none / 0 |
[archive start, now] |
DVR not bound |
0 |
> 0 |
[archive start, +d] |
DVR not bound |
> 0 |
none / 0 |
[t, now] |
DVR not bound |
> 0 |
> 0 |
[t, t+d] |
DVR not bound |
Boundary normalization:
t earlier than archive start — start is automatically aligned to the first available chunk (cleanup may have already trimmed). This is not 404 — what remains is served.
t in the future or window entirely earlier than the archive — empty but valid playlist (HLS: header only +
EXT-X-ENDLIST; DASH:@type="static",mediaPresentationDuration="PT0S").Chunks are selected strictly by the chunk start time falling within the half-open interval [t, t+d) — there are no partial segments.
VOD on a stream without an archive (or whose storage is in Error) — 404 immediately on the master playlist / MPD. No session is created.
Error text in 404 body (visible in server logs and HTTP body):
VOD: stream not running— the stream is in the config but not in Running.VOD: no DVR archive— no storage is set on the stream, or the storage is in Error.VOD: DVR detached— the stream was detached from the storage between requests.VOD: EPG event not found— no event found for?epg=.
VOD HLS playlist — closed (the player sees the duration and can seek):
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:5.000,
...
#EXT-X-ENDLIST
These markers are absent in the live playlist — that is the only difference.
VOD DASH MPD — static: @type="static", fixed mediaPresentationDuration, explicit <SegmentURL>. Live DASH remains @type="dynamic".
If the selected interval has gaps in the archive (e.g. a recording restart or cleanup trimmed a middle portion), the DASH MPD is automatically split into multiple <Period> — one per contiguous track. Players (VLC, dashjs, Shaka) seek across period boundaries without special configuration.
EPG-aligned VOD¶
If the stream is bound to an EPG source (fields EPG Source and EPG Channel in the stream settings), the client may request the archive by a moment that falls within an EPG event:
http://host:port/hls/stream/login/password/index.m3u8?epg=1778500000
http://host:port/dash/stream/login/password/index.mpd?epg=1778500000
The server locates the EPG event active at the given epoch and substitutes its start and duration as the VOD window bounds. Useful for program catalogs: the UI knows the event time but is not required to compute exact sec/ms boundaries.
If the stream is not bound to EPG or no event is active at that moment — 404 ``VOD: EPG event not found``. Parameters t and d are ignored when epg is present.
Adaptive streams¶
Adaptive groups (see HLS Adaptive Multistream) support the same VOD parameters:
http://host:port/hls/adaptive/group/login/password/index.m3u8?t=0
http://host:port/dash/adaptive/group/login/password/index.mpd?t=0
Only variants that have a DVR storage configured are included in the master playlist (HLS). Variants without DVR are skipped (VOD: variant N has no DVR appears in the log); assembly from the remaining variants proceeds.
In the DASH variant, each quality becomes a separate <Representation> inside common <Period>; the player may switch between qualities without reopening the manifest.
Player behavior¶
VOD HLS / DASH from the archive plays in standard players: VLC, hls.js, dashjs, Shaka, ffmpeg. Timeline seeking works.
If the player requests a segment that cleanup has already removed by that moment, the server returns 404 for that segment (not 500). VLC, hls.js, dashjs skip such a segment and continue playback from the next one.
Additional capabilities:
Active session protection: while a VOD session is open, cleanup does not delete chunks within its window (see below).
Live-edge bridge: if a client in a VOD session requests a segment beyond the right boundary of the VOD window — e.g. moves forward along the timeline after reaching the archive end — the server automatically serves the segment from live memory. No redirects and no re-authorisation.
Playlist cache: on repeated requests for the same VOD
index.m3u8/index.mpd, the server returns a byte-for-byte identical response — without rebuilding. Suitable for a CDN in front of PSS.
Subtitles in the archive¶
If the stream carries subtitles (DVB Subtitling, Teletext, or WebVTT) and the OTT WebVTT option is enabled in the stream settings, subtitles are archived in parallel with the TS chunks — as .vtt files next to .ts.
In live mode, the master playlist contains EXT-X-MEDIA:TYPE=SUBTITLES; in VOD mode the server returns a VOD subtitle playlist (with ENDLIST) and .vtt segments at the same URLs.
Notes:
HLS: the
X-TIMESTAMP-MAPheader is preserved at the start of every.vtt(required by the HLS specification).DASH: the
X-TIMESTAMP-MAPheader is stripped on the fly (it is bound to the absolute PCR and conflicts with the DASH anchor; otherwise VLC shows empty subtitles).In an adaptive group: if the active sub-stream does not write subtitles, VTT segments return 404. On the next variant, the player may receive subtitles again.
Subtitles can be disabled either via the OTT WebVTT option on the stream, or by not enabling HLS in OTT/HLS mode at all.
Cleanup and retention¶
PSS uses four cleanup strategies that run on every cleanup task tick (by default every 10 sec):
Rolling cleanup (per-stream): for each stream, chunks older than Storage Hours are deleted. Runs always, even with a half-empty disk.
Size-based cleanup (storage-wide): when Used % on disk continuously exceeds Max Usage for Disk Pressure Grace seconds, the oldest chunks are trimmed proportionally across all streams bound to the storage. Cleanup proceeds in batches of Disk Pressure Cut sec of video per stream per tick. Never deletes chunks younger than Storage Min Hour.
Emergency disk-full cut: when free space drops below Disk Emergency Bytes, cleanup runs aggressively and may delete even session-protected chunks. Recording stops until free space recovers with a hysteresis of × 2.
Orphan scan: files that are not accounted for in the index may remain on disk (after an abrupt shutdown of PSS). The collector walks through the stream subdirectories and deletes such “forgotten” files — the first run shortly after the service starts, then once an hour and whenever disk pressure is triggered. As protection against a race with writing, files younger than 60 sec are skipped.
Note. Cleanup tasks run in the background; the user normally does not have to act. If the archive grows faster than it is trimmed, lower Storage Hours on the streams or raise Disk Pressure Cut.
Active VOD session protection¶
When a client opens a VOD session, a “protection slot” with the window start time is registered on every storage involved in its window. Rolling and size-based cleanup do not touch chunks within the open session’s window. The slot is released automatically when the session closes (FIN, timeout).
This means:
If a client holds a VOD session for a long time, it is guaranteed to be able to seek to any moment within its window — chunks will not “vanish from under it”.
Cleanup by Max Usage may temporarily fail to bring Used % to the desired threshold while a session is active; once the client leaves, cleanup catches up.
Emergency disk-full cut and Storage Min Hour bypass the protection: if the disk is near zero free space, chunks are deleted and the client receives 404 for the affected segments (the player skips them).
After a PSS restart, protection slots disappear — cleanup resumes immediately.
Multiple storages¶
Any number of DVR Storage records may be created — one per directory / disk. Streams are bound to different storages independently. Cleanup and thresholds (Max Usage, Disk Pressure) operate per-storage.
Use cases:
Tiering by value: a fast-SSD storage for premium channels with deep retention, a high-capacity HDD storage for the rest.
Dedicated disk for the archive: so that DVR recording does not compete with the system disk or temporary files.
Project separation: one disk for channel set #1, another for #2 — simplifies migration and audit.
Changing the stream binding (the Storage field in Stream / OTT) switches the recording on the fly. Old files remain untouched on the prior disk — they may either be deleted manually, or recording may be reattached by switching the stream back.
Storage state and monitoring¶
The Data / DVR Storage List section (and the API GET /data/dvr-storage-list) reports per storage:
State — Ready / Error (plus the intermediate Not Ready for a just-added storage).
Total / Free / Used Bytes — free and used space on the disk.
Used % — current usage percentage.
Archived Bytes — total size of chunks accounted for in the index of all bound streams (excluding orphan files).
Pressure Since Sec — moment (epoch) when Used % first exceeded Max Usage in the current pressure episode; 0 means “no pressure now”.
Active Task — the maintenance background operation running right now: gc-orphans (removal of orphaned files), disk-pressure-trim (trimming under disk pressure) or none, and how many seconds it has already been running. Short-lived (< 2 s) operations do not flicker in the UI.
Last cleanup — a summary of the most recent cleanup runs: how long ago the previous orphaned-file collection ran (and how many were deleted) and the last trim under disk pressure (how much space was freed).
Attached streams — the list of attached streams; for each one the name, the recording-in-progress indicator (active), the configured archive depth (retention-hours) and the actual fill (archived-sec and archived-bytes) are shown. The sum of archived-bytes across the streams equals the Archived Bytes of the whole storage.
States:
Ready — the directory is available, recording and cleanup proceed normally.
Error — the storage directory is unavailable (unmounted, no permissions, file system error) or there is critically little free space on the disk; recording is stopped. Recovery is automatic — as soon as the directory is available again and enough free space appears.
If a storage has gone into Error, check whether the archive directory is mounted and whether there is free space on the disk — PSS does not exit this state on its own until the problem is physically resolved.
Archive fill over time:
At the stream level (Data / Stream) the storage-gap-percent metric is available — the proportion of time gaps in the accumulated archive: 0 % means continuous recording, higher values mean there are gaps in the archive (source restarts, segments trimmed by cleanup).
The
GET /data/dvrstatendpoint returns a histogram of archive fill by time intervals, with markers for recording events (source start/stop, PMT change, scrambling switches, index rebuild, cleanup run) and subtitle activity — for rendering the DVR-archive timeline in the admin interface.
Protection against accidental data loss¶
Several admin-interface operations lead to loss of recorded archive or cessation of VOD delivery. Before execution, the admin UI displays a modal confirmation:
Deleting DVR Storage — all bound streams lose VOD access; on-disk files remain but are unreachable through PSS without the storage record.
Switching the stream to another storage — VOD over the old archive stops working.
Detaching the stream from storage (Storage = 0) — same effect.
Reducing Max Usage — may trigger size-based cleanup and delete old chunks.
Reducing Storage Hours / Storage Min Hour — may push part of the archive out of the rolling window.
This is a deliberate product decision: the operation is permitted but requires confirmation, and deleted files remain on disk (can be restored from backup). Placing the archive on a separate file server / RAID substantially lowers the risk of irreversible loss.
Current-version limitations¶
A VOD session opened by a client does not track the live edge — if new chunks have been added to the archive during the session, the client must re-request the playlist (standard behavior of all HLS / DASH players).
Assigning one storage per disk is the recommended layout — multiple records with different subdirectories on the same disk compete for free space.
Segments are addressed by hash (HLS over MPEG-TS) or by the
$Number$.m4stemplate with a sharedinit.mp4(live DASH over CMAF) / explicit<SegmentURL>to.m4s(VOD DASH). Changing the chunk size between live and VOD does not require reopening the URL.The orphan scanner runs hourly; to accelerate, restart PSS.