playout-runtime 0.2.0

Sans-IO linear channel playout: a schedule model (programme/ad/slate), transition planning across a join (PTS-rebase offset + discontinuity detection), and SCTE-35 splice_insert() emission points built with scte35-splice. No HTTP, no tokio, no transcoding.
Documentation

playout-runtime

crates.io docs.rs License

Sans-IO linear channel playout (issue #748): a schedule model (programme / ad / slate), transition planning across the join between two sources, and the SCTE-35 splice_insert() emission points a transition implies. no_std + alloc. No HTTP, no tokio.

What's here

  • [schedule] — [schedule::Schedule]: an ordered list of [schedule::ScheduleEntry] (programme/ad/slate, a source identity, and a planned start on the channel timeline). The format is deliberately ours to define — see the issue's design-decision comment: SCTE 224 covers policy/blackout signalling, not channel assembly, and adopting it would answer a different question. There is therefore no Parse/ Serialize pair here; it's an in-memory model, not a wire format.
  • [transition] — [transition::next_transition] finds the next join the schedule implies and [transition::TransitionPlan::plan] computes it: the channel-timeline instant it happens at, a PTS-rebase offset so the incoming source's own clock lands continuously on the shared timeline, and a discontinuity flag when the codec configuration changes across the join. This is the crate's primary tested property — see Timeline continuity below.
  • [scte35] — [scte35::build_splice_insert] builds the splice_insert() (ANSI/SCTE 35 2023r1 §9.7.3) a transition implies, using scte35-splice's own Serialize (not hand-assembled bytes), with its target instant conditioned against real candidate boundaries via [ssai_runtime::splice::condition_splice_point_wrapping] — reused, not re-implemented.

What this crate is not

  • No transcoding or conforming. A differing codec config across a transition is a discontinuity to signal ([transition::TransitionPlan::discontinuity]), never something this crate re-encodes. This workspace parses containers and never touches the codec bitstream, and that holds here too.
  • No actual sample-timestamp rewriting. transmux's IR transform would apply [transition::TransitionPlan::pts_rebase_offset] to real sample PTS/DTS values; this crate computes the number, not the rewrite. broadcast_hls::mark_init_discontinuities would act on [transition::TransitionPlan::discontinuity] at the playlist/init-segment level — neither dependency is pulled in here.
  • No boundary conditioning of its own. [scte35::build_splice_insert] calls ssai-runtime's condition_splice_point_wrapping rather than duplicating nearest-boundary snapping — two implementations of the same boundary math could disagree about the same boundary.
  • No HTTP, no tokio. A multimux adapter driving a real channel clock against this crate's planning is deliberately future work, mirroring the rtsp-runtime/hls-runtime/ssai-runtime sans-IO split.

Timeline continuity: the primary tested property

Issue #748's framing: "a schedule that plays the right thing at the wrong timestamp is worse than no scheduler." This crate's tests hold TransitionPlan to that bar rather than treating schedule bookkeeping as the interesting part:

  • transition::tests::rebase_lands_the_incoming_source_continuously_on_the_channel_timeline — once rebased, the incoming source's own PTS sequence lands exactly on the channel timeline with no gap and no overlap.
  • transition::tests::rebase_returns_none_rather_than_wrapping_out_of_range — a rebase that would fall outside u64's range reports None, never a wrapped, wrong instant.
  • tests/real_fixture_transition.rs plans a programme -> ad transition against the real, non-IDR-aligned cue in fixtures/scte35-ssai/ (DASH-IF livesim2, Apache-2.0): the nearest video keyframe is 6000 ticks (67 ms) after the cue's nominal instant, measured independently straight from the fragment's own boxes (fixtures/scte35-ssai/PROVENANCE.md). The test asserts that measured gap and that the emitted cue carries the conditioned instant, not the raw request — a regression that made build_splice_insert a passthrough of requested_pts fails this test loudly.

Examples

  • schedule_and_transition — build a programme -> ad -> programme schedule and walk every transition it implies.
  • emit_splice_from_real_cue — the full pipeline against the real fixture: decode the cue, plan the transition, condition the splice point, emit and byte-exact-round-trip the splice_insert().
cargo run -p playout-runtime --example schedule_and_transition
cargo run -p playout-runtime --example emit_splice_from_real_cue

Install

[dependencies]
playout-runtime = "0.2"

Spec references

License

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