├── .tool-versions
├── .gitignore
├── generate_cert.sh
├── package.json
├── client
├── index.html
└── webrtc.js
├── LICENSE
├── cert.pem
├── README.md
├── server
└── server.js
└── key.pem
/.tool-versions:
--------------------------------------------------------------------------------
1 | nodejs 22.11.0
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 |
--------------------------------------------------------------------------------
/generate_cert.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 3650 -nodes
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "WebRTC-Example",
3 | "version": "1.0.0",
4 | "description": "An 'as simple as it gets' WebRTC example.",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node server/server.js",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/shanet/WebRTC-Example.git"
13 | },
14 | "keywords": ["webrtc"],
15 | "author": "Kira Tully",
16 | "license": "MIT",
17 | "bugs": {
18 | "url": "https://github.com/shanet/WebRTC-Example/issues"
19 | },
20 | "homepage": "https://github.com/shanet/WebRTC-Example",
21 | "dependencies": {
22 | "ws": "^8.17.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Open this page in a second browser window then click below to start the WebRTC connection.
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Kira Tully
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | 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
32 | -----END CERTIFICATE-----
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WebRTC Example
2 | ==============
3 |
4 | An 'as simple as it gets' WebRTC example.
5 |
6 | See [https://ephemeral.cx/2014/09/a-dead-simple-webrtc-example/](https://ephemeral.cx/2014/09/a-dead-simple-webrtc-example/) for a detailed walkthrough of the code.
7 |
8 | Note: This repo is kept updated. The general ideas are there, but the above blog post may be somewhat out of date with the code in this repo.
9 |
10 | ## Usage
11 |
12 | If you use `asdf` to manage tool versions a `.tool-versions` file is included in this repo.
13 |
14 | The signaling server uses Node.js and `ws` and can be started as such:
15 |
16 | ```
17 | $ npm install
18 | $ npm start
19 | ```
20 |
21 | With the server running, open Firefox/Chrome/Safari and visit `https://localhost:8443`.
22 |
23 | Please note the following:
24 |
25 | * Note the HTTPS! There is no redirect from HTTP to HTTPS.
26 | * You\'ll need to accept the invalid TLS certificate as it is self-signed and WebRTC must be run over TLS.
27 | * Some browsers or OSs may not allow the webcam to be used by multiple pages at once. You may need to use two different browsers or machines.
28 |
29 | ## Problems?
30 |
31 | This is a short example that I don't check often. As such, I rely on users for reports if something breaks. Issues and pull requests are greatly appreciated.
32 |
33 | ## License
34 |
35 | The MIT License (MIT)
36 |
37 | Copyright (c) 2014 Kira Tully
38 |
39 | Permission is hereby granted, free of charge, to any person obtaining a copy
40 | of this software and associated documentation files (the "Software"), to deal
41 | in the Software without restriction, including without limitation the rights
42 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
43 | copies of the Software, and to permit persons to whom the Software is
44 | furnished to do so, subject to the following conditions:
45 |
46 | The above copyright notice and this permission notice shall be included in
47 | all copies or substantial portions of the Software.
48 |
49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
50 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
51 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
52 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
53 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
54 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
55 | THE SOFTWARE.
56 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const HTTPS_PORT = 8443;
2 |
3 | const fs = require('fs');
4 | const https = require('https');
5 | const WebSocket = require('ws');
6 | const WebSocketServer = WebSocket.Server;
7 |
8 | // Yes, TLS is required for WebRTC
9 | const serverConfig = {
10 | key: fs.readFileSync('key.pem'),
11 | cert: fs.readFileSync('cert.pem'),
12 | };
13 |
14 | function main() {
15 | const httpsServer = startHttpsServer(serverConfig);
16 | startWebSocketServer(httpsServer);
17 | printHelp();
18 | }
19 |
20 | function startHttpsServer(serverConfig) {
21 | // Handle incoming requests from the client
22 | const handleRequest = (request, response) => {
23 | console.log(`request received: ${request.url}`);
24 |
25 | // This server only serves two files: The HTML page and the client JS file
26 | if(request.url === '/') {
27 | response.writeHead(200, {'Content-Type': 'text/html'});
28 | response.end(fs.readFileSync('client/index.html'));
29 | } else if(request.url === '/webrtc.js') {
30 | response.writeHead(200, {'Content-Type': 'application/javascript'});
31 | response.end(fs.readFileSync('client/webrtc.js'));
32 | }
33 | };
34 |
35 | const httpsServer = https.createServer(serverConfig, handleRequest);
36 | httpsServer.listen(HTTPS_PORT, '0.0.0.0');
37 | return httpsServer;
38 | }
39 |
40 | function startWebSocketServer(httpsServer) {
41 | // Create a server for handling websocket calls
42 | const wss = new WebSocketServer({server: httpsServer});
43 |
44 | wss.on('connection', (ws) => {
45 | ws.on('message', (message) => {
46 | // Broadcast any received message to all clients
47 | console.log(`received: ${message}`);
48 | wss.broadcast(message);
49 | });
50 | });
51 |
52 | wss.broadcast = function(data) {
53 | this.clients.forEach((client) => {
54 | if(client.readyState === WebSocket.OPEN) {
55 | client.send(data, {binary: false});
56 | }
57 | });
58 | };
59 | }
60 |
61 | function printHelp() {
62 | console.log(`Server running. Visit https://localhost:${HTTPS_PORT} in Firefox/Chrome/Safari.\n`);
63 | console.log('Please note the following:');
64 | console.log(' * Note the HTTPS in the URL; there is no HTTP -> HTTPS redirect.');
65 | console.log(' * You\'ll need to accept the invalid TLS certificate as it is self-signed.');
66 | console.log(' * Some browsers or OSs may not allow the webcam to be used by multiple pages at once. You may need to use two different browsers or machines.');
67 | }
68 |
69 | main();
70 |
--------------------------------------------------------------------------------
/key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | 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
51 | ZEbffYj4hKrpVVuPFYwKzuLcXP1vTFQ=
52 | -----END PRIVATE KEY-----
53 |
--------------------------------------------------------------------------------
/client/webrtc.js:
--------------------------------------------------------------------------------
1 | let localStream;
2 | let localVideo;
3 | let peerConnection;
4 | let remoteVideo;
5 | let serverConnection;
6 | let uuid;
7 |
8 | const peerConnectionConfig = {
9 | 'iceServers': [
10 | {'urls': 'stun:stun.stunprotocol.org:3478'},
11 | {'urls': 'stun:stun.l.google.com:19302'},
12 | ]
13 | };
14 |
15 | async function pageReady() {
16 | uuid = createUUID();
17 |
18 | localVideo = document.getElementById('localVideo');
19 | remoteVideo = document.getElementById('remoteVideo');
20 |
21 | serverConnection = new WebSocket(`wss://${window.location.hostname}:8443`);
22 | serverConnection.onmessage = gotMessageFromServer;
23 |
24 | const constraints = {
25 | video: true,
26 | audio: true,
27 | };
28 |
29 | if(!navigator.mediaDevices.getUserMedia) {
30 | alert('Your browser does not support getUserMedia API');
31 | return;
32 | }
33 |
34 | try {
35 | const stream = await navigator.mediaDevices.getUserMedia(constraints);
36 |
37 | localStream = stream;
38 | localVideo.srcObject = stream;
39 | } catch(error) {
40 | errorHandler(error);
41 | }
42 | }
43 |
44 | function start(isCaller) {
45 | peerConnection = new RTCPeerConnection(peerConnectionConfig);
46 | peerConnection.onicecandidate = gotIceCandidate;
47 | peerConnection.ontrack = gotRemoteStream;
48 |
49 | for(const track of localStream.getTracks()) {
50 | peerConnection.addTrack(track, localStream);
51 | }
52 |
53 | if(isCaller) {
54 | peerConnection.createOffer().then(createdDescription).catch(errorHandler);
55 | }
56 | }
57 |
58 | function gotMessageFromServer(message) {
59 | if(!peerConnection) start(false);
60 |
61 | const signal = JSON.parse(message.data);
62 |
63 | // Ignore messages from ourself
64 | if(signal.uuid == uuid) return;
65 |
66 | if(signal.sdp) {
67 | peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(() => {
68 | // Only create answers in response to offers
69 | if(signal.sdp.type !== 'offer') return;
70 |
71 | peerConnection.createAnswer().then(createdDescription).catch(errorHandler);
72 | }).catch(errorHandler);
73 | } else if(signal.ice) {
74 | peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(errorHandler);
75 | }
76 | }
77 |
78 | function gotIceCandidate(event) {
79 | if(event.candidate != null) {
80 | serverConnection.send(JSON.stringify({'ice': event.candidate, 'uuid': uuid}));
81 | }
82 | }
83 |
84 | function createdDescription(description) {
85 | console.log('got description');
86 |
87 | peerConnection.setLocalDescription(description).then(() => {
88 | serverConnection.send(JSON.stringify({'sdp': peerConnection.localDescription, 'uuid': uuid}));
89 | }).catch(errorHandler);
90 | }
91 |
92 | function gotRemoteStream(event) {
93 | console.log('got remote stream');
94 | remoteVideo.srcObject = event.streams[0];
95 | }
96 |
97 | function errorHandler(error) {
98 | console.log(error);
99 | }
100 |
101 | // Taken from http://stackoverflow.com/a/105074/515584
102 | // Strictly speaking, it's not a real UUID, but it gets the job done here
103 | function createUUID() {
104 | function s4() {
105 | return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
106 | }
107 |
108 | return `${s4() + s4()}-${s4()}-${s4()}-${s4()}-${s4() + s4() + s4()}`;
109 | }
110 |
--------------------------------------------------------------------------------