Repository: Dirvann/webrtc-video-conference-simple-peer Branch: master Commit: 0d95c6fd417c Files: 11 Total size: 17.0 KB Directory structure: gitextract_ufu3lduj/ ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── public/ │ ├── index.html │ └── js/ │ └── main.js ├── src/ │ ├── app.js │ ├── routes.js │ └── socketController.js └── ssl/ ├── cert.pem └── key.pem ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test # parcel-bundler cache (https://parceljs.org/) .cache # Next.js build output .next # Nuxt.js build / generate output .nuxt dist # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and *not* Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Dirk Vanbeveren Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # WebRTC Video Conferencing with simple-peer A simple video conferencing example using simple-peer. This project allows multiple devices to connect with eachother with audio and video using webrtc. The package [simple-peer](https://github.com/feross/simple-peer) is used for webrtc. The implementation of the signaling server is done with [socket.io](https://socket.io/) ## Demo [Demo on heroku](https://dirvann-webrtc-video.herokuapp.com/) ## Running run `npm install` and then `npm start` in the main directory. Then open the browser at `localhost:3012` or `[your network ip/ public dns]:3012`. ## Configuration Configurations can be found in `app.js` and `public/js/main.js`. Replace the ssl certificates `ssl/key.pem` and `ssl/cert.pem` with your own. ================================================ FILE: package.json ================================================ { "name": "mywebrtc", "version": "1.0.0", "description": "", "main": "index.js", "dependencies": { "express": "^4.17.1", "httpolyglot": "^0.1.2", "simple-peer": "^9.7.0", "socket.io": "^2.4.0" }, "devDependencies": {}, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node src/app.js" }, "author": "", "license": "ISC" } ================================================ FILE: public/index.html ================================================

