use moq_net::broadcast;
use moq_net::stats::{Role, Tier};
use crate::{Result, SessionsFrame, TrafficFrame, sessions_track, traffic_track};
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct Config {
pub compression: bool,
}
impl Config {
pub fn new() -> Self {
Self::default()
}
pub fn with_compression(mut self, compression: bool) -> Self {
self.compression = compression;
self
}
}
pub struct Consumer {
broadcast: broadcast::Consumer,
config: Config,
}
impl Consumer {
pub fn new(broadcast: broadcast::Consumer, config: Config) -> Self {
Self { broadcast, config }
}
pub async fn traffic(&self, tier: &Tier, role: Role) -> Result<Traffic> {
let name = traffic_track(tier, role, self.config.compression);
Ok(Traffic {
inner: self.subscribe(&name).await?,
})
}
pub async fn sessions(&self, tier: &Tier) -> Result<Sessions> {
let name = sessions_track(tier, self.config.compression);
Ok(Sessions {
inner: self.subscribe(&name).await?,
})
}
async fn subscribe<T: serde::de::DeserializeOwned>(&self, name: &str) -> Result<moq_json::snapshot::Consumer<T>> {
let track = self.broadcast.track(name)?.subscribe(None).await?;
let mut config = moq_json::snapshot::consumer::Config::default();
if self.config.compression {
config.compression = moq_json::Compression::Deflate;
}
Ok(moq_json::snapshot::Consumer::new(track, config))
}
}
pub struct Traffic {
inner: moq_json::snapshot::Consumer<TrafficFrame>,
}
impl Traffic {
pub async fn next(&mut self) -> Result<Option<TrafficFrame>> {
Ok(self.inner.next().await?)
}
}
pub struct Sessions {
inner: moq_json::snapshot::Consumer<SessionsFrame>,
}
impl Sessions {
pub async fn next(&mut self) -> Result<Option<SessionsFrame>> {
Ok(self.inner.next().await?)
}
}
#[cfg(test)]
mod tests {
fn produce_origin() -> moq_net::origin::Producer {
let (producer, driver) = moq_net::origin::Producer::new(moq_net::origin::Config::default());
if tokio::runtime::Handle::try_current().is_ok() {
tokio::spawn(moq_net::time::run(driver));
} else {
std::mem::forget(driver);
}
producer
}
use std::time::Duration;
use moq_net::{Consume, PathOwned, Timestamp, announce, broadcast, origin, track};
use crate::{Producer, Tier, produce};
use super::*;
fn test_producer() -> (Producer, origin::Producer) {
let origin = produce_origin();
let producer = Producer::new(
produce::Config::new()
.with_origin(origin.clone())
.with_node(PathOwned::from("sjc")),
);
(producer, origin)
}
struct Feed {
track: track::Producer,
sub: track::Subscriber,
_announced: announce::Consumer,
_source: broadcast::Producer,
_ctx: moq_net::stats::Session,
}
impl Feed {
async fn write(&mut self, bytes: usize) {
let mut group = self.track.append_group().unwrap();
group.write_frame(Timestamp::ZERO, vec![0u8; bytes]).unwrap();
group.finish().unwrap();
let mut group = self.sub.recv_group().await.unwrap().unwrap();
while group.read_frame().await.unwrap().is_some() {}
}
}
async fn feed(producer: &Producer, tier: Tier, root: &str, path: &str) -> Feed {
let ctx = producer.registry().tier(tier).session(root);
let feed_origin = produce_origin();
let egress = feed_origin.consume().with_stats(ctx.clone());
let mut announced = egress.announced();
let source = feed_origin.create_broadcast(path).unwrap();
source.announce(origin::Route::default()).unwrap();
let track = source.clone().create_track("video", None).unwrap();
let update = announced.next().await.expect("announce");
assert!(update.kind.is_active());
let consumer = egress.request_broadcast(path).await.expect("resolve");
let sub = consumer.track("video").unwrap().subscribe(None).await.unwrap();
Feed {
track,
sub,
_announced: announced,
_source: source,
_ctx: ctx,
}
}
async fn announced(origin: &origin::Producer) -> moq_net::broadcast::Consumer {
let mut consumer = origin.consume().announced();
tokio::time::advance(Duration::from_millis(1)).await;
let update = consumer.next().await.expect("expected announce");
assert!(update.kind.is_active());
origin
.consume()
.request_broadcast(moq_net::Path::new(update.prefix.as_str()))
.await
.expect("resolve")
}
async fn drive_tick() {
tokio::time::advance(Duration::from_millis(1100)).await;
for _ in 0..4 {
tokio::task::yield_now().await;
}
}
#[tokio::test(start_paused = true)]
async fn plain_and_compressed_round_trip() {
let (producer, origin) = test_producer();
let tier = Tier::default();
let mut fed = feed(&producer, tier.clone(), "acme", "foo/bar").await;
fed.write(42).await;
drive_tick().await;
let broadcast = announced(&origin).await;
let plain = Consumer::new(broadcast.consume(), Config::new());
let compressed = Consumer::new(broadcast.consume(), Config::new().with_compression(true));
let mut plain_traffic = plain.traffic(&tier, Role::Publisher).await.expect("subscribe plain");
let mut z_traffic = compressed
.traffic(&tier, Role::Publisher)
.await
.expect("subscribe compressed");
let plain_frame = plain_traffic.next().await.expect("read").expect("frame");
let z_frame = z_traffic.next().await.expect("read").expect("frame");
assert_eq!(plain_frame, z_frame, "both flavors carry the same data");
assert_eq!(plain_frame.get("foo/bar").expect("entry").bytes, 42);
fed.write(8).await;
drive_tick().await;
let plain_frame = plain_traffic.next().await.expect("read").expect("frame");
let z_frame = z_traffic.next().await.expect("read").expect("frame");
assert_eq!(plain_frame.get("foo/bar").expect("entry").bytes, 50);
assert_eq!(plain_frame, z_frame, "delta reconstructs the same frame");
let mut sessions = compressed.sessions(&tier).await.expect("subscribe sessions");
let frame = sessions.next().await.expect("read").expect("frame");
assert_eq!(frame.get("acme").expect("root").active(), 1);
}
}