├── .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 | --------------------------------------------------------------------------------