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 ConsumerConfig {
13 pub compression: bool,
17}
18
19impl ConsumerConfig {
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: ConsumerConfig,
42}
43
44impl Consumer {
45 pub fn new(broadcast: broadcast::Consumer, config: ConsumerConfig) -> Self {
48 Self { broadcast, config }
49 }
50
51 pub async fn traffic(&self, tier: &Tier, role: Role) -> Result<TrafficConsumer> {
54 let name = traffic_track(tier, role, self.config.compression);
55 Ok(TrafficConsumer {
56 inner: self.subscribe(&name).await?,
57 })
58 }
59
60 pub async fn sessions(&self, tier: &Tier) -> Result<SessionsConsumer> {
63 let name = sessions_track(tier, self.config.compression);
64 Ok(SessionsConsumer {
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 config = moq_json::snapshot::ConsumerConfig::default().with_compression(self.config.compression);
72 Ok(moq_json::snapshot::Consumer::new(track, config))
73 }
74}
75
76pub struct TrafficConsumer {
80 inner: moq_json::snapshot::Consumer<TrafficFrame>,
81}
82
83impl TrafficConsumer {
84 pub async fn next(&mut self) -> Result<Option<TrafficFrame>> {
86 Ok(self.inner.next().await?)
87 }
88}
89
90pub struct SessionsConsumer {
92 inner: moq_json::snapshot::Consumer<SessionsFrame>,
93}
94
95impl SessionsConsumer {
96 pub async fn next(&mut self) -> Result<Option<SessionsFrame>> {
98 Ok(self.inner.next().await?)
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use std::time::Duration;
105
106 use moq_net::{Consume, Origin, PathOwned, Timestamp, announce, broadcast, origin, track};
107
108 use crate::{Producer, ProducerConfig, Tier};
109
110 use super::*;
111
112 fn test_producer() -> (Producer, origin::Producer) {
113 let origin = Origin::random().produce();
114 let producer = Producer::new(
115 ProducerConfig::new()
116 .with_origin(origin.clone())
117 .with_node(PathOwned::from("sjc")),
118 );
119 (producer, origin)
120 }
121
122 struct Feed {
125 track: track::Producer,
126 sub: track::Subscriber,
127 _announced: announce::Consumer,
128 _source: broadcast::Producer,
129 _ctx: moq_net::stats::Session,
130 }
131
132 impl Feed {
133 async fn write(&mut self, bytes: usize) {
136 let mut group = self.track.append_group().unwrap();
137 group.write_frame(Timestamp::ZERO, vec![0u8; bytes]).unwrap();
138 group.finish().unwrap();
139 let mut group = self.sub.recv_group().await.unwrap().unwrap();
140 while group.read_frame().await.unwrap().is_some() {}
141 }
142 }
143
144 async fn feed(producer: &Producer, tier: Tier, root: &str, path: &str) -> Feed {
145 let ctx = producer.registry().tier(tier).session(root);
146 let feed_origin = Origin::random().produce();
147 let egress = feed_origin.consume().with_stats(ctx.clone());
148
149 let mut announced = egress.announced();
150 let mut source = feed_origin
151 .create_broadcast(path, broadcast::Route::announced())
152 .unwrap();
153 let track = source.create_track("video", None).unwrap();
154
155 tokio::time::sleep(Duration::from_millis(1)).await;
156 tokio::time::sleep(Duration::from_millis(1)).await;
157
158 let announce::Update { broadcast, .. } = announced.next().await.expect("announce");
159 let consumer = broadcast.expect("active");
160 let sub = consumer.track("video").unwrap().subscribe(None).await.unwrap();
161
162 Feed {
163 track,
164 sub,
165 _announced: announced,
166 _source: source,
167 _ctx: ctx,
168 }
169 }
170
171 async fn announced(origin: &origin::Producer) -> moq_net::broadcast::Consumer {
172 let mut consumer = origin.consume().announced();
173 tokio::time::advance(Duration::from_millis(1)).await;
174 let announce::Update { broadcast, .. } = consumer.next().await.expect("expected announce");
175 broadcast.expect("active")
176 }
177
178 async fn drive_tick() {
179 tokio::time::advance(Duration::from_millis(1100)).await;
180 for _ in 0..4 {
181 tokio::task::yield_now().await;
182 }
183 }
184
185 #[tokio::test(start_paused = true)]
186 async fn plain_and_compressed_round_trip() {
187 let (producer, origin) = test_producer();
191 let tier = Tier::default();
192 let mut fed = feed(&producer, tier.clone(), "acme", "foo/bar").await;
193 fed.write(42).await;
194
195 drive_tick().await;
196
197 let broadcast = announced(&origin).await;
198 let plain = Consumer::new(broadcast.consume(), ConsumerConfig::new());
199 let compressed = Consumer::new(broadcast.consume(), ConsumerConfig::new().with_compression(true));
200
201 let mut plain_traffic = plain.traffic(&tier, Role::Publisher).await.expect("subscribe plain");
202 let mut z_traffic = compressed
203 .traffic(&tier, Role::Publisher)
204 .await
205 .expect("subscribe compressed");
206
207 let plain_frame = plain_traffic.next().await.expect("read").expect("frame");
208 let z_frame = z_traffic.next().await.expect("read").expect("frame");
209 assert_eq!(plain_frame, z_frame, "both flavors carry the same data");
210 assert_eq!(plain_frame.get("foo/bar").expect("entry").bytes, 42);
211
212 fed.write(8).await;
214 drive_tick().await;
215 let plain_frame = plain_traffic.next().await.expect("read").expect("frame");
216 let z_frame = z_traffic.next().await.expect("read").expect("frame");
217 assert_eq!(plain_frame.get("foo/bar").expect("entry").bytes, 50);
218 assert_eq!(plain_frame, z_frame, "delta reconstructs the same frame");
219
220 let mut sessions = compressed.sessions(&tier).await.expect("subscribe sessions");
221 let frame = sessions.next().await.expect("read").expect("frame");
222 assert_eq!(frame.get("acme").expect("root").active(), 1);
223 }
224}