Skip to main content

arcly_stream/
traits.rs

1//! The foundational extension traits a downstream user implements.
2//!
3//! These are codec- and runtime-agnostic. Implement the ones you need and the
4//! engine drives them; the engine never assumes a particular protocol,
5//! container, or storage backend.
6
7use crate::bus::PublishRegistry;
8use crate::frame::CodecId;
9use crate::{MediaFrame, Result, StreamError};
10use async_trait::async_trait;
11use bytes::Bytes;
12use std::path::Path;
13use std::sync::Arc;
14
15/// A source that produces [`MediaFrame`]s (e.g. a protocol ingest connection).
16#[async_trait]
17pub trait MediaSource: Send + Sync {
18    /// Returns the next frame, or `Ok(None)` when the source is exhausted.
19    /// An `Err` result indicates a fatal I/O or protocol error.
20    ///
21    /// Using `Result<Option<T>>` rather than `Option<Result<T>>` follows the
22    /// standard Rust I/O convention: `Ok(Some(frame))` = got a frame,
23    /// `Ok(None)` = end of stream, `Err(e)` = unrecoverable error.
24    async fn next_frame(&mut self) -> Result<Option<MediaFrame>>;
25}
26
27/// A sink that consumes [`MediaFrame`]s (e.g. a protocol playback connection,
28/// a transcoding pipeline node, or a recorder).
29#[async_trait]
30pub trait MediaSink: Send + Sync {
31    /// Send a frame to this sink.
32    async fn send_frame(&mut self, frame: MediaFrame) -> Result<()>;
33
34    /// Flush any internal buffers and signal end of stream.
35    async fn flush(&mut self) -> Result<()> {
36        Ok(())
37    }
38}
39
40/// Adapts a [`MediaSink`] to the bus's [`FrameSink`](crate::bus::FrameSink) so it
41/// can be driven directly by
42/// [`StreamHandle::drive_to`](crate::bus::StreamHandle::drive_to) — the one-call
43/// path to **record or restream any live stream** (a WebRTC ingest included) to a
44/// sink, with the instant-start GOP replay the bus already provides.
45///
46/// ```ignore
47/// let handle = playback.get_stream(&key)?;
48/// let mut driver = SinkDriver::new(RecordingSink::new(storage, "rec/cam", 2));
49/// handle.drive_to(&shutdown, &mut driver).await?;
50/// driver.into_inner().flush().await?; // finalize the last chunk
51/// ```
52pub struct SinkDriver<S: MediaSink>(S);
53
54impl<S: MediaSink> SinkDriver<S> {
55    /// Wrap a [`MediaSink`] for use with `drive_to`.
56    pub fn new(sink: S) -> Self {
57        Self(sink)
58    }
59
60    /// Recover the inner sink (e.g. to `flush` it after the drive ends).
61    pub fn into_inner(self) -> S {
62        self.0
63    }
64}
65
66#[async_trait]
67impl<S: MediaSink> crate::bus::FrameSink for SinkDriver<S> {
68    async fn send(&mut self, frame: Arc<MediaFrame>) -> Result<std::ops::ControlFlow<()>> {
69        // The bus hands a shared `Arc`; `MediaSink` takes ownership. Avoid a copy
70        // when this is the last reference, else clone (cheap — payload is `Bytes`).
71        let frame = Arc::try_unwrap(frame).unwrap_or_else(|a| (*a).clone());
72        self.0.send_frame(frame).await?;
73        Ok(std::ops::ControlFlow::Continue(()))
74    }
75}
76
77/// Low-level protocol handler that accepts connections and maps them to
78/// stream identities.  Each concrete protocol implementation provides this.
79///
80/// Unlike `stream-center`'s original `ProtocolHandler`, `run` receives a
81/// [`PublishRegistry`] trait object rather than reaching for a concrete
82/// `ApplicationRegistry`, so a handler is reusable against any engine — or a
83/// host's own bus implementation.
84#[async_trait]
85pub trait ProtocolHandler: Send + Sync {
86    /// Human-readable name (e.g. `"rtmp"`, `"hls"`).
87    fn name(&self) -> &'static str;
88
89    /// Start accepting connections.  Implementations should run until the
90    /// provided `shutdown` token is cancelled, resolving incoming connections
91    /// to a [`StreamKey`](crate::StreamKey) and forwarding frames through
92    /// `registry`.
93    async fn run(
94        &self,
95        registry: Arc<dyn PublishRegistry>,
96        shutdown: tokio_util::sync::CancellationToken,
97    ) -> Result<()>;
98}
99
100/// Object-storage abstraction used by recording and HLS/DASH packagers.
101#[async_trait]
102pub trait StorageBackend: Send + Sync + 'static {
103    /// Write `data` at `key`.  Overwrites if the key exists.
104    async fn put(&self, key: &str, data: Bytes) -> Result<()>;
105
106    /// Read the object stored at `key`.
107    async fn get(&self, key: &str) -> Result<Bytes>;
108
109    /// Delete the object at `key`.
110    async fn delete(&self, key: &str) -> Result<()>;
111
112    /// List all keys with the given `prefix`.
113    async fn list(&self, prefix: &str) -> Result<Vec<String>>;
114
115    /// Returns `true` if the object exists.
116    async fn exists(&self, key: &str) -> Result<bool> {
117        match self.get(key).await {
118            Ok(_) => Ok(true),
119            Err(StreamError::StorageNotFound(_)) => Ok(false),
120            Err(e) => Err(e),
121        }
122    }
123
124    /// Write the file at `path` to `key`, then remove the source file.
125    ///
126    /// The default implementation reads the file into memory and calls [`put`](Self::put).
127    /// Backends that share a filesystem with the temp directory (e.g. disk)
128    /// should override this with a rename to avoid the memory round-trip.
129    async fn put_file(&self, key: &str, path: &Path) -> Result<()> {
130        let data = tokio::fs::read(path)
131            .await
132            .map_err(|e| StreamError::storage(format!("put_file read failed: {e}")))?;
133        self.put(key, Bytes::from(data)).await?;
134        tokio::fs::remove_file(path).await.ok();
135        Ok(())
136    }
137}
138
139/// `Arc<B>` is itself a [`StorageBackend`], so a single backend can be shared
140/// across many packagers/recorders without wrapping boilerplate.
141#[async_trait]
142impl<B: StorageBackend> StorageBackend for Arc<B> {
143    async fn put(&self, key: &str, data: Bytes) -> Result<()> {
144        (**self).put(key, data).await
145    }
146    async fn get(&self, key: &str) -> Result<Bytes> {
147        (**self).get(key).await
148    }
149    async fn delete(&self, key: &str) -> Result<()> {
150        (**self).delete(key).await
151    }
152    async fn list(&self, prefix: &str) -> Result<Vec<String>> {
153        (**self).list(prefix).await
154    }
155    async fn exists(&self, key: &str) -> Result<bool> {
156        (**self).exists(key).await
157    }
158    async fn put_file(&self, key: &str, path: &Path) -> Result<()> {
159        (**self).put_file(key, path).await
160    }
161}
162
163/// Hardware-acceleration backend (NVENC, AMF, QSV, NPU, …).
164///
165/// Implementors are registered at startup based on available hardware.
166pub trait HwAccelBackend: Send + Sync {
167    /// Unique backend identifier (e.g. `"nvenc"`, `"qsv"`).
168    fn id(&self) -> &'static str;
169
170    /// Returns whether the backend is usable on this machine.
171    fn is_available(&self) -> bool;
172
173    /// List codec IDs this backend can encode.
174    fn supported_encoders(&self) -> &[CodecId];
175
176    /// List codec IDs this backend can decode.
177    fn supported_decoders(&self) -> &[CodecId];
178}