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: adTagParameters with imafw_ prefix arrive with prefix stripped (e.g. imafw_gege)
  • 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 resolve SqueezeEventConfig
  • caid — alternative to asset_key for event resolution
  • ge, 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:

  1. Parse all query params as FreeWheel targeting params
  2. Look up SqueezeEventConfig by asset_key or caid
  3. Create or update SqueezeStream with:
    • StreamID = DAI stream_id
    • EventID = resolved from asset_key/caid
    • FWParams = all targeting params
    • VPRN, PVRN = from params or random fallback
    • ClientIP = from X-Device-Ip header
  4. 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 (.m3u8 with “master” in name, or .mpd): Ensures stream is registered as a SqueezeStream, rewrites absolute DAI variant URLs to route through FsMulti.
  • Variant manifest (.m3u8 with variant/bandwidth indicators): Pre-converts root-relative segment URLs to absolute Propeller origin URLs, applies live window trimming via CreateWindowedVariantManifest (when WindowsSegs > 0), then runs through RewriteHLSVariantManifest. 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/ segment
  • asset_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
  • generateFreewheelAdTagURL reads stream.FWParams — populated from preroll
  • SetParameter handles both native FW names and imafw_-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 before RewriteHLSVariantManifest processes them. This prevents BuildSegmentURL from 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.com by 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):

  1. /dai/adproxy creates SqueezeStream with full FWParams + EventID
  2. /dai/manifest/ finds existing stream, updates LastSeen

Manifest first (race condition):

  1. /dai/manifest/ creates SqueezeStream with EventID resolved from URL path assetKey (no FWParams)
  2. /dai/adproxy finds 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:

  • 0 or unset — DAI’s manifest format and DVR window pass through unchanged (no decode/encode roundtrip that could alter EXT-X-VERSION or duration precision). LiveEdgeDelaySegments is still applied via string-based trimLiveEdgeSegments to 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 using applyLiveWindow, 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: ProcessDAIVariantManifestCreateWindowedVariantManifestProcessLiveHLSManifest. 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 client adTagParameters before forwarding. Params must be defined as key-values in GAM Inventory to be forwarded.

Files

  • handlers/dai_handler.go — HTTP handler for /dai/adproxy and /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

This site uses Just the Docs, a documentation theme for Jekyll.