================================================ FILE: public/js/main.js ================================================ /** * Socket.io socket */ let socket; /** * The stream object used to send media */ let localStream = null; /** * All peer connections */ let peers = {} // redirect if not https if(location.href.substr(0,5) !== 'https') location.href = 'https' + location.href.substr(4, location.href.length - 4) //////////// CONFIGURATION ////////////////// /** * RTCPeerConnection configuration */ const configuration = { // Using From https://www.metered.ca/tools/openrelay/ "iceServers": [ { urls: "stun:openrelay.metered.ca:80" }, { urls: "turn:openrelay.metered.ca:80", username: "openrelayproject", credential: "openrelayproject" }, { urls: "turn:openrelay.metered.ca:443", username: "openrelayproject", credential: "openrelayproject" }, { urls: "turn:openrelay.metered.ca:443?transport=tcp", username: "openrelayproject", credential: "openrelayproject" } ] } /** * UserMedia constraints */ let constraints = { audio: true, video: { width: { max: 300 }, height: { max: 300 } } } ///////////////////////////////////////////////////////// constraints.video.facingMode = { ideal: "user" } // enabling the camera at startup navigator.mediaDevices.getUserMedia(constraints).then(stream => { console.log('Received local stream'); localVideo.srcObject = stream; localStream = stream; init() }).catch(e => alert(`getusermedia error ${e.name}`)) /** * initialize the socket connections */ function init() { socket = io() socket.on('initReceive', socket_id => { console.log('INIT RECEIVE ' + socket_id) addPeer(socket_id, false) socket.emit('initSend', socket_id) }) socket.on('initSend', socket_id => { console.log('INIT SEND ' + socket_id) addPeer(socket_id, true) }) socket.on('removePeer', socket_id => { console.log('removing peer ' + socket_id) removePeer(socket_id) }) socket.on('disconnect', () => { console.log('GOT DISCONNECTED') for (let socket_id in peers) { removePeer(socket_id) } }) socket.on('signal', data => { peers[data.socket_id].signal(data.signal) }) } /** * Remove a peer with given socket_id. * Removes the video element and deletes the connection * @param {String} socket_id */ function removePeer(socket_id) { let videoEl = document.getElementById(socket_id) if (videoEl) { const tracks = videoEl.srcObject.getTracks(); tracks.forEach(function (track) { track.stop() }) videoEl.srcObject = null videoEl.parentNode.removeChild(videoEl) } if (peers[socket_id]) peers[socket_id].destroy() delete peers[socket_id] } /** * Creates a new peer connection and sets the event listeners * @param {String} socket_id * ID of the peer * @param {Boolean} am_initiator * Set to true if the peer initiates the connection process. * Set to false if the peer receives the connection. */ function addPeer(socket_id, am_initiator) { peers[socket_id] = new SimplePeer({ initiator: am_initiator, stream: localStream, config: configuration }) peers[socket_id].on('signal', data => { socket.emit('signal', { signal: data, socket_id: socket_id }) }) peers[socket_id].on('stream', stream => { let newVid = document.createElement('video') newVid.srcObject = stream newVid.id = socket_id newVid.playsinline = false newVid.autoplay = true newVid.className = "vid" newVid.onclick = () => openPictureMode(newVid) newVid.ontouchstart = (e) => openPictureMode(newVid) videos.appendChild(newVid) }) } /** * Opens an element in Picture-in-Picture mode * @param {HTMLVideoElement} el video element to put in pip mode */ function openPictureMode(el) { console.log('opening pip') el.requestPictureInPicture() } /** * Switches the camera between user and environment. It will just enable the camera 2 cameras not supported. */ function switchMedia() { if (constraints.video.facingMode.ideal === 'user') { constraints.video.facingMode.ideal = 'environment' } else { constraints.video.facingMode.ideal = 'user' } const tracks = localStream.getTracks(); tracks.forEach(function (track) { track.stop() }) localVideo.srcObject = null navigator.mediaDevices.getUserMedia(constraints).then(stream => { for (let socket_id in peers) { for (let index in peers[socket_id].streams[0].getTracks()) { for (let index2 in stream.getTracks()) { if (peers[socket_id].streams[0].getTracks()[index].kind === stream.getTracks()[index2].kind) { peers[socket_id].replaceTrack(peers[socket_id].streams[0].getTracks()[index], stream.getTracks()[index2], peers[socket_id].streams[0]) break; } } } } localStream = stream localVideo.srcObject = stream updateButtons() }) } /** * Enable screen share */ function setScreen() { navigator.mediaDevices.getDisplayMedia().then(stream => { for (let socket_id in peers) { for (let index in peers[socket_id].streams[0].getTracks()) { for (let index2 in stream.getTracks()) { if (peers[socket_id].streams[0].getTracks()[index].kind === stream.getTracks()[index2].kind) { peers[socket_id].replaceTrack(peers[socket_id].streams[0].getTracks()[index], stream.getTracks()[index2], peers[socket_id].streams[0]) break; } } } } localStream = stream localVideo.srcObject = localStream socket.emit('removeUpdatePeer', '') }) updateButtons() } /** * Disables and removes the local stream and all the connections to other peers. */ function removeLocalStream() { if (localStream) { const tracks = localStream.getTracks(); tracks.forEach(function (track) { track.stop() }) localVideo.srcObject = null } for (let socket_id in peers) { removePeer(socket_id) } } /** * Enable/disable microphone */ function toggleMute() { for (let index in localStream.getAudioTracks()) { localStream.getAudioTracks()[index].enabled = !localStream.getAudioTracks()[index].enabled muteButton.innerText = localStream.getAudioTracks()[index].enabled ? "Unmuted" : "Muted" } } /** * Enable/disable video */ function toggleVid() { for (let index in localStream.getVideoTracks()) { localStream.getVideoTracks()[index].enabled = !localStream.getVideoTracks()[index].enabled vidButton.innerText = localStream.getVideoTracks()[index].enabled ? "Video Enabled" : "Video Disabled" } } /** * updating text of buttons */ function updateButtons() { for (let index in localStream.getVideoTracks()) { vidButton.innerText = localStream.getVideoTracks()[index].enabled ? "Video Enabled" : "Video Disabled" } for (let index in localStream.getAudioTracks()) { muteButton.innerText = localStream.getAudioTracks()[index].enabled ? "Unmuted" : "Muted" } } ================================================ FILE: src/app.js ================================================ const fs = require('fs') const path = require('path') const express = require('express') const app = express() const httpolyglot = require('httpolyglot') const https = require('https') //////// CONFIGURATION /////////// // insert your own ssl certificate and keys const options = { key: fs.readFileSync(path.join(__dirname,'..','ssl','key.pem'), 'utf-8'), cert: fs.readFileSync(path.join(__dirname,'..','ssl','cert.pem'), 'utf-8') } const port = process.env.PORT || 3012 //////////////////////////// require('./routes')(app) const httpsServer = httpolyglot.createServer(options, app) const io = require('socket.io')(httpsServer) require('./socketController')(io) httpsServer.listen(port, () => { console.log(`listening on port ${port}`) }) ================================================ FILE: src/routes.js ================================================ const path = require('path') const express = require('express') module.exports = (app) => { // redirect http traffic to https traffic /*app.use('*', (req, res, next) => { if(!req.socket.encrypted){ console.log('unsecure connection: redirecting..') res.redirect('https://' + req.headers.host + req.path) } else { next() } })*/ app.use(express.static(path.join(__dirname, '..','public'))) app.use(express.static(path.join(__dirname, '..','node_modules'))) } ================================================ FILE: src/socketController.js ================================================ peers = {} module.exports = (io) => { io.on('connect', (socket) => { console.log('a client is connected') // Initiate the connection process as soon as the client connects peers[socket.id] = socket // Asking all other clients to setup the peer connection receiver for(let id in peers) { if(id === socket.id) continue console.log('sending init receive to ' + socket.id) peers[id].emit('initReceive', socket.id) } /** * relay a peerconnection signal to a specific socket */ socket.on('signal', data => { console.log('sending signal from ' + socket.id + ' to ', data) if(!peers[data.socket_id])return peers[data.socket_id].emit('signal', { socket_id: socket.id, signal: data.signal }) }) /** * remove the disconnected peer connection from all other connected clients */ socket.on('disconnect', () => { console.log('socket disconnected ' + socket.id) socket.broadcast.emit('removePeer', socket.id) delete peers[socket.id] }) /** * Send message to client to initiate a connection * The sender has already setup a peer connection receiver */ socket.on('initSend', init_socket_id => { console.log('INIT SEND by ' + socket.id + ' for ' + init_socket_id) peers[init_socket_id].emit('initSend', socket.id) }) }) } ================================================ FILE: ssl/cert.pem ================================================ -----BEGIN CERTIFICATE----- MIICAzCCAWwCCQDUB0G6UpqG9DANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 cyBQdHkgTHRkMCAXDTIwMDUwMzE1MjA0MloYDzIyOTQwMjE2MTUyMDQyWjBFMQsw CQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJu ZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1 NxC8z2k7jEQkF0YkuOSqCBtc4dsb+RgPSkXu6NFMf8g59Sg7EBK29ledRlrszF1j 9ztpgvZCy2PwpJCWu5efCacifRpDege2fPACDre/v04vWfRCzQzgwu21kM9tBPyf VZlM1Z5tjcbmAKrE8/DSMWxgiBP/o343UpZkQ5VLYQIDAQABMA0GCSqGSIb3DQEB CwUAA4GBAK+uEcDRU4BnLkr47TZ4mYm7F9h2OXAqf/9FXrH6iwBqiVenG9j8GHop lLZ0uAkTGUkSKlqe7imaRogKOKWzB89BTl4M3NqheeIlR5rgb8T6h73CIZViVdyF nid/BTSwWY3eu9qeIL6tLf4977ExfIUZCVlL739pasTx6wXQ1h5i -----END CERTIFICATE----- ================================================ FILE: ssl/key.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIICXwIBAAKBgQC1NxC8z2k7jEQkF0YkuOSqCBtc4dsb+RgPSkXu6NFMf8g59Sg7 EBK29ledRlrszF1j9ztpgvZCy2PwpJCWu5efCacifRpDege2fPACDre/v04vWfRC zQzgwu21kM9tBPyfVZlM1Z5tjcbmAKrE8/DSMWxgiBP/o343UpZkQ5VLYQIDAQAB AoGBAKdQplWOM51Vjvy7uHnjdM1BGvKRXlrfjMVhUFyb/dNiEB7jKjgOSRlBLff6 o60LrBbGiMaMso9Nd4MSjqV9oykYQ3zhdSV/W8W7nA5OhXzgJF4cngkrwnxWhLYA mqH8GJWCvz7/7A8Ckeutx8Zn1GTJWcd/CfSd9SDNGAJqJA4BAkEA3RYhdATWwjhZ KsWwFMVzG7fBqCvJLO/Ep9kIvpLtiaQjyNtftS8Nw91IpEVodTAtxxiVBiFRiFxS 3yTWVUOEsQJBANHVDejleEYvT/Rc7aN0bhJVcZuhaN9qPDIXY5UIQTYR1NzgcQKA qCo7u0x0+vQiYJ15YqrRo8ZkdqvyCNlmnbECQQC2KrYF0rbh8WwHQjyD4O2nuRFo cCujSyzO4JXD8WyoLQcPSTLjJ5JAAOUJ9ebMKJaPpkGke2+i2++szb2NI8UBAkEA kKoBmAK0hDbUOdXjpGB+DrfHxpNmmTlF3QcRCcuSIfPzPICkiSQoTE24GMNBzRTy ZT8tzjUQY5QZ2PvaLAA6UQJBAJXLnYMIX0ZkO1nG6mMIWoZtrLpfg+pSFa0VUsHs 7G3iYf9v31Wd6HipMRzg/q63m90nwI5emTYe+EEo4lkGnz8= -----END RSA PRIVATE KEY-----