VOD SSAI Mode
Video-on-demand ad insertion with full seeking support, pre-stitched manifests, and deterministic quartile tracking for accurate measurement.
VOD vs Live SSAI
VOD SSAI differs fundamentally from live: all ad decisions are made at session creation time, manifests are stable throughout playback, and seeking requires special handling for accurate measurement.
| Aspect | VOD | Live |
|---|---|---|
| Ad Decisioning | At session creation (all breaks) | Just-in-time (per break) |
| Manifest Stability | Immutable after creation | Sliding window updates |
| Seeking | Full support required | DVR window only |
| Quartile Tracking | Deterministic (seek-aware) | Sequential |
| Session TTL | Fixed (content duration + buffer) | Refresh-on-access |
VOD Marker Processing
VOD content often originates from harvested live streams that contain preserved ad markers. The VODMarkerProcessor detects and normalizes these markers from multiple sources:
HLS Marker Types
- •
#EXT-X-DATERANGE- Apple interstitials - •
#EXT-X-CUE-OUT/#EXT-X-CUE-IN- Standard cues - •
#EXT-OATCLS-SCTE35- SCTE-35 in comments - •
#EXT-X-CHAPTER- Chapter markers
DASH Marker Types
- •
EventStreamwith SCTE-35 - •
EventStreamwith DVB-DASH cues - • Period boundaries
- • Custom event schemes
Processing Pipeline
- Parse Origin Manifest: Extract all ad markers with timeline positions
- Normalize Markers: Convert all marker types to unified
AdBreakCueformat - Resolve Pairs: Match start/end markers, calculate break durations
- Validate Positions: Ensure markers don't overlap, are within duration
- Generate Breaks: Create ad break list with positions and durations
Manifest Stability
VOD manifests are generated once at session creation and remain immutable throughout playback. This is critical for seeking and progress tracking.
HLS VOD Requirements
#EXT-X-ENDLISTtag required at end#EXT-X-PLAYLIST-TYPE:VODpresent- All segments listed from start to end
- Complete DATERANGE markers for all ad breaks
DASH VOD Requirements
type="static"MPDmediaPresentationDurationset- All periods present from start
- Complete EventStream for ad metadata
Common Issue: Missing ENDLIST
VOD playlists without #EXT-X-ENDLIST cause players to poll for updates indefinitely. The SSAI system enforces ENDLIST presence for VOD mode and validates during manifest generation.
Seeking Behavior
VOD SSAI must handle seeking correctly. Users may seek forward through ads, seek backward to rewatch content, or jump directly to specific points.
Seek Into Ad Break
When seeking into an ad break, playback starts from the ad at that position. Impression is credited when at least 2 seconds of the ad are viewed.
Seek Past Ad Break
When seeking past an ad break (skipping), the ads are not credited. Optional: force ad viewing on first pass (requires SDK integration).
Seek Backward
Seeking backward does not re-fire already-credited impressions. Quartiles are deduplicated per slot per session.
Timeline Mapping
Original Timeline (with ad markers): [Content: 0-30m] [Break @ 30m, 30s] [Content: 30m-60m] [Break @ 60m, 30s] [Content: 60m-90m] Stitched Timeline (player sees): [Content: 0-30m] [Ad Pod 1: 30s] [Content: 30m30s-60m30s] [Ad Pod 2: 30s] [Content: 61m-91m] Total Duration: 90m content + 1m ads = 91m Seek to 45m → Player position 45m30s (after first ad break) Seek to 70m → Player position 71m (after both ad breaks)
Deterministic Quartile Tracking
The VODReconciler handles quartile tracking for seekable content with configurable behavior for missed quartiles.
Quartile Positions
Calculated by calculateQuartilePositions(duration):
| Event | Position | Progress |
|---|---|---|
| Impression | 0s (start) | 0% |
| firstQuartile | duration × 0.25 | 25% |
| midpoint | duration × 0.50 | 50% |
| thirdQuartile | duration × 0.75 | 75% |
| complete | duration (end) | 100% |
Reconciler Config
From VODReconcilerConfig:
- • timingToleranceSeconds: 0.5s
- • fireOnSeek: true (fire missed on seek)
- • maxAgeMinutes: 120 (2 hour retention)
Seek Handling
When fireOnSeek is enabled:
- • Seek forward: missed quartiles are fired
- • Seek backward: no duplicate fires
- • Dedupe via
state.firedSet
Session Reconciliation
At session end, the VODReconciler performs final reconciliation to identify discrepancies between expected and actual tracking events.
Reconciliation Output
{
"podId": "pod-abc123",
"slots": [
{
"slotId": "slot-1",
"creativeId": "cr-xyz",
"expected": ["impression", "firstQuartile", "midpoint", "thirdQuartile", "complete"],
"received": ["impression", "firstQuartile", "midpoint"],
"missing": ["thirdQuartile", "complete"],
"duplicates": [],
"status": "partial"
}
],
"discrepancies": [
{
"type": "missing_quartile",
"slotId": "slot-1",
"event": "thirdQuartile",
"reason": "user_seeked_past"
}
]
}Pre-roll Handling
VOD pre-rolls have special considerations since they appear before any content:
Pre-roll ads are at position 0 in the stitched manifest
Optional: SDK can prevent seeking until pre-roll complete
Deep links to specific content positions still show pre-roll first
VOD Session Lifecycle
Session TTL Calculation
// VOD session TTL = content duration + buffer const sessionTTL = contentDuration + (24 * 60 * 60); // +24 hours buffer // Example: 90 minute movie // TTL = 90 minutes + 24 hours = 25.5 hours // Session expires after TTL regardless of activity // (Unlike live which refreshes on manifest access)