1#![warn(missing_docs)]
38
39pub mod client;
40pub mod server;
41
42mod codec;
47mod egress;
48mod error;
49mod ingest;
50mod net;
51mod sdp;
52mod session;
53
54pub use axum;
60
61pub use url;
66
67pub use client::Client;
68pub use error::*;
69pub use server::{Response, Server, whep, whip};
70
71#[cfg(test)]
72mod tests {
73 use std::time::Duration;
74
75 use axum::Router;
76 use bytes::Bytes;
77
78 use crate::codec::{Bridge, Frame, Track};
79 use crate::{Client, Server, client, server};
80
81 const TIMEOUT: Duration = Duration::from_secs(10);
82 const OPUS_PACKET: &[u8] = &[0xfc, 0xff, 0xfe];
83
84 #[tokio::test]
85 async fn whip_and_whep_round_trip_opus() {
86 let source_origin = moq_tokio::origin::spawn();
87 let source_consumer = source_origin.consume();
88 let mut announcements = source_consumer.announced();
89 let mut source = source_origin
90 .create_broadcast("source")
91 .expect("create source broadcast");
92 source
93 .announce(moq_net::origin::Route::default())
94 .expect("announce source broadcast");
95 let catalog = moq_mux::catalog::Producer::new(&mut source, moq_mux::catalog::Config::default())
96 .expect("create source catalog");
97 let mut opus = crate::codec::opus::Bridge::new(source, catalog, 48_000, 2).expect("create Opus bridge");
98 Bridge::push(
99 &mut opus,
100 Frame {
101 timestamp_us: 20_000,
102 payload: Bytes::from_static(OPUS_PACKET),
103 },
104 )
105 .expect("publish source packet");
106 let announcement = tokio::time::timeout(TIMEOUT, announcements.next())
107 .await
108 .expect("source announcement timed out")
109 .expect("source origin closed");
110 assert_eq!(announcement.prefix.as_str(), "source");
111 assert!(announcement.kind.is_active(), "source was unannounced");
112 drop(announcements);
113
114 let server_origin = moq_tokio::origin::spawn();
115 let server = Server::new(server::Config::default());
116 let app = Router::new()
117 .nest("/whip", server.publish_router(server_origin.clone()))
118 .nest("/whep", server.subscribe_router(server_origin.consume()));
119 let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
120 .await
121 .expect("bind HTTP listener");
122 let address = listener.local_addr().expect("HTTP listener address");
123 let http = tokio::spawn(async move { axum::serve(listener, app).await.expect("serve HTTP") });
124
125 let client = Client::new(client::Config::default());
126 let whip = format!("http://{address}/whip/ingested").parse().expect("WHIP URL");
127 tokio::time::timeout(TIMEOUT, client.publish(whip, source_consumer, "source"))
128 .await
129 .expect("WHIP negotiation timed out")
130 .expect("WHIP negotiation failed");
131
132 let output_origin = moq_tokio::origin::spawn();
133 let output = output_origin
134 .create_broadcast("output")
135 .expect("create output broadcast");
136 let output_consumer = output.consume();
137 let whep = format!("http://{address}/whep/ingested").parse().expect("WHEP URL");
138 tokio::time::timeout(TIMEOUT, client.subscribe(whep, output))
139 .await
140 .expect("WHEP negotiation timed out")
141 .expect("WHEP negotiation failed");
142
143 let catalog_track = output_consumer
144 .track(hang::Catalog::DEFAULT_NAME)
145 .expect("output catalog track")
146 .subscribe(hang::Catalog::default_subscription())
147 .await
148 .expect("subscribe to output catalog");
149 let mut catalogs = moq_mux::catalog::hang::Consumer::<()>::new(catalog_track);
150 let catalog = tokio::time::timeout(TIMEOUT, catalogs.next())
151 .await
152 .expect("output catalog timed out")
153 .expect("read output catalog")
154 .expect("output catalog ended");
155 let track_name = catalog.audio.renditions.keys().next().expect("output Opus rendition");
156 let track = output_consumer
157 .track(track_name)
158 .expect("output Opus track")
159 .subscribe(None)
160 .await
161 .expect("subscribe to output Opus track");
162 let mut opus = Track::opus(track);
163 let frame = tokio::time::timeout(TIMEOUT, opus.next())
164 .await
165 .expect("output Opus packet timed out")
166 .expect("read output Opus packet")
167 .expect("output Opus track ended");
168 assert_eq!(frame.payload.as_ref(), OPUS_PACKET);
169
170 http.abort();
171 }
172}