1 use anyhow::Result;
2 use clap::{AppSettings, Arg, Command};
3 use std::collections::HashMap;
4 use std::io::Write;
5 use std::sync::Arc;
6 use tokio::time::Duration;
7 use webrtc::api::interceptor_registry::register_default_interceptors;
8 use webrtc::api::media_engine::{MediaEngine, MIME_TYPE_OPUS, MIME_TYPE_VP8};
9 use webrtc::api::APIBuilder;
10 use webrtc::ice_transport::ice_server::RTCIceServer;
11 use webrtc::interceptor::registry::Registry;
12 use webrtc::peer_connection::configuration::RTCConfiguration;
13 use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState;
14 use webrtc::peer_connection::sdp::session_description::RTCSessionDescription;
15 use webrtc::rtcp::payload_feedbacks::picture_loss_indication::PictureLossIndication;
16 use webrtc::rtp_transceiver::rtp_codec::{
17 RTCRtpCodecCapability, RTCRtpCodecParameters, RTPCodecType,
18 };
19 use webrtc::track::track_local::track_local_static_rtp::TrackLocalStaticRTP;
20 use webrtc::track::track_local::{TrackLocal, TrackLocalWriter};
21
22 #[tokio::main]
main() -> Result<()>23 async fn main() -> Result<()> {
24 let mut app = Command::new("reflect")
25 .version("0.1.0")
26 .author("Rain Liu <[email protected]>")
27 .about("An example of how to send back to the user exactly what it receives using the same PeerConnection.")
28 .setting(AppSettings::DeriveDisplayOrder)
29 .subcommand_negates_reqs(true)
30 .arg(
31 Arg::new("FULLHELP")
32 .help("Prints more detailed help information")
33 .long("fullhelp"),
34 )
35 .arg(
36 Arg::new("debug")
37 .long("debug")
38 .short('d')
39 .help("Prints debug log information"),
40 ).arg(
41 Arg::new("audio")
42 .long("audio")
43 .short('a')
44 .help("Enable audio reflect"),
45 ).arg(
46 Arg::new("video")
47 .long("video")
48 .short('v')
49 .help("Enable video reflect"),
50 );
51
52 let matches = app.clone().get_matches();
53
54 if matches.is_present("FULLHELP") {
55 app.print_long_help().unwrap();
56 std::process::exit(0);
57 }
58
59 let audio = matches.is_present("audio");
60 let video = matches.is_present("video");
61 if !audio && !video {
62 println!("one of audio or video must be enabled");
63 std::process::exit(0);
64 }
65 let debug = matches.is_present("debug");
66 if debug {
67 env_logger::Builder::new()
68 .format(|buf, record| {
69 writeln!(
70 buf,
71 "{}:{} [{}] {} - {}",
72 record.file().unwrap_or("unknown"),
73 record.line().unwrap_or(0),
74 record.level(),
75 chrono::Local::now().format("%H:%M:%S.%6f"),
76 record.args()
77 )
78 })
79 .filter(None, log::LevelFilter::Trace)
80 .init();
81 }
82
83 // Everything below is the WebRTC-rs API! Thanks for using it ❤️.
84
85 // Create a MediaEngine object to configure the supported codec
86 let mut m = MediaEngine::default();
87
88 // Setup the codecs you want to use.
89 if audio {
90 m.register_codec(
91 RTCRtpCodecParameters {
92 capability: RTCRtpCodecCapability {
93 mime_type: MIME_TYPE_OPUS.to_owned(),
94 ..Default::default()
95 },
96 payload_type: 120,
97 ..Default::default()
98 },
99 RTPCodecType::Audio,
100 )?;
101 }
102
103 // We'll use a VP8 and Opus but you can also define your own
104 if video {
105 m.register_codec(
106 RTCRtpCodecParameters {
107 capability: RTCRtpCodecCapability {
108 mime_type: MIME_TYPE_VP8.to_owned(),
109 clock_rate: 90000,
110 channels: 0,
111 sdp_fmtp_line: "".to_owned(),
112 rtcp_feedback: vec![],
113 },
114 payload_type: 96,
115 ..Default::default()
116 },
117 RTPCodecType::Video,
118 )?;
119 }
120
121 // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline.
122 // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
123 // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
124 // for each PeerConnection.
125 let mut registry = Registry::new();
126
127 // Use the default set of Interceptors
128 registry = register_default_interceptors(registry, &mut m)?;
129
130 // Create the API object with the MediaEngine
131 let api = APIBuilder::new()
132 .with_media_engine(m)
133 .with_interceptor_registry(registry)
134 .build();
135
136 // Prepare the configuration
137 let config = RTCConfiguration {
138 ice_servers: vec![RTCIceServer {
139 urls: vec!["stun:stun.l.google.com:19302".to_owned()],
140 ..Default::default()
141 }],
142 ..Default::default()
143 };
144
145 // Create a new RTCPeerConnection
146 let peer_connection = Arc::new(api.new_peer_connection(config).await?);
147 let mut output_tracks = HashMap::new();
148 let mut media = vec![];
149 if audio {
150 media.push("audio");
151 }
152 if video {
153 media.push("video");
154 };
155 for s in media {
156 let output_track = Arc::new(TrackLocalStaticRTP::new(
157 RTCRtpCodecCapability {
158 mime_type: if s == "video" {
159 MIME_TYPE_VP8.to_owned()
160 } else {
161 MIME_TYPE_OPUS.to_owned()
162 },
163 ..Default::default()
164 },
165 format!("track-{s}"),
166 "webrtc-rs".to_owned(),
167 ));
168
169 // Add this newly created track to the PeerConnection
170 let rtp_sender = peer_connection
171 .add_track(Arc::clone(&output_track) as Arc<dyn TrackLocal + Send + Sync>)
172 .await?;
173
174 // Read incoming RTCP packets
175 // Before these packets are returned they are processed by interceptors. For things
176 // like NACK this needs to be called.
177 let m = s.to_owned();
178 tokio::spawn(async move {
179 let mut rtcp_buf = vec![0u8; 1500];
180 while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {}
181 println!("{m} rtp_sender.read loop exit");
182 Result::<()>::Ok(())
183 });
184
185 output_tracks.insert(s.to_owned(), output_track);
186 }
187
188 // Wait for the offer to be pasted
189 let line = signal::must_read_stdin()?;
190 let desc_data = signal::decode(line.as_str())?;
191 let offer = serde_json::from_str::<RTCSessionDescription>(&desc_data)?;
192
193 // Set the remote SessionDescription
194 peer_connection.set_remote_description(offer).await?;
195
196 // Set a handler for when a new remote track starts, this handler copies inbound RTP packets,
197 // replaces the SSRC and sends them back
198 let pc = Arc::downgrade(&peer_connection);
199 peer_connection.on_track(Box::new(move |track, _, _| {
200 // Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval
201 // This is a temporary fix until we implement incoming RTCP events, then we would push a PLI only when a viewer requests it
202 let media_ssrc = track.ssrc();
203
204 if track.kind() == RTPCodecType::Video {
205 let pc2 = pc.clone();
206 tokio::spawn(async move {
207 let mut result = Result::<usize>::Ok(0);
208 while result.is_ok() {
209 let timeout = tokio::time::sleep(Duration::from_secs(3));
210 tokio::pin!(timeout);
211
212 tokio::select! {
213 _ = timeout.as_mut() =>{
214 if let Some(pc) = pc2.upgrade(){
215 result = pc.write_rtcp(&[Box::new(PictureLossIndication{
216 sender_ssrc: 0,
217 media_ssrc,
218 })]).await.map_err(Into::into);
219 }else{
220 break;
221 }
222 }
223 };
224 }
225 });
226 }
227
228 let kind = if track.kind() == RTPCodecType::Audio {
229 "audio"
230 } else {
231 "video"
232 };
233 let output_track = if let Some(output_track) = output_tracks.get(kind) {
234 Arc::clone(output_track)
235 } else {
236 println!("output_track not found for type = {kind}");
237 return Box::pin(async {});
238 };
239
240 let output_track2 = Arc::clone(&output_track);
241 tokio::spawn(async move {
242 println!(
243 "Track has started, of type {}: {}",
244 track.payload_type(),
245 track.codec().capability.mime_type
246 );
247 // Read RTP packets being sent to webrtc-rs
248 while let Ok((rtp, _)) = track.read_rtp().await {
249 if let Err(err) = output_track2.write_rtp(&rtp).await {
250 println!("output track write_rtp got error: {err}");
251 break;
252 }
253 }
254
255 println!(
256 "on_track finished, of type {}: {}",
257 track.payload_type(),
258 track.codec().capability.mime_type
259 );
260 });
261
262 Box::pin(async {})
263 }));
264
265 let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1);
266
267 // Set the handler for Peer connection state
268 // This will notify you when the peer has connected/disconnected
269 peer_connection.on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| {
270 println!("Peer Connection State has changed: {s}");
271
272 if s == RTCPeerConnectionState::Failed {
273 // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
274 // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
275 // Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
276 println!("Peer Connection has gone to failed exiting");
277 let _ = done_tx.try_send(());
278 }
279
280 Box::pin(async {})
281 }));
282
283 // Create an answer
284 let answer = peer_connection.create_answer(None).await?;
285
286 // Create channel that is blocked until ICE Gathering is complete
287 let mut gather_complete = peer_connection.gathering_complete_promise().await;
288
289 // Sets the LocalDescription, and starts our UDP listeners
290 peer_connection.set_local_description(answer).await?;
291
292 // Block until ICE Gathering is complete, disabling trickle ICE
293 // we do this because we only can exchange one signaling message
294 // in a production application you should exchange ICE Candidates via OnICECandidate
295 let _ = gather_complete.recv().await;
296
297 // Output the answer in base64 so we can paste it in browser
298 if let Some(local_desc) = peer_connection.local_description().await {
299 let json_str = serde_json::to_string(&local_desc)?;
300 let b64 = signal::encode(&json_str);
301 println!("{b64}");
302 } else {
303 println!("generate local_description failed!");
304 }
305
306 println!("Press ctrl-c to stop");
307 //let timeout = tokio::time::sleep(Duration::from_secs(20));
308 //tokio::pin!(timeout);
309
310 tokio::select! {
311 //_ = timeout.as_mut() => {
312 // println!("received timeout signal!");
313 //}
314 _ = done_rx.recv() => {
315 println!("received done signal!");
316 }
317 _ = tokio::signal::ctrl_c() => {
318 println!();
319 }
320 };
321
322 peer_connection.close().await?;
323
324 Ok(())
325 }
326