×

🎯 SCTE-35

Ad detection · Skip · Markers

SCTE-35 — Ad Detection

SCTE-35 are markers embedded in the MPEG-TS stream that signal the start and end of ad breaks. The system detects them on the live input, stores them alongside the recorded segments and exposes them to players — so commercial blocks can be highlighted on the timeline and skipped in the archive according to each broadcaster's policy.

Detection & data flow

UDP/SRT input → SCTE-35 parser → ETS buffer → FFmpeg transcode → segments → FfmpegWorker checks ETS → meta.json → player

Markers are parsed from the SCTE-35 PID (stream_type 0x86, located via the PMT). Every detected splice event is stored with UNIX timestamps, duration, segment names and offsets.

meta.json is written next to the segments — live in /dev/shm/stream_X/, archive in /mnt/stream_archives/.../ — so the live player and the archive read the same source of truth.

Ad blocks on the player status bar

Detected ad breaks are highlighted directly on the player's status bar, so the viewer sees where a commercial block begins and ends within the programme instead of running into it blind.

Live: the player is told about the break in progress and the ones ahead. current_ad_break reports whether a break is active right now and how much of it is left (remaining_ms); upcoming lists the following breaks with start time and duration, so the bar can be marked before they arrive.

Archive: the full ad_breaks list for the recording is returned at once, so the entire status bar can be rendered with every commercial block marked before playback even starts.

Skip policy in the archive

Whether ads may be pulled through in the archive — and from when — is configured per channel according to the requirements of the individual programmers. Broadcasters commonly require that commercials stay unskippable for a defined period after the original airing.

scte35_enabled — turns marker handling on for the channel.

skip_after_minutes — the delay after which skipping is permitted. For example 3 means the viewer may skip ad breaks only once the recording is at least 3 minutes old; until then the breaks are marked on the bar but must be played out.

out_of_network / auto_return — taken from the splice event itself; they tell the player whether the break genuinely leaves the network feed and whether the return is automatic.

Support by input type

udp:// — Yes. TS parser, SCTE-35 PID (0x86) from PMT.

srt:// — Yes. TS parser, SRT carries TS.

http://*.m3u8 — Partial. Read from HLS tags (CUE-OUT / CUE-IN / DATERANGE).

rtmp:// — No. RTMP carries no SCTE-35.

API endpoints

GET /live/{id}/meta.json — no auth. Live markers, served via nginx sendfile.

GET /api/dvr/{id}/meta?from=&to= — Bearer. Archive markers for a time range.

GET /play/link_archive_nt/...&meta=1 — B2C token. HLS with EXT-X-DATERANGE tags.

GET /play/b2c/v1/ad-markers/{id} — B2C token. Current ad break plus upcoming.

Example — live mode

GET /play/b2c/v1/ad-markers/328
{
  "channel_id": "328",
  "channel_name": "Markiza",
  "scte35_enabled": true,
  "skip_after_minutes": 3,
  "mode": "live",
  "current_ad_break": {
    "active": true,
    "start_unix": 1775575200,
    "end_unix": 1775575380,
    "remaining_ms": 45000
  },
  "upcoming": [
    {"event_id": 12346, "start_unix": 1775578800, "duration_ms": 300000}
  ]
}

Example — archive mode

{
  "scte35_enabled": true,
  "skip_after_minutes": 3,
  "mode": "archive",
  "ad_breaks": [
    {
      "type": "splice_insert",
      "event_id": 60977,
      "start_unix": 1775766093,
      "end_unix": 1775766438,
      "duration_ms": 345800,
      "out_of_network": true,
      "auto_return": true
    }
  ]
}

Configuration

Enabled per stream in Streams → Edit → DVR tab. The detection method is chosen automatically from the input URL: UDP and SRT inputs have the SCTE-35 PID parsed out of the PMT, while HLS inputs fall back to reading CUE-OUT / CUE-IN / DATERANGE tags.