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};