arcly_stream/lib.rs
1//! **arcly-stream** — a high-performance live-media streaming kernel.
2//!
3//! The pub/sub core of a streaming server: lock-free, zero-copy frame fan-out to
4//! an arbitrary number of late-joining subscribers, with instant-start GOP
5//! replay, publish/play authorization, live QoS, and a pluggable media plane —
6//! all free of any config schema, metrics singleton, or HTTP runtime. You bring
7//! the wire protocols and telemetry by implementing small traits; the kernel
8//! owns the hard, hot-path part.
9//!
10//! ```
11//! use arcly_stream::prelude::*;
12//!
13//! let engine = Engine::builder()
14//! .max_publishers(1000)
15//! .application(AppSpec::new("live").gop_cache(120)) // instant-start
16//! .build();
17//! assert_eq!(engine.list_apps().len(), 1);
18//! ```
19//!
20//! # Architecture
21//!
22//! The crate is layered as a small always-on **kernel**, a feature-gated
23//! **media plane**, and **deferred wire protocols** that exist only as traits:
24//!
25//! ```text
26//! ┌─────────────────────── Core kernel (always on) ───────────────────────┐
27//! │ Engine · StreamHandle (broadcast fan-out, GOP cache, live QoS) │
28//! │ PublishRegistry / PlaybackRegistry / EventBus · Observer · auth · │
29//! │ audit · health · MediaFrame / CodecId · StreamError │
30//! └───────────────────────────────────────────────────────────────────────┘
31//! ▲ implements ▲ feature-gated, pure Rust
32//! │ │
33//! ┌─────┴── Wire protocols & muxers ──┐ ┌───────┴── Media plane ──────────┐
34//! │ rtmp RTMP publish/play (ready) │ │ codec H.264/H.265/AV1/VP9/VVC │
35//! │ mpegts MPEG-TS muxer (ready) │ │ hls Packager + segmenter │
36//! │ ProtocolHandler (SRT/WHIP/RTSP) │ │ record RecordingSink │
37//! │ Transcoder/ClusterRelay (traits) │ │ ingest TCP accept loop │
38//! └───────────────────────────────────┘ └─────────────────────────────────┘
39//! ```
40//!
41//! Two wire-plane components ship operational: the [`rtmp`](crate::protocol::rtmp)
42//! handler (publish + play over real RTMP) and the
43//! [`MpegTsMuxer`](crate::packager::MpegTsMuxer) (player-compatible `.ts` output).
44//! Remaining protocols (SRT/WHIP/RTSP) and ABR/cluster backends are still trait
45//! contracts a host implements.
46//!
47//! ## Design invariants
48//!
49//! - **Zero-copy fan-out** — frames are `bytes::Bytes`; one publish clones an
50//! `Arc<MediaFrame>` pointer to every subscriber, never the payload.
51//! - **No locks on the hot path** — the frame bus is a `tokio::broadcast`
52//! channel; cached CONFIG frames and the GOP head use `ArcSwap` / atomics.
53//! - **No global state** — telemetry ([`Observer`]), authorization
54//! ([`StreamAuthenticator`]), and audit ([`AuditSink`]) are *injected* traits
55//! with no-op/permit-all defaults, never singletons. The engine is
56//! `Send + Sync` and can be instantiated many times per process.
57//! - **Contracts are traits** — protocols depend on [`PublishRegistry`] /
58//! [`PlaybackRegistry`], not on the concrete [`Engine`], so a host can swap in
59//! its own bus.
60//! - **Pluggable ingest** — a new wire protocol is one [`InboundProtocol`]
61//! ([`inbound`]) implementation, registered with
62//! [`EngineBuilder::protocol`](crate::EngineBuilder::protocol). The engine runs
63//! any worker that satisfies the trait; the bundled `RtmpHandler` is just the
64//! first reference implementation.
65//! - **`#![forbid(unsafe_code)]`** — the kernel contains no `unsafe`, enforced
66//! by the compiler.
67//!
68//! ## Module layout
69//!
70//! | Module | Responsibility |
71//! |---------------|---------------------------------------------------------------|
72//! | [`frame`] | `MediaFrame`, `CodecId`, `FrameType`, `FrameFlags` |
73//! | [`identity`] | `AppName`, `StreamId`, `StreamKey` (cheap `Arc<str>` ids) |
74//! | [`error`] | `StreamError` and the crate [`Result`] alias |
75//! | [`traits`] | `MediaSource`, `MediaSink`, `ProtocolHandler`, `StorageBackend`, `HwAccelBackend` |
76//! | [`bus`] | `StreamHandle` fan-out (GOP cache, live QoS) + registry contracts |
77//! | [`inbound`] | `InboundProtocol` trait + `IngestContext` / `PublishSession` — the multi-protocol ingestion seam |
78//! | [`engine`] | `Engine`, `EngineBuilder`, `AppSpec` — composition, run loop, source pump, idle reaper |
79//! | [`auth`] | `StreamAuthenticator` publish/play authorization (permit-all default) |
80//! | [`observe`] | `Observer` telemetry hook + `NoopObserver` |
81//! | [`audit`] | `AuditSink` + `AuditPipeline` lifecycle audit trail |
82//! | [`health`] | `HealthCheck` / `HealthRegistry` readiness aggregation |
83//! | [`transcode`] | `Transcoder` / `RenditionSpec` ABR contracts |
84//! | [`cluster`] | `ClusterRelay` origin/edge federation contracts |
85//! | [`testing`] | In-memory helpers for downstream tests |
86//!
87//! ## Cargo features
88//!
89//! | Feature | Effect |
90//! |---|---|
91//! | *(none)* | Pure engine: frame model, bus, traits, auth/audit/health/cluster contracts |
92//! | `codec` | Baseline H.264 NAL/SPS + AAC ADTS parsers ([`codec`]) |
93//! | `codec-h265` / `codec-av1` / `codec-vp9` / `codec-vvc` | Next-gen codec parsers (opt-in, pure Rust) |
94//! | `codecs-all` | Every codec parser at once |
95//! | `storage-fs` | Filesystem [`StorageBackend`] adapter |
96//! | `ingest` | Shared TCP accept loop, keyframe gate, rate limiter, NAL scan |
97//! | `hls` | HLS/LL-HLS packager: `Muxer`/`Packager`, segmenter, playlists ([`packager`]) |
98//! | `mpegts` | Native MPEG-TS `Muxer` (`MpegTsMuxer`) — playable `.ts` segments |
99//! | `rtmp` | Working RTMP publish/play [`ProtocolHandler`](protocol::rtmp::RtmpHandler) |
100//! | `record` | Segment/recording sink over a [`StorageBackend`] ([`record`]) |
101//! | `hls` / `record` | Shared keyframe-boundary [`segment`] timing |
102//! | `metrics` | Prometheus [`Observer`] implementation |
103//! | `macros` | `#[derive(MediaSink)]`, `#[protocol]` ergonomics |
104//! | `full` | everything |
105
106#![cfg_attr(docsrs, feature(doc_cfg))]
107// The engine contains no `unsafe` — make that a compiler-enforced guarantee so
108// a future change can't quietly introduce it.
109#![forbid(unsafe_code)]
110
111pub mod audit;
112pub mod auth;
113pub mod bus;
114pub mod cluster;
115pub mod engine;
116pub mod error;
117pub mod frame;
118pub mod health;
119pub mod identity;
120pub mod inbound;
121pub mod observe;
122pub mod testing;
123pub mod traits;
124pub mod transcode;
125
126#[cfg(feature = "_codec")]
127#[cfg_attr(docsrs, doc(cfg(feature = "codec")))]
128pub mod codec;
129
130#[cfg(feature = "storage-fs")]
131#[cfg_attr(docsrs, doc(cfg(feature = "storage-fs")))]
132pub mod storage;
133
134#[cfg(feature = "ingest")]
135#[cfg_attr(docsrs, doc(cfg(feature = "ingest")))]
136pub mod protocol;
137
138#[cfg(feature = "hls")]
139#[cfg_attr(docsrs, doc(cfg(feature = "hls")))]
140pub mod packager;
141
142#[cfg(feature = "record")]
143#[cfg_attr(docsrs, doc(cfg(feature = "record")))]
144pub mod record;
145
146#[cfg(any(feature = "hls", feature = "record"))]
147#[cfg_attr(docsrs, doc(cfg(any(feature = "hls", feature = "record"))))]
148pub mod segment;
149
150#[cfg(feature = "metrics")]
151#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
152pub mod observability;
153
154// ── Flat re-exports (match sc-core's surface so migration is mechanical) ────
155pub use audit::{AuditAction, AuditPipeline, AuditRecord, AuditSink};
156pub use auth::{AllowAll, Credentials, StreamAuthenticator};
157pub use bus::{
158 EventBus, PlaybackRegistry, PublishRegistry, Qos, StreamEvent, StreamEventKind, StreamHandle,
159 StreamMetadata, StreamState, Subscription,
160};
161pub use cluster::{ClusterRelay, NodeAddr};
162pub use engine::{AppSpec, Engine, EngineBuilder, EngineConfig};
163pub use error::{ProtocolErrorKind, StreamError};
164pub use frame::{AudioFrame, CodecId, FrameFlags, FrameType, MediaFrame, VideoFrame};
165pub use health::{HealthCheck, HealthRegistry, HealthReport, HealthStatus};
166pub use identity::{AppName, StreamId, StreamKey};
167pub use inbound::{InboundProtocol, IngestContext, PublishSession};
168pub use observe::{NoopObserver, Observer};
169pub use traits::{HwAccelBackend, MediaSink, MediaSource, ProtocolHandler, StorageBackend};
170
171// Re-export `bytes` so downstream crates (and doctests) can name `Bytes` — the
172// payload type of every `MediaFrame` — without pinning a separate version.
173pub use async_trait;
174pub use bytes;
175pub use transcode::{RenditionSpec, Transcoder};
176
177/// The crate-wide fallible result. Mirrors `sc-core::Result`.
178pub type Result<T> = std::result::Result<T, StreamError>;
179
180/// Re-exports the derive macros expand against. Stable surface for codegen so
181/// internal files can move without touching the macro crate.
182#[cfg(feature = "macros")]
183#[doc(hidden)]
184pub mod __macro_support {
185 pub use crate::bus::PublishRegistry;
186 pub use crate::traits::{MediaSink, ProtocolHandler};
187 pub use crate::{MediaFrame, Result, StreamError, StreamKey};
188 pub use async_trait::async_trait;
189 pub use std::sync::Arc;
190 pub use tokio_util::sync::CancellationToken;
191}
192
193/// `use arcly_stream::prelude::*;` — everything a downstream user needs.
194pub mod prelude {
195 pub use crate::audit::{AuditPipeline, AuditRecord, AuditSink};
196 pub use crate::auth::{AllowAll, Credentials, StreamAuthenticator};
197 pub use crate::bus::{
198 EventBus, PlaybackRegistry, PublishRegistry, Qos, StreamEvent, StreamEventKind,
199 StreamHandle, StreamState, Subscription,
200 };
201 pub use crate::cluster::{ClusterRelay, NodeAddr};
202 pub use crate::engine::{AppSpec, Engine, EngineBuilder, EngineConfig};
203 pub use crate::error::{ProtocolErrorKind, StreamError};
204 pub use crate::frame::{CodecId, FrameFlags, FrameType, MediaFrame};
205 pub use crate::health::{HealthCheck, HealthRegistry, HealthStatus};
206 pub use crate::identity::{AppName, StreamId, StreamKey};
207 pub use crate::inbound::{InboundProtocol, IngestContext, PublishSession};
208 pub use crate::observe::{NoopObserver, Observer};
209 pub use crate::traits::{
210 HwAccelBackend, MediaSink, MediaSource, ProtocolHandler, StorageBackend,
211 };
212 pub use crate::transcode::{RenditionSpec, Transcoder};
213 pub use crate::Result;
214 pub use async_trait::async_trait;
215 pub use tokio_util::sync::CancellationToken;
216
217 #[cfg(feature = "macros")]
218 pub use arcly_stream_macros::{protocol, MediaSink as MediaSinkDerive};
219}