Skip to content

Commit 4453554

Browse files
committed
Initial commit
0 parents  commit 4453554

37 files changed

+2278
-0
lines changed

.eslintrc

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
root: true
2+
3+
env:
4+
es6: true
5+
node: true
6+
browser: true
7+
8+
parserOptions:
9+
ecmaVersion: 2018
10+
11+
extends:
12+
'eslint:recommended'
13+
14+
rules:
15+
array-bracket-spacing: 2
16+
block-spacing: 2
17+
callback-return: 0
18+
camelcase: 2
19+
comma-spacing: 2
20+
comma-style: [2, last]
21+
computed-property-spacing: 2
22+
consistent-return: 1
23+
consistent-this: [2, self]
24+
dot-location: [2, property]
25+
dot-notation: 2
26+
eol-last: 2
27+
eqeqeq: 2
28+
func-style: [2, declaration]
29+
handle-callback-err: 2
30+
key-spacing: 2
31+
linebreak-style: [2, unix]
32+
new-cap: 2
33+
new-parens: 2
34+
no-alert: 2
35+
no-caller: 2
36+
no-console: 1
37+
no-const-assign: 2
38+
no-else-return: 2
39+
no-eval: 2
40+
no-extend-native: 2
41+
no-extra-bind: 2
42+
no-implicit-coercion: 0
43+
no-implied-eval: 2
44+
no-invalid-this: 2
45+
no-iterator: 2
46+
no-labels: 2
47+
no-lone-blocks: 2
48+
no-lonely-if: 2
49+
no-loop-func: 2
50+
no-multiple-empty-lines: 2
51+
no-nested-ternary: 0
52+
no-new-object: 2
53+
no-new-require: 2
54+
no-path-concat: 2
55+
no-process-env: 2
56+
no-process-exit: 2
57+
no-proto: 2
58+
no-return-assign: 2
59+
no-shadow: 0
60+
no-shadow-restricted-names: 2
61+
no-script-url: 2
62+
no-self-compare: 2
63+
no-sequences: 2
64+
no-spaced-func: 2
65+
no-throw-literal: 2
66+
no-trailing-spaces: 2
67+
no-undef-init: 2
68+
no-undefined: 2
69+
no-unneeded-ternary: 2
70+
no-unused-expressions: 2
71+
no-use-before-define: [2, nofunc]
72+
no-useless-call: 2
73+
no-useless-concat: 2
74+
no-warning-comments: 1
75+
no-with: 1
76+
object-curly-spacing: [2, always]
77+
one-var: [2, never]
78+
operator-assignment: [2, always]
79+
quote-props: [2, consistent]
80+
quotes: [2, single]
81+
semi: [2, always]
82+
keyword-spacing: 2
83+
space-before-blocks: [2, always]
84+
space-before-function-paren: [2, {
85+
anonymous: 'never',
86+
named: 'never',
87+
asyncArrow: 'always'
88+
}]
89+
space-infix-ops: 2
90+
space-unary-ops: 2
91+
spaced-comment: 1
92+
strict: [2, global]

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
package-lock.json

