Operating offline
Offline mode lets a device keep playing when it loses connectivity. The player pre-fetches a full eligible playlist while online, stores it with its creatives, and rotates locally, buffering every playback event to drain on reconnect.
Offline mode is also how the player caches media files ahead of time. Because a pre-fetch downloads each ad’s <MediaFile> bytes to local storage, those creatives are already on the device when they are needed — so the player never has to fetch a large creative at slot time, even for online playback. Pre-caching this way avoids the slot-time download that could otherwise overrun the slot and time out (see Warm a fresh player).
Pre-fetch and warm
Section titled “Pre-fetch and warm”Ahead of any outage, request the playlist with offline=true and download each ad’s creative bytes:
GET /v1alpha2/ads?playerid=screen-7&key=retailer-acme&limit=20&offline=true→ 200 OK with up to the server-side cap of ads (no dvactid).An offline ad plays only if its creative is already on the device, so download the <MediaFile> bytes at pre-fetch time, not lazily at slot time. A fresh player has an empty playlist, so run this pre-fetch on first boot and start playing as soon as the first file is downloaded. Refresh it on a schedule; we recommend every 12 hours.
The full set of caching rules, including what to do when a file is missing at slot time, is on Caching creatives.
Every ad in an offline playlist is already eligible for this player and place: the server has applied location, zone, campaign eligibility, budget, and pacing before returning it. The player’s job offline is narrower: drop expired ads, match audience locally, and honor the source mix.
Several ads may share one flight
Section titled “Several ads may share one flight”A playlist can hold more than one ad from the same advertiser: one flight with several approved creatives contributes one <Ad> per creative, each with its own <AdServingId>, its own <Creative id>, its own media file and its own tracking URLs. They share a flight_id in verve-metadata and differ in creative.
Two consequences for the player:
- Group by
flight_idto avoid repeating an advertiser. Rotating blindly over the stored set can play two creatives of one flight back to back. If the player wants variety of advertiser rather than variety of creative, rotate flights first and creatives within a flight second. - Count frequency caps per
flight_id, never per ad. A cap is the flight’s, and every ad from that flight carries the sameaudienceblock and the samefrequency_caps. Counting plays per<Ad>would let a three-creative flight run three times its cap. See Enforcing frequency caps offline.
Choosing a stored ad
Section titled “Choosing a stored ad”At slot time: drop ads past expires_at, then rotate the rest, honoring the player’s source mix. If every stored ad has expired and the player cannot refresh, see When the player cannot refresh.
Online vs offline is the line that matters. Online, the server splits plays across sources per opportunity; matching the deployment’s per-source percentages is fully Verve’s job. Offline, that responsibility is the player’s: over the offline window the player keeps each source’s plays near its target share.
Honoring the source mix offline. The player already holds the per-source percentages; they are the player’s deployment’s configuration. Each ad tells the player which source it came from, via source_id (with source for the coarse type). Group the player’s eligible ads by source_id and rotate so that over the offline window each source’s plays approach its configured share. If a source has no eligible ads it can’t take its share, so renormalize its portion across the sources the player does have.
Matching audience offline
Section titled “Matching audience offline”Some stored ads carry an audience block in their verve-metadata. Play an ad when its audience matches the player’s current context (detected shoppers, cart contents, weather). An ad with no audience block targets everyone, so it is always eligible.
The block is a list of targeting_groups. An ad matches when any one group matches (groups are OR’d), and a group matches when all of its conditions match (conditions are AND’d). Each condition is one of:
| Condition | Matches when |
|---|---|
demographic |
A detected shopper fits any listed profiles entry (gender, age_range, and/or group_size). group_size is an object, {"operator": ..., "value": 3}, not a bare number. |
device |
The shopper’s basket contains one of the listed skus or items. |
store |
The current weather is one of the listed weather_types. |
Play the ads whose audience matches the current context, then apply the source-mix rotation to that matching set.
Enum values arrive as their platform string names. The verve-metadata schema lists the known values of each field.
gender and age_range follow IAB Audience Taxonomy 1.1. An absent field matches any value. Treat a value you don’t recognise as not matching rather than discarding the ad: these sets grow.
The same gender / age_range values here are the IAB-normalized result of whatever the device reported; see Demographics for how the online seg parameter carries the equivalent live signal.
Enforcing frequency caps offline
Section titled “Enforcing frequency caps offline”A frequency cap limits how many times one player may show a flight within a time window. Online the server enforces this itself and never returns a flight that has reached its cap. Offline it cannot: a disconnected player is the only thing that knows what it has played, so the server sends the cap’s configuration and the player enforces it locally.
Offline ads of a capped flight carry a frequency_caps array in their verve-metadata, a sibling of audience:
"frequency_caps": [ { "window": "FREQUENCY_CAP_WINDOW_HOUR", "limit": 2 }, { "window": "FREQUENCY_CAP_WINDOW_DAY", "limit": 20 }]| Field | Meaning |
|---|---|
window |
The limit’s period, in UTC: FREQUENCY_CAP_WINDOW_MINUTE (last 60 seconds), _THIRTY_MINUTES (last 30 minutes), _HOUR (current clock hour), _DAY (current day), _WEEK (current ISO week, from Monday), or _FLIGHT (whole flight). |
limit |
The maximum number of plays allowed per player within that window. |
Rules for the player:
- Every entry applies at once. Two entries mean both hold simultaneously (2 per hour and 20 per day). An ad is eligible only while it is under all of its limits.
- Count per
flight_id, never per<Ad>. The cap is the flight’s, and every creative from that flight carries the samefrequency_caps. Counting per ad would let a three-creative flight run three times its cap. - The player owns the count. The server sends the limit, never a current count or a remaining allowance — an offline count would be stale by the time the ad played. The player counts its own plays and stops matching the flight once a window’s limit is reached.
- No
frequency_capskey means uncapped. The key is absent for uncapped flights, and on online responses, where the server has already enforced the cap.
Reported plays still flow normally: the caps change what the player chooses, not what it fires. See Reporting exposure.
Operating offline at scale
Section titled “Operating offline at scale”- Clock drift. Every stamped URL carries
sts(the server’s clock at decision time). Because the player replays it verbatim, the server estimates the player’s device’s drift per event: no action on the player’s side, and the player’s rawetsis always preserved. - Late events are accepted, not rejected. A fire that arrives after its campaign’s billing window has closed is still accepted (flagged server-side), so a long-offline drain never silently loses events.
- Reconnect stampede. When a whole site reconnects at once, thousands of buffered fires per screen arrive together. Drain them in bulk with
POST /v1alpha2/track:batch— up to 250 fires per request instead of one each — and add a short randomized delay before the first batch. - Bounded buffer. Hold roughly the offline-playlist window of events and drop oldest-first on overflow, so a long-offline device degrades to best-effort rather than unbounded growth.
- Retrying a batch is safe. A failed or unanswered drain should be resent whole; the server counts a given fire once however many times it arrives, so a player never has to track which entries landed.
For how to buffer and drain the fires themselves, see Reporting exposure.