Overview
The ad system is composed of four components that work together:AdsPollingTask— background task that handshakes with the ad server and polls for active ad snapshots.AdManager— SceneGraph node onPlayerScreenthat receives snapshots, reconciles active ad slots, manages expiry, and emits viewport reduction signals.AdFormatA,AdFormatB,AdFormatC— three SceneGraph nodes that render individual ad creatives according to their layout rules.
AdsPollingTask
Runs the handshake and polls
ADS_PATH_ACTIVE every 10 sAdManager
Reconciles snapshot data into active render slots; tracks impressions
Format A
Full-width banner overlaid on the video, anchored to top or bottom
Format B
Corner badge overlay positioned in one of four screen corners
Format C
Viewport-reduction banner: shrinks the video area rather than overlaying it
Impression tracking
Batch impression events sent to
ADS_PATH_IMPRESSIONSAd server configuration
All ad traffic is routed to a dedicated ads backend, separate from the main application server.| Constant | Value | Description |
|---|---|---|
ADS_API_BASE_URL | https://ads.globaltv.lat/api/v1 | Base URL for all ad endpoints |
ADS_USE_DEDICATED | true | Use dedicated ads server (not main server) |
ADS_HANDSHAKE_ENABLED | true | Run the device handshake on startup |
ADS_FLOW_DIAG | false | Diagnostic logging for the ad flow (disabled by default) |
ADS_USE_DEDICATED = false, all ad requests fall back to m.global.activeServer (the main app server).
Endpoints
| Constant | Path | Method | Purpose |
|---|---|---|---|
ADS_PATH_HANDSHAKE | /app/devices/handshake | POST | Device registration on session start |
ADS_PATH_ACTIVE | /app/ads/active | GET | Active ad snapshot poll |
ADS_PATH_IMPRESSIONS | /app/impressions/events/batch | POST | Batch impression event upload |
Handshake flow
TryAdsHandshake() runs once when AdsPollingTask starts (before the first poll). It registers the device with the ad server.
Precondition check
If
ADS_HANDSHAKE_ENABLED = false, ADS_USE_DEDICATED = false, or ADS_API_BASE_URL is empty, the handshake is skipped entirely.POST request
404, the task retries with ADS_PATH_HANDSHAKE_FALLBACK (empty by default, so no fallback occurs unless configured).Active snapshot polling
After the handshake,AdsPollingTask enters a polling loop.
Poll interval
| Constant | Value | Description |
|---|---|---|
ADS_POLL_MS | 10000 ms | Default interval between polls |
next_check_at field (ISO 8601 or epoch timestamp) to shorten the interval. The task uses the smaller of the two values, clamped to a minimum of 2 s.
Poll parameters
Each poll request includes:| Parameter | Source | Description |
|---|---|---|
device_id | roDeviceInfo.GetChannelClientId() | Unique device identifier |
stream_id | AdsPollingTask.streamId | Set by PlayerScreen per channel |
stream_position | Channel number string | Numeric position in the playlist |
since_version | Last known version string | Enables delta responses (no change → HTTP 204) |
stream_id and stream_position are available, two separate poll parameter sets are built and tried in order, falling back on HTTP 422.
Response handling
| HTTP code | Action |
|---|---|
200 | Parse JSON body, normalize ads list, emit adsSnapshot |
204 | No change — retain current ad state |
422 + since_version error | Reset since_version to "", retry |
401/403/404 (credentials) | Emit sessionInvalid → re-login flow |
| Auth inactive signal | Emit userInactive → re-login flow |
| Other error | Log warning, retain current state, increment fail count |
Channel change reset
WhenPlayerScreen tunes to a new channel, it toggles AdsPollingTask.resetChannel. The task detects this event and immediately issues a fresh poll with since_version = "" for the new stream:
AdManager: snapshot reconciliation
AdManager receives the normalized snapshot from PlayerScreen and reconciles it against the currently active ad slots.
Snapshot arrives
PlayerScreen.OnAdsSnapshot() filters the snapshot to the current channel key before forwarding it to AdManager.adsSnapshot.Record normalization
AdManager.NormalizeAdRecord processes each ad entry: resolves the format type (a, b, or c), extracts media_url, maps position strings (including Spanish variants), and computes activeUntilSec from the active_until field with clock-skew correction.Slot reconciliation
ReconcileSlots diffs the desired slot map against the active slot map:- Slots no longer desired are destroyed (
DestroySlot). - New slots are created (
CreateSlot) — each creates the appropriateAdFormatA/B/Cnode. - Existing slots with an unchanged ad signature are kept in place.
Expiry sweep
A 1-second repeating timer calls
OnExpirySweep, which checks each active slot’s activeUntilSec. Expired slots are destroyed automatically.Failure threshold
If 3 consecutive image load failures occur across any slots (m.maxFails = 3), AdManager clears all slots and emits allAdsHidden = true. PlayerScreen then restores the video to its full base dimensions and the ad system remains silent for the rest of that session.
Impression tracking
When an ad image loads successfully,AdManager.StartImpressionForSlot opens an impression session for that slot:
DestroySlot), CloseImpressionForSlot fires. If the ad was visible for at least 1 s, an ad_impression_closed metric event is emitted via m.top.trackMetric:
PlayerScreen.OnAdTrackMetric forwards this to MetricsTask, which batches and POSTs events to ADS_PATH_IMPRESSIONS.
Ad formats
Format A — banner overlay on video
Format A — banner overlay on video
Format B — corner badge
Format B — corner badge
Slot keys: Fade: Same fade-in/fade-out animation as Format A.
b:top-left, b:top-right, b:bottom-left, b:bottom-rightFormat B renders a small square badge in one of the four corners of the video. It does not reduce the video area.Default dimensions:- Width:
10%of the viewport width - Height:
10%of the viewport height
top-left (default), top-right, bottom-left, bottom-right. Shorthand values (top, bottom) resolve to the right-side equivalent (top-right, bottom-right).Pillarbox-aware positioning: When the video container is wider than the 16:9 active content (letterboxed/pillarboxed), AdManager detects the side bars and constrains the badge to the pillarbox area rather than the content area. This keeps the badge visible without obscuring the stream.Format C — viewport reduction
Format C — viewport reduction
Slot keys: On destroy (fade out): The fade-out completion callback also resets reduction fields to
c:top, c:bottomFormat C is the only format that physically shrinks the video area. It renders a banner in the space freed by moving the video up or down, rather than compositing over it.Default dimensions:- Width:
100%of the base screen viewport width - Height:
15%of the base screen viewport height
top or bottom (default)Viewport reduction mechanism:When the image loads, AdFormatC sets two fields on itself:- For bottom ads: the video height is reduced by
bannerH;videoOffsetY = 0. - For top ads: the video is offset downward by
bannerHand its height reduced;videoOffsetY = bannerH.
AdManager.RecomputeVideoReductionAndEmit aggregates reductions from both c:top and c:bottom slots, enforces a minimum video height of 480 px, and emits the final videoHeightReduction + videoOffsetY values to PlayerScreen.On image load failure: AdFormatC immediately resets both reduction fields to 0 so the video returns to full size:0 before hiding the poster, ensuring the video snaps back at the moment the ad disappears.Viewport used: Format C uses m.top.videoBaseViewport (the full base screen area), not the current reduced video rect. This ensures the banner fills the freed space at the edge of the screen rather than the edge of the already-reduced video.Slot key reference
| Format | Position values | Slot key |
|---|---|---|
| A | top, top-left, top-right | a:top |
| A | bottom, bottom-left, bottom-right | a:bottom |
| B | top-left | b:top-left |
| B | top-right | b:top-right |
| B | bottom-left | b:bottom-left |
| B | bottom-right | b:bottom-right |
| C | top, top-left, top-right | c:top |
| C | bottom, bottom-left, bottom-right | c:bottom |
Diagnostic flag
ADS_FLOW_DIAG = false controls verbose ad flow logging. When set to true, AdManager and AdsPollingTask emit detailed GTV_Warn entries for every snapshot apply, slot create/destroy, expiry check, and viewport computation. This flag is intended for local development only.
Enable
ADS_FLOW_DIAG in AppConstants.brs to trace the full ad lifecycle in the Roku debug console. Remember to set it back to false before packaging for submission.