WebRTC DataChannel Example
View full source code or view the compiled example online
This example creates 2 peer connections and 2 data channels in single browser tab.
Send ping/pong between peer1.dc
and peer2.dc
.
Cargo.toml
The Cargo.toml
enables features necessary to use WebRTC DataChannel and its negotiation.
[package]
authors = ["The wasm-bindgen Developers"]
edition = "2021"
name = "webrtc_datachannel"
publish = false
version = "0.0.0"
[lib]
crate-type = ["cdylib"]
[dependencies]
js-sys = { path = "../../crates/js-sys" }
wasm-bindgen = { path = "../../" }
wasm-bindgen-futures = { path = "../../crates/futures" }
[dependencies.web-sys]
features = [
"MessageEvent",
"RtcPeerConnection",
"RtcSignalingState",
"RtcSdpType",
"RtcSessionDescriptionInit",
"RtcPeerConnectionIceEvent",
"RtcIceCandidate",
"RtcDataChannel",
"RtcDataChannelEvent",
]
path = "../../crates/web-sys"
src/lib.rs
The Rust code connects WebRTC data channel.
#![allow(unused)] fn main() { use js_sys::Reflect; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; use web_sys::{ MessageEvent, RtcDataChannelEvent, RtcPeerConnection, RtcPeerConnectionIceEvent, RtcSdpType, RtcSessionDescriptionInit, }; macro_rules! console_log { ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) } macro_rules! console_warn { ($($t:tt)*) => (warn(&format_args!($($t)*).to_string())) } #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = console)] fn log(s: &str); #[wasm_bindgen(js_namespace = console)] fn warn(s: &str); } #[wasm_bindgen(start)] async fn start() -> Result<(), JsValue> { /* * Set up PeerConnections * pc1 <=> pc2 * */ let pc1 = RtcPeerConnection::new()?; console_log!("pc1 created: state {:?}", pc1.signaling_state()); let pc2 = RtcPeerConnection::new()?; console_log!("pc2 created: state {:?}", pc2.signaling_state()); /* * Create DataChannel on pc1 to negotiate * Message will be shown here after connection established * */ let dc1 = pc1.create_data_channel("my-data-channel"); console_log!("dc1 created: label {:?}", dc1.label()); let dc1_clone = dc1.clone(); let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |ev: MessageEvent| { if let Some(message) = ev.data().as_string() { console_warn!("{:?}", message); dc1_clone.send_with_str("Pong from pc1.dc!").unwrap(); } }); dc1.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref())); onmessage_callback.forget(); /* * If negotiation has done, this closure will be called * */ let ondatachannel_callback = Closure::<dyn FnMut(_)>::new(move |ev: RtcDataChannelEvent| { let dc2 = ev.channel(); console_log!("pc2.ondatachannel!: {:?}", dc2.label()); let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |ev: MessageEvent| { if let Some(message) = ev.data().as_string() { console_warn!("{:?}", message); } }); dc2.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref())); onmessage_callback.forget(); let dc2_clone = dc2.clone(); let onopen_callback = Closure::<dyn FnMut()>::new(move || { dc2_clone.send_with_str("Ping from pc2.dc!").unwrap(); }); dc2.set_onopen(Some(onopen_callback.as_ref().unchecked_ref())); onopen_callback.forget(); }); pc2.set_ondatachannel(Some(ondatachannel_callback.as_ref().unchecked_ref())); ondatachannel_callback.forget(); /* * Handle ICE candidate each other * */ let pc2_clone = pc2.clone(); let onicecandidate_callback1 = Closure::<dyn FnMut(_)>::new(move |ev: RtcPeerConnectionIceEvent| { if let Some(candidate) = ev.candidate() { console_log!("pc1.onicecandidate: {:#?}", candidate.candidate()); let _ = pc2_clone.add_ice_candidate_with_opt_rtc_ice_candidate(Some(&candidate)); } }); pc1.set_onicecandidate(Some(onicecandidate_callback1.as_ref().unchecked_ref())); onicecandidate_callback1.forget(); let pc1_clone = pc1.clone(); let onicecandidate_callback2 = Closure::<dyn FnMut(_)>::new(move |ev: RtcPeerConnectionIceEvent| { if let Some(candidate) = ev.candidate() { console_log!("pc2.onicecandidate: {:#?}", candidate.candidate()); let _ = pc1_clone.add_ice_candidate_with_opt_rtc_ice_candidate(Some(&candidate)); } }); pc2.set_onicecandidate(Some(onicecandidate_callback2.as_ref().unchecked_ref())); onicecandidate_callback2.forget(); /* * Send OFFER from pc1 to pc2 * */ let offer = JsFuture::from(pc1.create_offer()).await?; let offer_sdp = Reflect::get(&offer, &JsValue::from_str("sdp"))? .as_string() .unwrap(); console_log!("pc1: offer {:?}", offer_sdp); let offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer); offer_obj.set_sdp(&offer_sdp); let sld_promise = pc1.set_local_description(&offer_obj); JsFuture::from(sld_promise).await?; console_log!("pc1: state {:?}", pc1.signaling_state()); /* * Receive OFFER from pc1 * Create and send ANSWER from pc2 to pc1 * */ let offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer); offer_obj.set_sdp(&offer_sdp); let srd_promise = pc2.set_remote_description(&offer_obj); JsFuture::from(srd_promise).await?; console_log!("pc2: state {:?}", pc2.signaling_state()); let answer = JsFuture::from(pc2.create_answer()).await?; let answer_sdp = Reflect::get(&answer, &JsValue::from_str("sdp"))? .as_string() .unwrap(); console_log!("pc2: answer {:?}", answer_sdp); let answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer); answer_obj.set_sdp(&answer_sdp); let sld_promise = pc2.set_local_description(&answer_obj); JsFuture::from(sld_promise).await?; console_log!("pc2: state {:?}", pc2.signaling_state()); /* * Receive ANSWER from pc2 * */ let answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer); answer_obj.set_sdp(&answer_sdp); let srd_promise = pc1.set_remote_description(&answer_obj); JsFuture::from(srd_promise).await?; console_log!("pc1: state {:?}", pc1.signaling_state()); Ok(()) } }