Skip to main content

playout_runtime/
lib.rs

1//! Sans-IO linear channel playout — issue
2//! [#748](https://github.com/fishloa/rust-broadcast/issues/748).
3//!
4//! A schedule model for assembling a linear channel from an ordered list of
5//! sources ([`schedule`]), the transition planning that turns "what's next"
6//! into a concrete timeline-continuity plan ([`transition`]), and the
7//! SCTE-35 `splice_insert()` emission points a transition implies
8//! ([`scte35`]).
9//!
10//! ## Design decisions (issue #748's design-decision comment settles these)
11//!
12//! - **The schedule format is ours to define.** There is no applicable open
13//!   standard for "ordered channel-assembly playlist" to adopt: SCTE 224
14//!   (ESNI) covers policy/blackout signalling, not channel assembly, and is
15//!   paywalled besides. [`schedule::Schedule`] is therefore a plain in-memory
16//!   model with no `Parse`/`Serialize` pair — there is no wire format to be
17//!   symmetric about.
18//! - **This crate builds on `ssai-runtime` rather than duplicating it.**
19//!   `ssai_runtime::splice::condition_splice_point` already owns
20//!   nearest-boundary splice-point conditioning (with an explicit tolerance,
21//!   refusing rather than silently snapping when nothing is close enough);
22//!   [`scte35::build_splice_insert`] calls it rather than re-implementing
23//!   the same nearest-boundary math a second time. This crate's own job is
24//!   deciding *when* a transition happens (the schedule); `ssai-runtime`'s
25//!   is deciding *how the splice lands* against real boundaries.
26//! - **The hard part is the transition, not the schedule.** Joining two
27//!   sources means timeline continuity: a PTS-rebase offset
28//!   ([`transition::TransitionPlan::rebase`]) so the incoming source's own
29//!   clock lands continuously on the shared channel timeline, and a
30//!   discontinuity flag ([`transition::TransitionPlan::discontinuity`]) when
31//!   the codec configuration changes across the join. "A schedule that plays
32//!   the right thing at the wrong timestamp is worse than no scheduler" —
33//!   this crate's tests hold timeline correctness as the primary property,
34//!   not scheduling logic.
35//!
36//! ## What this crate is **not**
37//!
38//! - **No transcoding or conforming.** A differing codec config across a
39//!   transition is a discontinuity to *signal*
40//!   ([`transition::TransitionPlan::discontinuity`]), never something this
41//!   crate re-encodes. This workspace parses containers and never touches
42//!   the codec bitstream, and that holds here too.
43//! - **No actual sample-timestamp rewriting.** `transmux`'s IR transform is
44//!   what would apply [`transition::TransitionPlan::pts_rebase_offset`] to
45//!   real sample PTS/DTS values; this crate computes the number, not the
46//!   rewrite. Likewise `broadcast_hls::mark_init_discontinuities` is what
47//!   would act on [`transition::TransitionPlan::discontinuity`] at the
48//!   playlist/init-segment level — neither dependency is pulled in here.
49//! - **No HTTP, no tokio.** A `multimux` adapter driving a real channel
50//!   clock against this crate's planning is deliberately future work, not
51//!   this crate — mirroring the `rtsp-runtime`/`hls-runtime` and
52//!   `ssai-runtime` split the rest of the workspace uses.
53//!
54//! `no_std` + `alloc`.
55
56#![no_std]
57#![forbid(unsafe_code)]
58#![cfg_attr(docsrs, feature(doc_cfg))]
59
60extern crate alloc;
61
62pub mod error;
63pub mod schedule;
64pub mod scte35;
65pub mod transition;
66
67pub use error::{Error, Result};
68pub use schedule::{CodecConfigId, EntryKind, Schedule, ScheduleEntry};
69pub use scte35::{BreakEdge, ConditionedSplicePoint, build_splice_insert, to_section};
70pub use transition::{PlannedTransition, TransitionPlan, next_transition};