media-plane 0.1.1

The ingress/egress spine for a live media origin: Dialer/Listener -> byte stages -> IngestSession -> Trunk (bounded sample/segment/event/part rings with cursor subscribers), three egress shapes (served/push/segment), and tiered retention with DVR pinning. Built on broadcast_common::Stage. no_std + alloc byte layer; Trunk and above require std.
Documentation

media-plane

Crates.io docs.rs

The media-plane integration layer: the crate that ties ingress, the byte layer, container demux (transmux), IR transforms, the sample/segment/event Trunk, and the three egress shapes together into one runnable pipeline, per docs/superpowers/specs/2026-07-26-media-plane-architecture.md in the workspace:

Dialer|Listener ──► [ByteStage]* ──► IngestSession ──► [IrTransform]* ──► TrunkWriter
   (N sources)       byte→byte          demux              IR→IR                │
                                                                                 ▼
                                                                  ┌──────── Trunk ────────┐
                                                                  │ sample ring           │
                                                                  │ segment log           │
                                                                  │ EVENT log (90 kHz)    │
                                                                  └───────────────────────┘
                         subscribe() ─► SampleCursor  ─► PushEgress    (WHEP, RTMP-out, SRT-out)
                         subscribe() ─► SegmentCursor ─► SegmentEgress (DVR, MABR, ROUTE, Smooth)
                         resolve()   ─────────────────► ServedEgress   (LL-HLS, DASH, catch-up)

What's implemented (this release)

This crate is functionally complete end to end (plan steps 3a through 3e):

  • Byte layer — [ByteStage] (the pre-demux byte-to-byte drive contract, a specialisation of broadcast_common::Stage), [ByteTap] (a non-blocking positional observer for conformance/analysis), [ByteMerge] (the one bounded multi-input primitive; no_std + alloc).
  • Trunk — the bounded, dual-retention (Timed/Sparse) sample ring, the segment log (with pinning-cursor retention for lossless DVR/archive, never writer back-pressure by default), and the 90 kHz absolute event log (EventCursor/EventAnchor), plus reader wake.
  • ingressDialer/Listener/IngestSession and IngestDriver /ListenDriver: the handshake-then-live pump that dispatches each session's reported programs/samples into a fresh Trunk.
  • egressPushEgress (WHEP/RTMP-out/SRT-out), SegmentEgress (DVR/MABR/ROUTE/Smooth), ServedEgress (LL-HLS/DASH/catch-up), with bounded Await negotiation.
  • retentionRetention::HotOnly/Tiered, RetentionDriver draining a pinning segment cursor into a caller-supplied, sans-IO SegmentSink.

no_std note — the byte layer only

Only the byte layer (byte_stage/byte_tap/byte_merge) is no_std + alloc. Trunk and everything built on it (ingress, egress, retention) require the std feature — Trunk needs std::sync::Mutex/ Arc/Condvar for cross-thread sharing. "The plane is no_std-capable" is true of the byte layer, not the whole crate; std is a default feature, and cargo build --no-default-features builds the byte layer alone.

Quickstart

use media_plane::{ByteMerge, MergePolicy, SourceId};
use broadcast_common::stage::Timestamp;
use bytes::Bytes;

// The one bounded multi-input primitive in the byte layer: two UDP sources
// (e.g. a bonded/backup feed) reduced to one output stream.
let mut merge = ByteMerge::new(MergePolicy::FirstArrival, 2, /* max_queued */ 64);
merge.feed(SourceId(0), Bytes::from_static(b"ts-packet"), Timestamp::ZERO)?;
while let Some((msg, _at)) = merge.poll() {
    // hand `msg` to a container demuxer (e.g. `transmux::StreamingTsDemux`)
    let _ = msg;
}
# Ok::<(), media_plane::MergeError>(())

See examples/ingest_trunk_playback.rs for the full ingress → TrunkSampleCursor pipeline (std feature) driven against a real broadcast capture, and examples/byte_tap_wire_observer.rs for ByteTap's non-blocking wire-observer contract on the same capture.

Recorded deviations (not defects)

  • [MergePolicy] deliberately has no Hitless2022_7 variant yet — SMPTE ST 2022-7 seamless switching needs an RTP sequence-number parse this layer does not have; see the byte_merge module docs. Tracked as #752.
  • Pull sources (HLS/DASH/Smooth) are request-driven, not stream-driven, and IngestSession::poll_transmit has no way to express "issue a GET for this URL" yet — a recorded seam, not solved here; see the ingress module docs' "Known seam" section.

Features

Feature Default Adds
std on Trunk, ingress, egress, retention (and their re-exports) — everything above the no_std byte layer.

MSRV

Rust 1.86.

License

MIT OR Apache-2.0