Skip to content

HLS ingest

Sapsan follows someone else's HLS playlist in client (pull) mode: it polls the playlist, fetches the segments and feeds frames into the pipeline just like RTSP, SRT or MPEG-TS. That is how almost every aggregator, every third-party CDN and every partner who is not allowed to open UDP or SRT delivers a stream.

Configuration

streams:
  - name: chan1
    inputs:
    - hls:
        url: https://cdn.example.com/live/index.m3u8
Parameter Default Description
url playlist address exactly as the operator wrote it
headers none headers added to every request to the source: playlist, segments, keys
variant_mode single single — one variant of the master, ladder — the whole ladder
variant_bandwidth none which bitrate to match the variant against (nearest below); without it the highest BANDWIDTH wins
skew_threshold_ms 500 the skew at which the ladder is declared out of sync
skew_policy report what to do on a skewed verdict: report — keep going and show it, fallback_to_single — fall back to a single variant
connect_timeout_ms 5000 connection timeout
read_timeout_ms 15000 body timeout: playlist, segment, key
max_body_bytes 64 MiB size limit of a single response
peer_timeout_ms global how long to wait for frames before the source is considered lost

An omitted field means the server default, not "disabled".

The scheme can be written in three equivalent ways:

  • https://host/path.m3u8 — recognized by the path extension, a query string does not get in the way;
  • hls://host/path.m3u8 — same as http://;
  • hlss://host/path.m3u8 — same as https://.

Adding the source in the console

Open the stream, go to the Sources tab, press + Add Source and pick the HLS protocol. Only one field is required: URL, the playlist address.

Adding an HLS input

The rest is optional, and an empty field means the server default: connect, body and peer timeouts, the body size limit, the variant bitrate.

If the source requires a token, press Add header and fill in the name, then the value. The row becomes a real header the moment it gets a name: a header without a name is rejected by the server and never goes on the wire.

Request headers for the source

The Capture the whole ladder checkbox reveals Skew threshold and the When renditions are skewed choice. It is a separate checkbox rather than a consequence of the source serving a master playlist: capturing the ladder multiplies both the ingest traffic and the number of tracks in the stream, and on a stream with an archive it shortens the recording depth by the same factor.

Ladder capture and skew threshold

Do not forget to Save: the input reaches the server only on that button.

From then on the state of the source lives on the same tab. The card in the Source health block carries the ladder verdict and the worst skew:

Ladder verdict on the source card

The per-rendition breakdown — declared and actual skew, drift, unpaired segments and keyframe divergence — sits below it in the Rendition skew block:

Per-rendition skew breakdown

Start rule

Whatever was already in the playlist at connect time is history. Capture starts from the first segment that appears after the connection, and it behaves the same way on a reconnect.

There is deliberately no start-depth setting: otherwise every dropped connection would make the stream flush the accumulated window into the pipeline in seconds instead of broadcasting.

Authentication

Two ways, and they combine:

  • headersheaders, e.g. Authorization: Bearer <token>; they go with the playlist, variants, init segments, segments and keys of the same origin, and they never follow a redirect to a foreign origin;
  • credentials in the URL itself — plain Basic authentication done by the HTTP client.

Header values never reach the log: only their names are printed. The input address is logged in full and returned as is by the management API — only the operator knows which part of that address is the secret, and guessing would either leave the token in the query or cut away the very part the input is recognized by in the log.

Ladder and rendition skew

With variant_mode: ladder every variant of the master is captured. The highest BANDWIDTH variant becomes the reference, and the rest are compared against it segment by segment, by media sequence number. The verdict is exposed in the input statistics, field hls_ladder:

  • synced — the renditions run together, switching between them is safe;
  • skewed — the skew exceeded the threshold or the structure is broken: switching will jerk or desynchronize;
  • unmeasurable — nothing to compare, no segment pairs share a number.

Skew is measured two ways at once. Declared is the difference of EXT-X-PROGRAM-DATE-TIME in the markup, actual is the difference of the media time of the first frames. When the two disagree, it is the markup that is ill, not the stream, and it is the source owner who fixes it.

Note

Sapsan does not bring ladder variants onto a common timescale, and it does not repair bad content at all: a silent fix does not make a ladder switchable, it only hides the defect. So the skew is shown as a number and the operator decides.

The fallback_to_single policy drops the capture to a single variant on a skewed verdict — for some sources that is the only workable mode. The fallback lasts until the input is restarted.

Encryption

AES-128 (whole segment) and SAMPLE-AES (per frame, for H.264 and AAC) are supported. The key is fetched by the same client with the same headers as everything else and cached by address, so key rotation in the middle of a playlist works by itself — a new key has a new address.

A silent key server is an input error naming the key address and the response code; the segment body is not downloaded at all, since there would be nothing to decrypt it with.

CENC/DRM (Widevine, PlayReady, FairPlay) is not supported and is rejected by the KEYFORMAT name.

What is supported

Feature State
Media playlist: EXT-X-MEDIA-SEQUENCE, EXTINF, EXT-X-ENDLIST yes
Master playlist: variant selection, EXT-X-MEDIA renditions yes
fMP4 (EXT-X-MAP) and MPEG-TS segments yes
EXT-X-BYTERANGE yes
EXT-X-DISCONTINUITY, playlist recreation, falling behind the window yes
EXT-X-GAP yes, skipped without an error
EXT-X-PROGRAM-DATE-TIME — media time anchored to UTC yes
AES-128 and SAMPLE-AES (H.264, AAC) encryption yes
Ladder: capture of every variant, skew measurement yes
LL-HLS: EXT-X-PART, blocking reload no, parts are ignored
CENC/DRM no
Packed audio: bare ADTS or ID3+AAC instead of a container no, the format is named in the error
TTML subtitles and WebVTT segments no, the track is recognized and skipped
MPEG-DASH ingest no

Applying changes

Only a change of url, headers or the HTTP settings (connect_timeout_ms, read_timeout_ms, max_body_bytes) restarts the input: a new source has its own track numbering, its own init and its own position in the playlist. Everything else — variant mode, skew threshold and policy, peer_timeout_ms — applies in place, without interrupting the broadcast.

Verification

Open http://server/streaming/v/chan1/index.m3u8 in a player or request a screenshot at http://server/streaming/live-preview-jpeg/chan1.

Look beyond "frames are flowing" at three things:

  • how far the output lags behind the playlist edge — without it a "the stream is behind" complaint is indistinguishable from "the source is behind", and the two are fixed in different places;
  • skipped segments and their breakdown: washed out of the window, cancelled in flight, declared with EXT-X-GAP. The last one is not a source error but its honest statement;
  • the seam between neighbouring fragments — a silent defect: every segment was downloaded, no parse errors, yet the media inside them does not join up, and the archive keeps the hole.

Next steps