FsMulti DAI Proxy Architecture
Overview
The DAI Proxy flow enables FsMulti to perform squeezeback ad insertion on streams managed by Google DAI (Dynamic Ad Insertion) via the IMA SDK. It operates alongside the existing FsMulti squeezeback flow — both use SqueezeStream for stream state and share the same ad decision, manifest rewriting, and uflock transcoding logic.
Key design principle: No client changes required. The player registers with DAI directly via the IMA SDK. FsMulti inserts itself into the path through GAM CDN configuration and a preroll ad tag redirect.
How It Differs from the Existing FsMulti Flow
| Aspect | Existing FsMulti Flow | DAI Proxy Flow |
|---|---|---|
| Entry point | /squeezeback/{assetId}/... | /dai/manifest/... + /dai/adproxy |
| Stream ID | FsMulti-generated (9-digit random) | DAI stream_id (UUID:LOC) used directly |
| Manifest source | Propeller origin (source_uri) | dai.google.com via CDN proxy |
| Stream registration | Player requests /squeezeback/{assetId}/master.m3u8 | Preroll ad tag or first manifest request |
| Targeting params | Player query params / JWT on manifest URL | GAM preroll ad tag — client adTagParameters with imafw_ prefix stripped by GAM |
| Full-screen ads | N/A | DAI handles via SCTE-35 — pass-through in manifest |
| Squeezeback trigger | REST API or SCTE/ID3 monitor | REST API only (DAI owns SCTE markers) |
| Segment URLs | Propeller origin | Content segments → Propeller origin, DAI ad segments → dai.google.com |
| Client changes | None (existing integration) | None (GAM/DAI config only) |
Both flows create SqueezeStream objects in Redis and reuse all downstream logic.
Architecture
Participants
- Player (IMA SDK) — registers with DAI, plays manifests, fires beacons
- FsMulti — CDN proxy for manifests, ad tag proxy for targeting capture, squeezeback engine
- Google DAI — stream registration, manifest serving, full-screen ad stitching via SCTE-35
- FreeWheel — ad decisioning for squeezeback breaks (L-bar, dual-box)
Sequence Flow
Phase 1 — Stream Init (no client changes)
Player → DAI: Register via IMA SDK (contentSourceId, videoId, ppid, nonce, adTagParams, csid)
DAI → Player: StreamEvent.LOADED (stream_id + manifest URL pointing to FsMulti via CDN config)
Phase 1b — Ad Tag Proxy / Targeting Capture (DAI → FsMulti)
DAI → FsMulti: GET /dai/adproxy?stream_id=X&asset_key=Y&ge=1&gr=3&sb=22&csid=...
(GAM preroll ad tag configured to point to FsMulti)
(DAI substitutes %%STREAM_ID%% and %%ASSET_KEY%% macros)
(Client adTagParameters with imafw_ prefix are forwarded with prefix stripped)
FsMulti: Parse targeting params, resolve event config via asset_key,
create SqueezeStream, register in event stream registry
FsMulti → DAI: Empty VAST response (no ad served)
Phase 2 — First Master Manifest Request
Player → FsMulti: GET /dai/manifest/.../event/{assetKey}/stream/{stream_id}/master.m3u8
FsMulti: Extract stream_id and assetKey from URL path
Ensure stream registered, resolve event config from assetKey
FsMulti → DAI: Proxy GET dai.google.com master manifest
DAI → FsMulti: Master manifest with absolute dai.google.com variant URLs
FsMulti: Rewrite dai.google.com variant URLs to FsMulti host
FsMulti → Player: Master manifest with FsMulti variant URLs
Phase 3 — Normal Playback (variant manifest polling)
Player → FsMulti: GET /dai/manifest/.../variant/.../bandwidth/443383.m3u8
FsMulti → DAI: Proxy GET dai.google.com variant manifest
DAI → FsMulti: Variant manifest with relative segment URLs (/out/v1/.../segment.ts)
FsMulti: Rewrite relative segment URLs to absolute Propeller origin URLs
(absolute DAI ad segment URLs left untouched)
Apply LiveEdgeDelaySegments trimming (keeps playback behind live edge)
Apply live window trimming if WindowsSegs > 0 (see DVR Window Handling)
FsMulti → Player: Variant manifest with Propeller segment URLs
Phase 4a — Full-Screen Ad Break (DAI handles via SCTE-35)
Note: DAI detects SCTE-35 markers in Propeller source, decisions and
stitches full-screen ads. FsMulti does not use SCTE markers for
squeezeback triggers — DAI owns those.
Player → FsMulti: GET variant manifest
FsMulti → DAI: Proxy
DAI → FsMulti: Variant manifest with DAI full-screen ad segments (absolute URLs)
FsMulti → Player: Pass-through (DAI ad segment URLs intact, content segments → Propeller)
Player → DAI: Beacon via IMA SDK (stream_id + ID3 tags)
Phase 4b — Squeezeback Break Triggered via REST API
Note: Triggered by PUT /api/v1/squeeze-ad-break (not by SCTE markers)
FsMulti: Load SqueezeStream.FWParams for stream_id
FsMulti → FreeWheel: Ad request with targeting params
FreeWheel → FsMulti: VMAP with personalized ad decisions + tracking URLs
FsMulti: Rewrite tracking URLs through /beacon endpoint
FsMulti: Transcode squeezeback segments via uflock
Phase 5 — Manifest During Squeezeback Break
Player → FsMulti: GET variant manifest
FsMulti → DAI: Proxy GET dai.google.com variant manifest
DAI → FsMulti: Variant manifest (may contain DAI content or ad segments)
FsMulti: RewriteHLSVariantManifest: replace break segments with
squeezeback segments (uflock URLs). Content segments → Propeller.
DAI full-screen ad segments → left untouched.
FsMulti → Player: Variant manifest with squeezeback segments
Player → FsMulti: GET /vast?stream_id=X (triggered by com.cbsinteractive.vtg ID3 tag)
FsMulti → Player: VAST with FsMulti beacon URLs
Player → FsMulti: Beacon (impression, quartile, complete)
FsMulti → FreeWheel: Proxy beacon
FsMulti → Player: 200 OK
Phase 6 — Break End
Player → FsMulti: GET variant manifest
FsMulti → DAI: Proxy
FsMulti: No active break — rewrite relative segments to Propeller origin only
FsMulti → Player: Variant manifest with Propeller segment URLs
GAM/DAI Configuration Required
CDN Configuration
GAM CDN delivery settings replace dai.google.com with the FsMulti host in manifest URLs. This applies to master and variant manifests only — segment URLs point to the Propeller origin and are fetched directly by the player.
- Delivery URL prefix:
https://fsmulti.example.com/dai/manifest/ - Master playlist origin forwarding type: Conventional
- Scope: Master manifest + variant manifests
- Segments: Not proxied — player fetches directly from Propeller origin
Reference: Create CDN configurations
Preroll Ad Tag Configuration
GAM live stream preroll settings point the preroll ad tag to FsMulti. DAI substitutes macros before making the request. Client adTagParameters sent via IMA SDK with imafw_ prefix are forwarded by GAM with the prefix stripped.
- Ad tag URL:
https://fsmulti.example.com/dai/adproxy?stream_id=%%STREAM_ID%%&asset_key=%%ASSET_KEY%% - DAI macros:
%%STREAM_ID%%and%%ASSET_KEY%%are expanded by GAM - Client params:
adTagParameterswithimafw_prefix arrive with prefix stripped (e.g.imafw_ge→ge) - Response: FsMulti returns empty VAST (no ad served to player)
Reference: Pre-rolls in DAI live streams
GAM Inventory Key-Values
Client adTagParameters with imafw_ prefix must be defined as key-values in GAM Inventory for GAM to forward them. Navigate to Inventory → Key-values and create entries with Dynamic value type for each targeting param the client sends.
FsMulti Endpoints
GET /dai/adproxy
Receives the preroll ad tag request from DAI. Captures all FreeWheel targeting params and creates/updates a SqueezeStream keyed by the DAI stream_id.
Query parameters (substituted by GAM from macros + client adTagParameters):
stream_id— DAI stream ID (from%%STREAM_ID%%macro)asset_key— GAM LiveStreamEvent asset key (from%%ASSET_KEY%%macro), used to resolveSqueezeEventConfigcaid— alternative toasset_keyfor event resolutionge,gr,sb,csid,ppid,vprn,pvrn— FreeWheel targeting params (prefix stripped by GAM)_fw_us_privacy,_fw_nielsen_app_id,_fw_app_bundle— additional FreeWheel params- Any other FreeWheel param is accepted and stored in
SqueezeStream.FWParams
Response: Empty VAST XML (<VAST version="4.0"/>)
Client IP: Extracted from X-Device-Ip header (DAI forwards the real client IP here)
Stream registration logic:
- Parse all query params as FreeWheel targeting params
- Look up
SqueezeEventConfigbyasset_keyorcaid - Create or update
SqueezeStreamwith:StreamID= DAI stream_idEventID= resolved from asset_key/caidFWParams= all targeting paramsVPRN,PVRN= from params or random fallbackClientIP= from X-Device-Ip header
- Register stream in event’s stream registry (
squeeze_event_streams:{eventID})
GET /dai/manifest/{path}
Proxies manifest requests to dai.google.com. The {path} is the original DAI URL path after the CDN host replacement.
Behavior by manifest type:
- Master manifest (
.m3u8with “master” in name, or.mpd): Ensures stream is registered as aSqueezeStream, rewrites absolute DAI variant URLs to route through FsMulti. - Variant manifest (
.m3u8with variant/bandwidth indicators): Pre-converts root-relative segment URLs to absolute Propeller origin URLs, applies live window trimming viaCreateWindowedVariantManifest(whenWindowsSegs > 0), then runs throughRewriteHLSVariantManifest. During an active squeezeback break, replaces break segments with squeezeback segments. Absolute DAI full-screen ad segment URLs are left untouched.
URL extraction from path:
stream_id: Parsed after/stream/or/streams/segmentasset_key(event key): Parsed after/event/segment
Stream registry: Calls RegisterStreamForEvent on every request to ensure the stream is discoverable by ProcessSqueezeAdBreak.
Stream State: SqueezeStream
The DAI proxy flow uses the same SqueezeStream model as the existing FsMulti flow. The DAI stream_id is used directly as SqueezeStream.StreamID, which means:
- Ad break triggers via the API find the stream in the same Redis key space
generateFreewheelAdTagURLreadsstream.FWParams— populated from prerollSetParameterhandles both native FW names andimafw_-prefixed names transparently- Manifest rewriting, uflock transcoding, beacon proxying — all work unchanged
stream.VPRN,stream.PVRN,stream.ClientIP— set from preroll params
Redis key: squeeze_stream_{dai_stream_id} Event registry: squeeze_event_streams:{eventID} sorted set
HLS Manifest Handling
Master Manifest
DAI returns master manifests with absolute https://dai.google.com/... variant URLs. FsMulti rewrites these to route through the FsMulti host so variant requests also proxy through FsMulti.
Variant Manifest
DAI returns variant manifests with:
- Root-relative content segment URLs (e.g.
/out/v1/.../segment.ts) — rewritten to absolute Propeller origin URLs beforeRewriteHLSVariantManifestprocesses them. This preventsBuildSegmentURLfrom doubling the path. - Absolute DAI ad segment URLs (e.g.
https://dai.google.com/.../ad/0/.../0.ts) — left untouched.isRelativeURL()returns false, so they pass through unchanged.
During a squeezeback break, RewriteHLSVariantManifest replaces break segments with squeezeback segment URLs (pointing to uflock). Content segments remain as Propeller origin URLs. DAI full-screen ad segments remain as DAI URLs.
Segment Delivery
Segments are not proxied through FsMulti:
- Content segments → fetched directly from Propeller origin by the player
- DAI ad segments → fetched directly from
dai.google.comby the player - Squeezeback segments → fetched from uflock/FsMulti by the player
Race Condition: Preroll vs First Manifest
The preroll ad tag request and the first manifest request can arrive in either order:
Preroll first (normal case):
/dai/adproxycreatesSqueezeStreamwith full FWParams + EventID/dai/manifest/finds existing stream, updatesLastSeen
Manifest first (race condition):
/dai/manifest/createsSqueezeStreamwith EventID resolved from URL path assetKey (no FWParams)/dai/adproxyfinds existing stream, enriches with FWParams
Either way, the stream is fully populated by the time a squeezeback break triggers.
SCTE-35 and Squeezeback Triggers
In the DAI proxy flow, SCTE-35 markers in the Propeller source stream are consumed by DAI for full-screen ad insertion. FsMulti never sees them — by the time the variant manifest is proxied, SCTE markers have been replaced with DAI ad segments.
Squeezeback breaks are triggered exclusively via the REST API (PUT /api/v1/squeeze-ad-break). The SCTE/ID3 tag monitor is not used for DAI proxy events.
DVR Window Handling
DAI controls the DVR window size in its manifest, configured in the GAM LiveStreamEvent settings. FsMulti preserves DAI’s DVR window by default and optionally trims it further using the WindowsSegs setting from the event config.
Behavior by WindowsSegs value:
0or unset — DAI’s manifest format and DVR window pass through unchanged (no decode/encode roundtrip that could alterEXT-X-VERSIONor duration precision).LiveEdgeDelaySegmentsis still applied via string-basedtrimLiveEdgeSegmentsto keep playback behind the live edge for uflock transcoding. This is the default for DAI proxy events.> 0(e.g. 5) — FsMulti trims the manifest to N segments from the live edge usingapplyLiveWindow, same as the regular squeezeback flow. Useful if a shorter window than DAI’s default is needed.
The windowing pipeline is shared with the regular squeezeback flow: ProcessDAIVariantManifest → CreateWindowedVariantManifest → ProcessLiveHLSManifest. When WindowsSegs <= 0, trimLiveEdgeSegments removes the last N segments (where N = LiveEdgeDelaySegments) without parsing/re-encoding the manifest. When WindowsSegs > 0, applyLiveWindow handles both windowing and live edge delay.
Known Limitations
- GAM ad tag host: GAM validates ad tag hosts. Custom domains may need whitelisting by the Google team.
- Targeting param delivery: GAM strips the
imafw_prefix from clientadTagParametersbefore forwarding. Params must be defined as key-values in GAM Inventory to be forwarded.
Files
handlers/dai_handler.go— HTTP handler for/dai/adproxyand/dai/manifest/repository/dai_targeting.go— Redis cache for raw targeting params (backup)cmd/http/main.go— Route registration for/dai/docs/squeezeback/dai_proxy.md— This document