Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Examples] Add WebRTC DataChannel example #2131

Merged
merged 4 commits into from
May 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}}
```