README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
node-webrtc-examples
2+
====================
3+
4+
This project presents a few example applications using node-webrtc.
5+
6+
- [audio-video-loopback](examples/audio-video-loopback): relays incoming audio
7+
and video using RTCRtpTransceivers.
8+
- [ping-pong](examples/ping-pong): simple RTCDataChannel ping/pong example.
9+
- [pitch-detector](examples/pitch-detector): pitch detector implemented using
10+
RTCAudioSink and RTCDataChannel.
11+
- [sine-wave](examples/sine-wave): generates a sine wave using RTCAudioSource;
12+
frequency control implemented using RTCDataChannel.
13+
- [sine-wave-stereo](examples/sine-wave-stereo): generates a stereo sine wave
14+
using RTCAudioSource; panning control implemented using RTCDataChannel.
15+
- [video-compositing](examples/video-compositing): uses RTCVideoSink,
16+
[node-canvas](https://github.com/Automattic/node-canvas), and RTCVideoSource
17+
to draw spinning text on top of an incoming video.
18+
19+
Usage
20+
-----
21+
22+
Install the project's dependencies and run the tests before starting the
23+
application server:
24+
25+
```
26+
npm install
27+
npm test
28+
npm start
29+
```
30+
31+
Then, navigate to [http://localhost:3000](http://localhost:3000).
32+
33+
Architecture
34+
------------
35+
36+
Each example application under [examples/](examples) has a Client and Server
37+
component. RTCPeerConnection negotiation is supported via a REST API (described
38+
below), and is abstracted away from each example application. Code for
39+
RTCPeerConnection negotiation lives under [lib/](lib).
40+
41+
### RTCPeerConnection Negotiation
42+
43+
RTCPeerConnections are negotiated via REST API. The Server always offers (with
44+
host candidates) and the Client always answers. In order to negotiate a new
45+
RTCPeerConnection, the Client first POSTs to `/connections`. The Server responds
46+
with an RTCPeerConnection ID and SDP offer. Finally, the Client POSTs an SDP
47+
answer to the RTCPeerConnection's URL.
48+
49+
```
50+
Client Server
51+
| |
52+
| POST /connections |
53+
| |
54+
|---------------------------------------------------->|
55+
| |
56+
| 200 OK |
57+
| { "id": "$ID", "localDescription": "$SDP_OFFER" } |
58+
| |
59+
|<----------------------------------------------------|
60+
| |
61+
| POST /connections/$ID/remote-description |
62+
| $SDP_ANSWER |
63+
| |
64+
|---------------------------------------------------->|
65+
| |
66+
| 200 OK |
67+
| |
68+
|<----------------------------------------------------|
69+
```
70+
71+
### RTCPeerConnection Teardown
72+
73+
RTCPeerConnections can be proactively torn down by sending a DELETE to the
74+
RTCPeerConnection's URL; otherwise, ICE disconnection or failure, if unresolved
75+
within the `timeToReconnect` window, will also trigger teardown. The default
76+
`timeToReconnect` value is 10 s.
77+
78+
```
79+
Client Server
80+
| |
81+
| DELETE /connections/$ID |
82+
| |
83+
|---------------------------------------------------->|
84+
| |
85+
| 200 OK |
86+
| |
87+
|<----------------------------------------------------|
88+
```
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict';
2+
3+
const createExample = require('../../lib/browser/example');
4+
5+
const description = 'This example simply relays incoming audio and video using \
6+
RTCRtpTransceivers.';
7+
8+
const localVideo = document.createElement('video');
9+
localVideo.autoplay = true;
10+
localVideo.muted = true;
11+
12+
const remoteVideo = document.createElement('video');
13+
remoteVideo.autoplay = true;
14+
15+
async function beforeAnswer(peerConnection) {
16+
const localStream = await window.navigator.mediaDevices.getUserMedia({
17+
audio: true,
18+
video: true
19+
});
20+
21+
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
22+
23+
localVideo.srcObject = localStream;
24+
25+
const remoteStream = new MediaStream(peerConnection.getReceivers().map(receiver => receiver.track));
26+
remoteVideo.srcObject = remoteStream;
27+
28+
// NOTE(mroberts): This is a hack so that we can get a callback when the
29+
// RTCPeerConnection is closed. In the future, we can subscribe to
30+
// "connectionstatechange" events.
31+
const { close } = peerConnection;
32+
peerConnection.close = function() {
33+
remoteVideo.srcObject = null;
34+
35+
localVideo.srcObject = null;
36+
37+
localStream.getTracks().forEach(track => track.stop());
38+
39+
return close.apply(this, arguments);
40+
};
41+
}
42+
43+
createExample('audio-video-loopback', description, { beforeAnswer });
44+
45+
const videos = document.createElement('div');
46+
videos.className = 'grid';
47+
videos.appendChild(localVideo);
48+
videos.appendChild(remoteVideo);
49+
document.body.appendChild(videos);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
3+
function beforeOffer(peerConnection) {
4+
const audioTransceiver = peerConnection.addTransceiver('audio');
5+
const videoTransceiver = peerConnection.addTransceiver('video');
6+
return Promise.all([
7+
audioTransceiver.sender.replaceTrack(audioTransceiver.receiver.track),
8+
videoTransceiver.sender.replaceTrack(videoTransceiver.receiver.track)
9+
]);
10+
}
11+
12+
module.exports = { beforeOffer };

examples/ping-pong/client.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
const createExample = require('../../lib/browser/example');
4+
5+
const description = 'This example sends a &ldquo;ping&rdquo; from the client \
6+
over an RTCDataChannel. Upon receipt, node-webrtc responds with a \
7+
&ldquo;pong&rdquo;. Open the Console to see the pings and pongs&hellip;';
8+
9+
function beforeAnswer(peerConnection) {
10+
let dataChannel = null;
11+
let interval = null;
12+
13+
function onMessage({ data }) {
14+
if (data === 'pong') {
15+
console.log('received pong');
16+
}
17+
}
18+
19+
function onDataChannel({ channel }) {
20+
if (channel.label !== 'ping-pong') {
21+
return;
22+
}
23+
24+
dataChannel = channel;
25+
dataChannel.addEventListener('message', onMessage);
26+
27+
interval = setInterval(() => {
28+
console.log('sending ping');
29+
dataChannel.send('ping');
30+
}, 1000);
31+
}
32+
33+
peerConnection.addEventListener('datachannel', onDataChannel);
34+
35+
// NOTE(mroberts): This is a hack so that we can get a callback when the
36+
// RTCPeerConnection is closed. In the future, we can subscribe to
37+
// "connectionstatechange" events.
38+
const { close } = peerConnection;
39+
peerConnection.close = function() {
40+
if (dataChannel) {
41+
dataChannel.removeEventListener('message', onMessage);
42+
}
43+
if (interval) {
44+
clearInterval(interval);
45+
}
46+
return close.apply(this, arguments);
47+
};
48+
}
49+
50+
createExample('ping-pong', description, { beforeAnswer });

examples/ping-pong/server.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
function beforeOffer(peerConnection) {
4+
const dataChannel = peerConnection.createDataChannel('ping-pong');
5+
6+
function onMessage({ data }) {
7+
if (data === 'ping') {
8+
dataChannel.send('pong');
9+
}
10+
}
11+
12+
dataChannel.addEventListener('message', onMessage);
13+
14+
// NOTE(mroberts): This is a hack so that we can get a callback when the
15+
// RTCPeerConnection is closed. In the future, we can subscribe to
16+
// "connectionstatechange" events.
17+
const { close } = peerConnection;
18+
peerConnection.close = function() {
19+
dataChannel.removeEventListener('message', onMessage);
20+
return close.apply(this, arguments);
21+
};
22+
}
23+
24+
module.exports = { beforeOffer };

0 commit comments

Comments
 (0)