Skip to content

Commit

Permalink
[Examples] Add WebRTC DataChannel example (#2131)
Browse files Browse the repository at this point in the history
* Add WebRTC DataChannel example

* Add guide

* Fix format

* Use webpack to build example
  • Loading branch information
Yuji Sugiura committed May 12, 2020
1 parent 6b5f734 commit f94e377
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ members = [
"examples/wasm2js",
"examples/webaudio",
"examples/webgl",
"examples/webrtc_datachannel",
"examples/websockets",
"examples/webxr",
"examples/without-a-bundler",
Expand Down
27 changes: 27 additions & 0 deletions examples/webrtc_datachannel/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "webrtc_datachannel"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2.62"
js-sys = "0.3"
wasm-bindgen-futures = "0.4.12"

[dependencies.web-sys]
version = "0.3.22"
features = [
"MessageEvent",
"RtcPeerConnection",
"RtcSignalingState",
"RtcSdpType",
"RtcSessionDescriptionInit",
"RtcPeerConnectionIceEvent",
"RtcIceCandidate",
"RtcDataChannel",
"RtcDataChannelEvent",
]
15 changes: 15 additions & 0 deletions examples/webrtc_datachannel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# WebRTC DataChannel Example

[View documentation for this example online][dox] or [View compiled example
online][compiled]

[compiled]: https://rustwasm.github.io/wasm-bindgen/exbuild/webrtc_datachannel/
[dox]: https://rustwasm.github.io/wasm-bindgen/examples/webrtc_datachannel.html

You can build the example locally with:

```
$ npm run serve
```

and then visiting http://localhost:8080 in a browser should run the example!
10 changes: 10 additions & 0 deletions examples/webrtc_datachannel/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebRTC DataChannel example</title>
</head>
<body>
<p>Open DevTools and check the Console.</p>
</body>
</html>
3 changes: 3 additions & 0 deletions examples/webrtc_datachannel/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
window.addEventListener('load', async () => {
await import('./pkg');
});
14 changes: 14 additions & 0 deletions examples/webrtc_datachannel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"scripts": {
"build": "webpack",
"serve": "webpack-dev-server"
},
"devDependencies": {
"@wasm-tool/wasm-pack-plugin": "1.0.1",
"text-encoding": "^0.7.0",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.29.4",
"webpack-cli": "^3.1.1",
"webpack-dev-server": "^3.1.0"
}
}
166 changes: 166 additions & 0 deletions examples/webrtc_datachannel/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use js_sys::Reflect;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
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)]
pub 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 shonw 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::wrap(
Box::new(move |ev: MessageEvent| match ev.data().as_string() {
Some(message) => {
console_warn!("{:?}", message);
dc1_clone.send_with_str("Pong from pc1.dc!").unwrap();
}
None => {}
}) as Box<dyn FnMut(MessageEvent)>,
);
dc1.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
onmessage_callback.forget();

/*
* If negotiaion has done, this closure will be called
*
*/
let ondatachannel_callback = Closure::wrap(Box::new(move |ev: RtcDataChannelEvent| {
let dc2 = ev.channel();
console_log!("pc2.ondatachannel!: {:?}", dc2.label());

let onmessage_callback =
Closure::wrap(
Box::new(move |ev: MessageEvent| match ev.data().as_string() {
Some(message) => console_warn!("{:?}", message),
None => {}
}) as Box<dyn FnMut(MessageEvent)>,
);
dc2.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
onmessage_callback.forget();

dc2.send_with_str("Ping from pc2.dc!").unwrap();
}) as Box<dyn FnMut(RtcDataChannelEvent)>);
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::wrap(
Box::new(move |ev: RtcPeerConnectionIceEvent| match ev.candidate() {
Some(candidate) => {
console_log!("pc1.onicecandidate: {:#?}", candidate.candidate());
let _ =
pc2_clone.add_ice_candidate_with_opt_rtc_ice_candidate(Some(&candidate));
}
None => {}
}) as Box<dyn FnMut(RtcPeerConnectionIceEvent)>,
);
pc1.set_onicecandidate(Some(onicecandidate_callback1.as_ref().unchecked_ref()));
onicecandidate_callback1.forget();

let pc1_clone = pc1.clone();
let onicecandidate_callback2 =
Closure::wrap(
Box::new(move |ev: RtcPeerConnectionIceEvent| match ev.candidate() {
Some(candidate) => {
console_log!("pc2.onicecandidate: {:#?}", candidate.candidate());
let _ =
pc1_clone.add_ice_candidate_with_opt_rtc_ice_candidate(Some(&candidate));
}
None => {}
}) as Box<dyn FnMut(RtcPeerConnectionIceEvent)>,
);
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 mut offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer);
offer_obj.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 mut offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer);
offer_obj.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 mut answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer);
answer_obj.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 mut answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer);
answer_obj.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(())
}
27 changes: 27 additions & 0 deletions examples/webrtc_datachannel/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");

module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
}),
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, ".")
}),
// Have this example work in Edge which doesn't ship `TextEncoder` or
// `TextDecoder` at this time.
new webpack.ProvidePlugin({
TextDecoder: ['text-encoding', 'TextDecoder'],
TextEncoder: ['text-encoding', 'TextEncoder']
})
],
mode: 'development'
};
25 changes: 25 additions & 0 deletions guide/src/examples/webrtc_datachannel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# WebRTC DataChannel Example

[View full source code][code] or [view the compiled example online][online]

[online]: https://rustwasm.github.io/wasm-bindgen/exbuild/webrtc_datachannel/
[code]: https://github.com/rustwasm/wasm-bindgen/tree/master/examples/webrtc_datachannel/

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.

```toml
{{#include ../../../examples/webrtc_datachannel/Cargo.toml}}
```

## `src/lib.rs`

The Rust code connects WebRTC data channel.

```rust
{{#include ../../../examples/webrtc_datachannel/src/lib.rs}}
```

0 comments on commit f94e377

Please sign in to comment.