Skip to main content

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 on PlayerScreen that 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 s

AdManager

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_IMPRESSIONS

Ad server configuration

All ad traffic is routed to a dedicated ads backend, separate from the main application server.
When ADS_USE_DEDICATED = false, all ad requests fall back to m.global.activeServer (the main app server).

Endpoints

Full URLs are constructed as:

Handshake flow

TryAdsHandshake() runs once when AdsPollingTask starts (before the first poll). It registers the device with the ad server.
1

Precondition check

If ADS_HANDSHAKE_ENABLED = false, ADS_USE_DEDICATED = false, or ADS_API_BASE_URL is empty, the handshake is skipped entirely.
2

Payload construction

3

POST request

If the primary path returns 404, the task retries with ADS_PATH_HANDSHAKE_FALLBACK (empty by default, so no fallback occurs unless configured).
4

Result

HTTP 2xx is logged as success. Any other code logs a warning with the response body snippet. The handshake result does not block polling — polling proceeds regardless.

Active snapshot polling

After the handshake, AdsPollingTask enters a polling loop.

Poll interval

The backend may return a 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: If both 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

Channel change reset

When PlayerScreen 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.
1

Snapshot arrives

PlayerScreen.OnAdsSnapshot() filters the snapshot to the current channel key before forwarding it to AdManager.adsSnapshot.
2

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.
3

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 appropriate AdFormatA/B/C node.
  • Existing slots with an unchanged ad signature are kept in place.
4

Expiry sweep

A 1-second repeating timer calls OnExpirySweep, which checks each active slot’s activeUntilSec. Expired slots are destroyed automatically.
5

Video reduction

After each reconcile, RecomputeVideoReductionAndEmit recalculates the combined Format C height reduction across c:top and c:bottom slots and emits videoHeightReduction and videoOffsetY to PlayerScreen.

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:
When the slot is destroyed (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

Slot keys: a:top, a:bottomFormat A renders a full-width banner image directly over the video. It does not reduce the video area — the ad is composited on top.Default dimensions:
  • Width: 100% of the video viewport width
  • Height: 15% of the video viewport height
Positions: top, top-left, top-right, bottom (default), bottom-left, bottom-rightThe banner is anchored to the top or bottom edge of the video viewport. The viewport used is m.top.videoViewport (the current, possibly-reduced video rect) — not the base screen rect.Conflict rule: Only one Format A slot may be active at a time. A second Format A ad with a different vertical position is dropped.
Fade: The ad fades in over ~6 frames (~0.1667 opacity per tick) after the image loads, and fades out the same way when destroyed.
Slot keys: 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
Positions: 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.
Fade: Same fade-in/fade-out animation as Format A.
Slot keys: 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
Positions: 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 bannerH and 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:
On destroy (fade out): The fade-out completion callback also resets reduction fields to 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

Slot keys determine both the render position and deduplication — two ads that map to the same slot key cannot coexist; the first one wins.

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.