1use moq_net::broadcast;
4use moq_net::stats::{Role, Tier};
5
6use crate::{Result, SessionsFrame, TrafficFrame, sessions_track, traffic_track};
7
8#[derive(Debug, Clone, Default)]
11#[non_exhaustive]
12pub struct Config {
13 pub compression: bool,
17}
18
19impl Config {
20 pub fn new() -> Self {
22 Self::default()
23 }
24
25 pub fn with_compression(mut self, compression: bool) -> Self {
27 self.compression = compression;
28 self
29 }
30}
31
32pub struct Consumer {
40 broadcast: broadcast::Consumer,
41 config: Config,
42}
43
44impl Consumer {
45 pub fn new(broadcast: broadcast::Consumer, config: Config) -> Self {
48 Self { broadcast, config }
49 }
50
51 pub async fn traffic(&self, tier: &Tier, role: Role) -> Result<Traffic> {
54 let name = traffic_track(tier, role, self.config.compression);
55 Ok(Traffic {
56 inner: self.subscribe(&name).await?,
57 })
58 }
59
60 pub async fn sessions(&self, tier: &Tier) -> Result<Sessions> {
63 let name = sessions_track(tier, self.config.compression);
64 Ok(Sessions {
65 inner: self.subscribe(&name).await?,
66 })
67 }
68
69 async fn subscribe<T: serde::de::DeserializeOwned>(&self, name: &str) -> Result<moq_json::snapshot::Consumer<T>> {
70 let track = self.broadcast.track(name)?.subscribe(None).await?;
71 let mut config = moq_json::snapshot::consumer::Config::default();
72 if self.config.compression {
73 config.compression = moq_json::Compression::Deflate;
74 }
75 Ok(moq_json::snapshot::Consumer::new(track, config))
76 }
77}
78
79pub struct Traffic {
83 inner: moq_json::snapshot::Consumer<TrafficFrame>,
84}
85
86impl Traffic {
87 pub async fn next(&mut self) -> Result<Option<TrafficFrame>> {
89 Ok(self.inner.next().await?)
90 }
91}
92
93pub struct Sessions {
95 inner: moq_json::snapshot::Consumer<SessionsFrame>,
96}
97
98impl Sessions {
99 pub async fn next(&mut self) -> Result<Option<SessionsFrame>> {
101 Ok(self.inner.next().await?)
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 fn produce_origin() -> moq_net::origin::Producer {
109 let (producer, driver) = moq_net::origin::Producer::new(moq_net::origin::Config::default());
110 if tokio::runtime::Handle::try_current().is_ok() {
111 tokio::spawn(moq_net::time::run(driver));
112 } else {
113 std::mem::forget(driver);
116 }
117 producer
118 }
119
120 use std::time::Duration;
121
122 use moq_net::{Consume, PathOwned, Timestamp, announce, broadcast, origin, track};
123
124 use crate::{Producer, Tier, produce};
125
126 use super::*;
127
128 fn test_producer() -> (Producer, origin::Producer) {
129 let origin = produce_origin();
130 let producer = Producer::new(
131 produce::Config::new()
132 .with_origin(origin.clone())
133 .with_node(PathOwned::from("sjc")),
134 );
135 (producer, origin)
136 }
137
138 struct Feed {
141 track: track::Producer,
142 sub: track::Subscriber,
143 _announced: announce::Consumer,
144 _source: broadcast::Producer,
145 _ctx: moq_net::stats::Session,
146 }
147
148 impl Feed {
149 async fn write(&mut self, bytes: usize) {
152 let mut group = self.track.append_group().unwrap();
153 group.write_frame(Timestamp::ZERO, vec![0u8; bytes]).unwrap();
154 group.finish().unwrap();
155 let mut group = self.sub.recv_group().await.unwrap().unwrap();
156 while group.read_frame().await.unwrap().is_some() {}
157 }
158 }
159
160 async fn feed(producer: &Producer, tier: Tier, root: &str, path: &str) -> Feed {
161 let ctx = producer.registry().tier(tier).session(root);
162 let feed_origin = produce_origin();
163 let egress = feed_origin.consume().with_stats(ctx.clone());
164
165 let mut announced = egress.announced();
166 let source = feed_origin.create_broadcast(path).unwrap();
167 source.announce(origin::Route::default()).unwrap();
168 let track = source.clone().create_track("video", None).unwrap();
169
170 let update = announced.next().await.expect("announce");
171 assert!(update.kind.is_active());
172 let consumer = egress.request_broadcast(path).await.expect("resolve");
173 let sub = consumer.track("video").unwrap().subscribe(None).await.unwrap();
174
175 Feed {
176 track,
177 sub,
178 _announced: announced,
179 _source: source,
180 _ctx: ctx,
181 }
182 }
183
184 async fn announced(origin: &origin::Producer) -> moq_net::broadcast::Consumer {
185 let mut consumer = origin.consume().announced();
186 tokio::time::advance(Duration::from_millis(1)).await;
187 let update = consumer.next().await.expect("expected announce");
188 assert!(update.kind.is_active());
189 origin
190 .consume()
191 .request_broadcast(moq_net::Path::new(update.prefix.as_str()))
192 .await
193 .expect("resolve")
194 }
195
196 async fn drive_tick() {
197 tokio::time::advance(Duration::from_millis(1100)).await;
198 for _ in 0..4 {
199 tokio::task::yield_now().await;
200 }
201 }
202
203 #[tokio::test(start_paused = true)]
204 async fn plain_and_compressed_round_trip() {
205 let (producer, origin) = test_producer();
209 let tier = Tier::default();
210 let mut fed = feed(&producer, tier.clone(), "acme", "foo/bar").await;
211 fed.write(42).await;
212
213 drive_tick().await;
214
215 let broadcast = announced(&origin).await;
216 let plain = Consumer::new(broadcast.consume(), Config::new());
217 let compressed = Consumer::new(broadcast.consume(), Config::new().with_compression(true));
218
219 let mut plain_traffic = plain.traffic(&tier, Role::Publisher).await.expect("subscribe plain");
220 let mut z_traffic = compressed
221 .traffic(&tier, Role::Publisher)
222 .await
223 .expect("subscribe compressed");
224
225 let plain_frame = plain_traffic.next().await.expect("read").expect("frame");
226 let z_frame = z_traffic.next().await.expect("read").expect("frame");
227 assert_eq!(plain_frame, z_frame, "both flavors carry the same data");
228 assert_eq!(plain_frame.get("foo/bar").expect("entry").bytes, 42);
229
230 fed.write(8).await;
232 drive_tick().await;
233 let plain_frame = plain_traffic.next().await.expect("read").expect("frame");
234 let z_frame = z_traffic.next().await.expect("read").expect("frame");
235 assert_eq!(plain_frame.get("foo/bar").expect("entry").bytes, 50);
236 assert_eq!(plain_frame, z_frame, "delta reconstructs the same frame");
237
238 let mut sessions = compressed.sessions(&tier).await.expect("subscribe sessions");
239 let frame = sessions.next().await.expect("read").expect("frame");
240 assert_eq!(frame.get("acme").expect("root").active(), 1);
241 }
242}