xref: /webrtc/examples/examples/reflect/reflect.rs (revision 8ec0a1cb)
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