Concept

Server-side ad insertion, explained

SSAI is the reason connected TV advertising can look like broadcast instead of like a web page. This is what it actually does, what it asks of your pipeline, and the parts that catch teams out.

What it is

Server-side ad insertion assembles advertising into the video stream before it reaches the device. The player receives one continuous stream and has no idea which segments are content and which are ads.

Contrast client-side insertion, where the player requests ads itself and plays them through a separate ad module. That switch between content and ad playback is where buffering, black frames and abandoned sessions come from.

How it works, at the manifest level

Streaming delivery is a playlist of short segments. In HLS that is an .m3u8 manifest; in DASH, an MPD. The player fetches the manifest, then fetches the segments it lists, in order.

SSAI works by rewriting that playlist. This is manifest manipulation, and it is the whole mechanism.

Before insertion

#EXTM3U
#EXT-X-VERSION:6
#EXTINF:6.000,
content-0001.ts
#EXTINF:6.000,
content-0002.ts
#EXT-X-CUE-OUT:30.000        <- break starts here, 30s
#EXT-X-CUE-IN
#EXTINF:6.000,
content-0003.ts

After insertion

#EXTM3U
#EXT-X-VERSION:6
#EXTINF:6.000,
content-0001.ts
#EXTINF:6.000,
content-0002.ts
#EXT-X-DISCONTINUITY            <- decoder boundary
#EXTINF:5.000,
ad-a-0001.ts
#EXTINF:5.000,
ad-a-0002.ts
#EXT-X-DISCONTINUITY
#EXTINF:6.000,
content-0003.ts

The ad segments sit inline with content segments. The player walks the list and plays what it is given — no ad SDK, no separate request, no switch between players.

Why the discontinuity tag matters

Ad creative rarely matches content encoding exactly — different resolution ladder, different audio configuration, different codec profile. EXT-X-DISCONTINUITY tells the player to reset its decoder at that boundary. Omit it and you get the artefacts SSAI was supposed to eliminate: frozen frames, audio desync, or a hard playback failure on stricter devices.

Why connected TV settled on it

  • No mid-break buffering. There is no second stream to establish, so the transition costs nothing. On a television, where viewers are least tolerant of a stall, this is the single biggest reason.
  • No client SDK. You are not shipping and maintaining an ad library across Roku, Fire TV, Samsung, LG, web and mobile, each with its own player quirks and release cycle.
  • Ad blocking is impractical. Ads and content are indistinguishable in the manifest and served from the same origin, so there is no request to block without breaking playback.
  • It works on constrained devices. Smart TV runtimes have very little processing headroom. Moving the work server-side removes that limit.

What it requires from you

SSAI moves work off the device and onto the packaging pipeline. Three things have to be in place.

  1. Break signalling the server can read

    The insertion layer has to know where a break starts and how long it runs. For live streams that normally means SCTE-35 markers carried in the transport stream and surfaced as EXT-X-CUE-OUT / CUE-IN. For on-demand it is usually a VMAP document or your own schedule.

  2. Manifests the insertion layer can serve

    Because the manifest is rewritten per viewer, the player must request it from the insertion layer rather than straight from your CDN origin. In practice this is a change to the manifest URL your app requests. Segment delivery normally stays on your existing CDN.

  3. Creative that can be conditioned

    Ad creative arrives as a single MP4 and must be transcoded into segments matching your delivery profile before it can be stitched. This is done ahead of time and cached, not at request time — which is why the first appearance of a new creative can behave differently from later ones.

The trade-offs, honestly

SSAI is the right default for connected TV. It is not free.

What you give upWhy, and what to do about it
Client-side signals The server does not automatically know screen state, mute state, or viewport. Anything the buyer wants that only the device knows has to be passed explicitly in the ad request.
Simple tracking The player is not firing the beacons, so the server has to model playback to know when a quartile was reached. See below — this is where most SSAI problems actually live.
Interactive formats Anything requiring the viewer to act on the ad needs a client-side component alongside the stitched stream. Stitching alone cannot deliver it.
Straightforward debugging A per-viewer manifest means no two sessions are identical. Reproducing a fault requires session-level logging, not a URL you can open.
Cache efficiency Personalised manifests are not shareable between viewers. Segments still cache normally; manifests largely do not.

Beaconing is the hard part

In client-side insertion the player knows exactly what it played and fires tracking events itself. In SSAI the server has to infer it from which segments were requested.

That inference is where discrepancies come from, and buyers pay against these numbers:

  • A requested segment is not a watched segment. Players pre-fetch ahead of the playhead. Counting a fetch as a view inflates delivery and will eventually be caught in a discrepancy report.
  • Viewers abandon mid-break. If someone closes the app three seconds into a thirty-second ad, only the first quartile should fire. Detecting that means noticing requests stopping, which takes a timeout to establish.
  • Seeking breaks the model. On-demand viewers scrub. A viewer who jumps past a break never watched it, whatever the manifest offered them.
  • Clock skew matters. Quartile timing derives from segment durations. Small errors accumulate across a long pod.
Ask about this in any evaluation

Any SSAI vendor, us included, should be able to tell you exactly how they distinguish a pre-fetched segment from a watched one, and how they handle mid-break abandonment. If the answer is vague, expect discrepancy disputes with your buyers later.

Common failure modes

SymptomUsual cause
Frozen frame at the breakMissing discontinuity tag, or ad segments encoded outside the content's profile.
Audio desync after an adAudio configuration mismatch between creative and content, or an audio-only segment boundary landing mid-frame.
Slate or blank where an ad should beUnfilled avails — no eligible demand, floors set too high, or the auction timeout firing before bids returned.
Break runs short or longCreative durations not summing to the signalled break length. Pod construction has to fit the window it was given.
Impressions under-reported versus the buyerBeaconing model discarding fetches the buyer counts, or a quartile threshold measured differently at each end.
First play of a new creative failsConditioning not complete. Creative needs transcoding before it can be stitched.

How this works with GoGo CTV

Our ad server performs decisioning and insertion together, so the auction that picks the pod is the same system that stitches it. That matters for break-fitting: the decisioning stage knows the exact duration it has to fill.

Implementation specifics not yet published

The manifest endpoint format, the session parameters we accept, the beaconing model in detail, and the creative conditioning pipeline are not documented on this site yet. Rather than approximate them, we send the current specification on request — contact@gogoctv.com.

Next

Get started

See insertion running on your own stream.

We can shadow-test a slice of your live inventory and show you latency, fill and playback behaviour side by side with your current setup.