arcly_stream/lib.rs
1//! **arcly-stream** — an open-extensible, high-performance live-media streaming
2//! kernel.
3//!
4//! The pub/sub core of a streaming server: lock-free, zero-copy frame fan-out to
5//! an arbitrary number of late-joining subscribers, with instant-start GOP
6//! replay, publish/play authorization, live QoS, a pluggable **multi-protocol
7//! ingestion layer** ([`InboundProtocol`]), and a feature-gated pure-Rust media
8//! plane — all free of any config schema, metrics singleton, or HTTP runtime.
9//! You bring the wire protocols and telemetry by implementing small traits; the
10//! kernel owns the hard, hot-path part.
11//!
12//! ```
13//! use arcly_stream::prelude::*;
14//!
15//! let engine = Engine::builder()
16//! .max_publishers(1000)
17//! .application(AppSpec::new("live").gop_cache(120)) // instant-start
18//! .build();
19//! assert_eq!(engine.list_apps().len(), 1);
20//! ```
21//!
22//! # Architecture
23//!
24//! The crate is layered as a small always-on **kernel**, a feature-gated
25//! **media plane**, and **deferred wire protocols** that exist only as traits:
26//!
27//! ```text
28//! ┌─────────────────────── Core kernel (always on) ───────────────────────┐
29//! │ Engine · StreamHandle (broadcast fan-out, GOP cache, live QoS) │
30//! │ PublishRegistry / PlaybackRegistry / EventBus · Observer · auth · │
31//! │ audit · health · MediaFrame / CodecId · StreamError │
32//! └───────────────────────────────────────────────────────────────────────┘
33//! ▲ implements ▲ feature-gated, pure Rust
34//! │ │
35//! ┌─────┴── Wire protocols & muxers ──┐ ┌───────┴── Media plane ──────────┐
36//! │ rtmp RTMP publish/play (ready) │ │ codec H.264/H.265/AV1/VP9/VVC │
37//! │ rtsp RTSP pull ingest (ready) │ │ hls Packager + segmenter │
38//! │ srt SRT/MPEG-TS ingest (ready) │ │ mpegts MPEG-TS muxer │
39//! │ webrtc WHIP/WHEP signaling+RTP │ │ fmp4 CMAF muxer │
40//! │ Transcoder/ClusterRelay (traits) │ │ record RecordingSink · ingest │
41//! └───────────────────────────────────┘ └─────────────────────────────────┘
42//! ```
43//!
44//! The wire plane ships four operational protocols, most **bidirectional** —
45//! [`rtmp`](crate::protocol::rtmp) (publish + play), [`rtsp`](crate::protocol::rtsp)
46//! (client-pull from IP cameras, plus a server that both serves players and
47//! ingests `ANNOUNCE`/`RECORD` publishers over UDP or TCP-interleaved RTP),
48//! [`srt`](crate::protocol::srt) (a multi-connection server that ingests
49//! `m=publish` MPEG-TS feeds and serves `m=request` callers back), and
50//! [`webrtc`](crate::protocol::webrtc) (WHIP/WHEP signaling and RTP/RTCP routing
51//! over a host-supplied [`DtlsSrtpTransport`](crate::protocol::webrtc::DtlsSrtpTransport))
52//! — alongside the [`MpegTsMuxer`](crate::packager::MpegTsMuxer) and
53//! [`Fmp4Muxer`](crate::packager::Fmp4Muxer) egress muxers (the latter feeding
54//! HLS/LL-HLS and **LL-DASH chunked-CMAF** packaging). Server-side egress is
55//! gated by an injected [`EgressGate`](crate::auth::EgressGate); ABR/cluster
56//! backends remain trait contracts a host implements.
57//!
58//! ## Design invariants
59//!
60//! - **Zero-copy fan-out** — frames are `bytes::Bytes`; one publish clones an
61//! `Arc<MediaFrame>` pointer to every subscriber, never the payload.
62//! - **No locks on the hot path** — the frame bus is a `tokio::broadcast`
63//! channel; cached CONFIG frames and the GOP head use `ArcSwap` / atomics.
64//! - **No global state** — telemetry ([`Observer`]), authorization
65//! ([`StreamAuthenticator`]), and audit ([`AuditSink`]) are *injected* traits
66//! with no-op/permit-all defaults, never singletons. The engine is
67//! `Send + Sync` and can be instantiated many times per process.
68//! - **Contracts are traits** — protocols depend on [`PublishRegistry`] /
69//! [`PlaybackRegistry`], not on the concrete [`Engine`], so a host can swap in
70//! its own bus.
71//! - **Pluggable ingest** — a new wire protocol is one [`InboundProtocol`]
72//! ([`inbound`]) implementation, registered with
73//! [`EngineBuilder::protocol`](crate::EngineBuilder::protocol). The engine runs
74//! any worker that satisfies the trait; the bundled `RtmpHandler` is just the
75//! first reference implementation.
76//! - **`#![forbid(unsafe_code)]`** — the kernel contains no `unsafe`, enforced
77//! by the compiler. Documentation completeness (`#![deny(missing_docs)]`) and
78//! intra-doc link integrity are compiler-enforced too.
79//!
80//! ## Module layout
81//!
82//! | Module | Responsibility |
83//! |---------------|---------------------------------------------------------------|
84//! | [`frame`] | `MediaFrame`, `CodecId`, `FrameType`, `FrameFlags` |
85//! | [`identity`] | `AppName`, `StreamId`, `StreamKey` (cheap `Arc<str>` ids) |
86//! | [`error`] | `StreamError` and the crate [`Result`] alias |
87//! | [`traits`] | `MediaSource`, `MediaSink`, `ProtocolHandler`, `StorageBackend`, `HwAccelBackend` |
88//! | [`bus`] | `StreamHandle` fan-out (GOP cache, live QoS) + registry contracts |
89//! | [`inbound`] | `InboundProtocol` trait + `IngestContext` / `PublishSession` — the multi-protocol ingestion seam |
90//! | [`engine`] | `Engine`, `EngineBuilder`, `AppSpec` — composition, run loop, source pump, idle reaper |
91//! | [`auth`] | `StreamAuthenticator` publish/play authorization (permit-all default) |
92//! | [`observe`] | `Observer` telemetry hook + `NoopObserver` |
93//! | [`audit`] | `AuditSink` + `AuditPipeline` lifecycle audit trail |
94//! | [`health`] | `HealthCheck` / `HealthRegistry` readiness aggregation |
95//! | [`transcode`] | `Transcoder` / `RenditionSpec` ABR contracts |
96//! | [`cluster`] | `ClusterRelay` origin/edge federation contracts |
97//! | [`testing`] | In-memory helpers for downstream tests |
98//!
99//! ## Cargo features
100//!
101//! | Feature | Effect |
102//! |---|---|
103//! | *(none)* | Pure engine: frame model, bus, traits, auth/audit/health/cluster contracts |
104//! | `codec` | Baseline H.264 NAL/SPS + AAC ADTS parsers ([`codec`]) |
105//! | `codec-h265` / `codec-av1` / `codec-vp9` / `codec-vvc` | Next-gen codec parsers (opt-in, pure Rust) |
106//! | `codecs-all` | Every codec parser at once |
107//! | `storage-fs` | Filesystem [`StorageBackend`] adapter |
108//! | `ingest` | Shared TCP accept loop, keyframe gate, rate limiter, NAL scan |
109//! | `hls` | HLS/LL-HLS packager: `Muxer`/`Packager`, segmenter, playlists ([`packager`]) |
110//! | `mpegts` | Native MPEG-TS `Muxer` (`MpegTsMuxer`) — playable `.ts` segments |
111//! | `fmp4` | Native fragmented-MP4/CMAF `Muxer` ([`Fmp4Muxer`](packager::Fmp4Muxer)) — `ftyp`+`moov` init & `moof`+`mdat` fragments for LL-HLS |
112//! | `rtmp` | Working RTMP publish/play [`ProtocolHandler`](protocol::rtmp::RtmpHandler) |
113//! | `rtsp` | Native RTSP client-pull ingest ([`RtspHandler`](protocol::rtsp::RtspHandler)) — `OPTIONS`/`DESCRIBE`/`SETUP`/`PLAY`/`TEARDOWN`, SDP, TCP-interleaved RTP |
114//! | `srt` | Native SRT listener ingest ([`SrtHandler`](protocol::srt::SrtHandler)) — handshake + MPEG-TS demux (unencrypted) |
115//! | `webrtc` | WHIP/WHEP signaling + RTP/RTCP routing ([`WhipEndpoint`](protocol::webrtc::WhipEndpoint)) over a pluggable `DtlsSrtpTransport` crypto seam — incl. RTCP feedback ([`rtcp`](protocol::webrtc::rtcp)), per-viewer adaptive bitrate, viewer-driven keyframe recovery, conference rooms ([`Room`](protocol::webrtc::Room)), active-speaker, and `peer_stats` |
116//! | `record` | Segment/recording sink over a [`StorageBackend`] ([`record`]) |
117//! | `hls` / `record` | Shared keyframe-boundary [`segment`] timing |
118//! | `metrics` | Prometheus [`Observer`] implementation |
119//! | `cluster` | In-process reference [`ClusterRelay`] + `ClusterDirectory` (origin/edge federation) |
120//! | `auth-token` | Production signed-token [`StreamAuthenticator`] ([`TokenAuthenticator`]) — HMAC-SHA-256, dependency-free |
121//! | `macros` | `#[derive(MediaSink)]`, `#[protocol]` ergonomics |
122//! | `full` | everything |
123//!
124//! Ready-to-use reference implementations ship for the operational traits too:
125//! [`FileAuditSink`] ([`AuditSink`] → file),
126//! [`StandardTelemetry`] (structured-logging [`Observer`]), and the
127//! `metrics`-gated [`PrometheusObserver`](observability::PrometheusObserver).
128
129#![cfg_attr(docsrs, feature(doc_cfg))]
130// The engine contains no `unsafe` — make that a compiler-enforced guarantee so
131// a future change can't quietly introduce it.
132#![forbid(unsafe_code)]
133// Documentation completeness is enforced, not aspirational: every public item
134// carries docs, and intra-doc links must resolve.
135#![deny(missing_docs)]
136#![deny(rustdoc::broken_intra_doc_links)]
137
138pub mod audit;
139pub mod auth;
140pub mod bus;
141pub mod cluster;
142pub mod engine;
143pub mod error;
144pub mod frame;
145pub mod health;
146pub mod identity;
147pub mod inbound;
148pub mod observe;
149pub mod testing;
150pub mod traits;
151pub mod transcode;
152
153// Canonical Annex-B start-code scanner, shared by the codec NAL utilities and
154// the ingest packetizers. Internal; compiled whenever either layer is present.
155#[cfg(any(feature = "_nal", feature = "ingest"))]
156mod bytescan;
157
158// Dependency-free SHA-256 / HMAC-SHA-256 backing the signed-token authenticator.
159#[cfg(feature = "auth-token")]
160mod crypto;
161
162#[cfg(feature = "_codec")]
163#[cfg_attr(docsrs, doc(cfg(feature = "codec")))]
164pub mod codec;
165
166#[cfg(feature = "storage-fs")]
167#[cfg_attr(docsrs, doc(cfg(feature = "storage-fs")))]
168pub mod storage;
169
170#[cfg(feature = "ingest")]
171#[cfg_attr(docsrs, doc(cfg(feature = "ingest")))]
172pub mod protocol;
173
174#[cfg(feature = "onvif")]
175#[cfg_attr(docsrs, doc(cfg(feature = "onvif")))]
176pub mod onvif;
177
178#[cfg(feature = "hls")]
179#[cfg_attr(docsrs, doc(cfg(feature = "hls")))]
180pub mod packager;
181
182#[cfg(feature = "record")]
183#[cfg_attr(docsrs, doc(cfg(feature = "record")))]
184pub mod record;
185
186#[cfg(any(feature = "hls", feature = "record"))]
187#[cfg_attr(docsrs, doc(cfg(any(feature = "hls", feature = "record"))))]
188pub mod segment;
189
190#[cfg(feature = "metrics")]
191#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
192pub mod observability;
193
194// ── Flat re-exports (match sc-core's surface so migration is mechanical) ────
195pub use audit::{AuditAction, AuditPipeline, AuditRecord, AuditSink, FileAuditSink};
196#[cfg(feature = "auth-token")]
197#[cfg_attr(docsrs, doc(cfg(feature = "auth-token")))]
198pub use auth::TokenAuthenticator;
199pub use auth::{AllowAll, Credentials, StreamAuthenticator};
200pub use bus::{
201 EventBus, PlaybackRegistry, PublishRegistry, Qos, StreamEvent, StreamEventKind, StreamHandle,
202 StreamMetadata, StreamState, Subscription,
203};
204pub use cluster::{mirror, ClusterRelay, FrameSource, NodeAddr};
205pub use engine::{AppSpec, Engine, EngineBuilder, EngineConfig};
206pub use error::{ProtocolErrorKind, StreamError};
207pub use frame::{AudioFrame, CodecId, FrameFlags, FrameType, MediaFrame, VideoFrame};
208pub use health::{HealthCheck, HealthRegistry, HealthReport, HealthStatus};
209pub use identity::{AppName, StreamId, StreamKey};
210pub use inbound::{InboundProtocol, IngestContext, PublishSession};
211pub use observe::{NoopObserver, Observer, StandardTelemetry};
212pub use traits::{
213 HwAccelBackend, MediaSink, MediaSource, ProtocolHandler, SinkDriver, StorageBackend,
214};
215
216// Re-export `bytes` so downstream crates (and doctests) can name `Bytes` — the
217// payload type of every `MediaFrame` — without pinning a separate version.
218pub use async_trait;
219pub use bytes;
220pub use transcode::{RenditionSpec, Transcoder};
221
222/// The crate-wide fallible result. Mirrors `sc-core::Result`.
223pub type Result<T> = std::result::Result<T, StreamError>;
224
225/// Re-exports the derive macros expand against. Stable surface for codegen so
226/// internal files can move without touching the macro crate.
227#[cfg(feature = "macros")]
228#[doc(hidden)]
229pub mod __macro_support {
230 pub use crate::bus::PublishRegistry;
231 pub use crate::traits::{MediaSink, ProtocolHandler};
232 pub use crate::{MediaFrame, Result, StreamError, StreamKey};
233 pub use async_trait::async_trait;
234 pub use std::sync::Arc;
235 pub use tokio_util::sync::CancellationToken;
236}
237
238/// `use arcly_stream::prelude::*;` — everything a downstream user needs.
239pub mod prelude {
240 pub use crate::audit::{AuditPipeline, AuditRecord, AuditSink, FileAuditSink};
241 #[cfg(feature = "auth-token")]
242 pub use crate::auth::TokenAuthenticator;
243 pub use crate::auth::{AllowAll, Credentials, StreamAuthenticator};
244 pub use crate::bus::{
245 EventBus, PlaybackRegistry, PublishRegistry, Qos, StreamEvent, StreamEventKind,
246 StreamHandle, StreamState, Subscription,
247 };
248 pub use crate::cluster::{ClusterRelay, NodeAddr};
249 pub use crate::engine::{AppSpec, Engine, EngineBuilder, EngineConfig};
250 pub use crate::error::{ProtocolErrorKind, StreamError};
251 pub use crate::frame::{CodecId, FrameFlags, FrameType, MediaFrame};
252 pub use crate::health::{HealthCheck, HealthRegistry, HealthStatus};
253 pub use crate::identity::{AppName, StreamId, StreamKey};
254 pub use crate::inbound::{InboundProtocol, IngestContext, PublishSession};
255 pub use crate::observe::{NoopObserver, Observer, StandardTelemetry};
256 #[cfg(feature = "rtsp")]
257 pub use crate::protocol::rtsp::{RtspHandler, RtspSource};
258 #[cfg(feature = "srt")]
259 pub use crate::protocol::srt::SrtHandler;
260 #[cfg(feature = "webrtc")]
261 pub use crate::protocol::webrtc::{
262 DtlsSrtpTransport, PeerStats, Room, WhepEndpoint, WhepResource, WhipEndpoint, WhipResource,
263 };
264 pub use crate::traits::{
265 HwAccelBackend, MediaSink, MediaSource, ProtocolHandler, SinkDriver, StorageBackend,
266 };
267 pub use crate::transcode::{drive_abr_ladder, RenditionSpec, Transcoder, RENDITION_TRACK_BASE};
268 pub use crate::Result;
269 pub use async_trait::async_trait;
270 pub use tokio_util::sync::CancellationToken;
271
272 #[cfg(feature = "macros")]
273 pub use arcly_stream_macros::{protocol, MediaSink as MediaSinkDerive};
274}