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