ssai-runtime 0.2.0

Sans-IO SCTE-35 SSAI session core: per-session ad-break state, a pluggable ad-decision trait, splice-point conditioning, and per-session HLS Interstitial playlist rendering. No HTTP, no ad-decision client. no_std.
Documentation

ssai-runtime

crates.io docs.rs License

Sans-IO SCTE-35 server-side ad insertion (SSAI) session core (issue #929). no_std + alloc, mirroring the rtsp-runtime/hls-runtime split: a driveable core with no HTTP, no ad-decision client, and no per-viewer media cursor.

What's here

  • [session] — per-session ad-break state: SessionStore maps a session id to one small [session::BreakState] record (the decision being rendered, plus the conditioned splice point it entered/resumed at). This is deliberately not a media cursor: every viewer in a break watches one of a small set of ad assets, and outside a break sees byte-identical primary content — see the issue's design-decision comment. media-plane's "writer cost is O(N) in cursor count" rule is about the shared media rings, not this.
  • [decision] — [decision::AdDecisionProvider], the one pluggable extension point for "which ad to play". VAST/VMAP (or anything else) is entirely the implementor's concern: the trait performs no I/O, and this crate has no HTTP client. [decision::AdBreakDecision] models the HLS Interstitials attributes this crate implements (X-ASSET-URI/ X-ASSET-LIST, X-RESUME-OFFSET, X-PLAYOUT-LIMIT, X-SNAP, X-RESTRICT — draft-pantos-hls-rfc8216bis Appendix D §D.2).
  • [splice] — [splice::condition_splice_point]: aligns a cue's nominal target instant against real candidate boundaries (segment starts, or sync-sample/IDR timestamps), reporting the actual delta rather than assuming alignment. Real cues frequently are not IDR-aligned — see Splice-point conditioning below.
  • [playlist] — [playlist::InterstitialDateRange] renders/parses the EXT-X-DATERANGE CLASS="com.apple.hls.interstitial" tag (Appendix D), and [playlist::render_session_playlist] clones a base broadcast_hls::MediaPlaylist and appends that tag to MediaPlaylist::extra_tags — the one difference between a viewer's playlist inside a break and everyone else's. For a per-viewer hot path, [playlist::SessionPlaylistBase] renders the base once and splices the tag line into the text per viewer (no per-viewer segment clone). Both refuse a base without EXT-X-PROGRAM-DATE-TIME (RFC 8216bis §4.4.5.1). A 33-bit-wrapping clock (TS PTS, SCTE-35 pts_time) uses [splice::condition_splice_point_wrapping].

What this crate is not

  • No HTTP client, no VAST/VMAP. Implementing AdDecisionProvider (and doing whatever network call that needs) is the caller's job.
  • No media manipulation. Splicing the actual CMAF/TS bytes at the conditioned splice point is transmux's job (its splice/SSAI IR transforms); this crate only decides where that point is and what the per-session manifest should say about it.
  • No per-viewer media cursor — see [session] above.
  • No multimux HTTP wiring — an adapter is future work, not this crate.

Splice-point conditioning: a real, non-IDR-aligned cue

fixtures/scte35-ssai/ (Apache-2.0, a genuine DASH-IF livesim2 capture; see its PROVENANCE.md) carries a real splice_insert() whose nominal presentation time is not on a video keyframe: the nearest keyframe lands 6000 ticks (67 ms) after the cue, measured independently straight from the fragment's own moof/traf/tfdt/trun boxes. That's real-world encoder behaviour, not a fixture bug, and ssai-runtime/examples/condition_real_cue.rs reproduces the measurement rather than asserting it:

cargo run -p ssai-runtime --example condition_real_cue
cue presentation_time = 160767315900000 (90kHz ticks, absolute representation clock)
tolerance=3000 ticks (~33ms, one 30fps frame) -> Err(NoAlignedBoundary { .. nearest_delta_ticks: 6000 })
tolerance=10000 ticks (~111ms) -> ConditionedSplicePoint { .. delta_ticks: 6000, direction: After }
conditioning correctly measured the real, non-IDR-aligned gap: 6000 ticks (~66.7ms)

A caller picks its own tolerance: a live low-latency splice might need a tight bound (and correctly reject this cue), while a VOD packager splicing at a GOP boundary can accept the drift. condition_splice_point never silently lies about alignment that isn't there.

Examples

  • condition_real_cue — splice-point conditioning against the real fixture's measured 67 ms gap (above).
  • session_playlist — the full walkthrough: decode a real cue, run it through an AdDecisionProvider, condition its splice point, track it in a SessionStore, and render one viewer's EXT-X-DATERANGE into their playlist while every other viewer's stays untouched.
cargo run -p ssai-runtime --example session_playlist

Install

[dependencies]
ssai-runtime = "0.2"

Spec references

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.