├── .env.example ├── .gitignore ├── README.md ├── env.d.ts ├── now.json ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── screenshot └── android_screenshot.png ├── server ├── .gitignore ├── now.json ├── package-lock.json ├── package.json └── server.js └── src ├── App.css ├── App.js ├── App.test.js ├── components ├── Icons.js ├── goToRoomInput.js └── video.js ├── helpers ├── media-access.js └── simple-peer.js ├── index.css ├── index.js ├── logo.svg ├── registerServiceWorker.js └── styles └── video.css /.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_SIGNALING_SERVER = http://localhost:8080 2 | REACT_APP_STUN_SERVERS = stun:stun4.l.google.com:19302 3 | REACT_APP_TURN_SERVERS = turn:numb.viagenie.ca 4 | 5 | # You can create your turn account here: http://numb.viagenie.ca/cgi-bin/numbacct 6 | REACT_APP_TURN_USERNAME = [email] 7 | REACT_APP_TURN_CREDENCIAL = [password] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /server/node_modules 6 | 7 | # testing 8 | /coverage 9 | 10 | # production 11 | /build 12 | 13 | # misc 14 | .DS_Store 15 | .env 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .env.production 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | .now -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebRTC with react 2 | 3 | This is a simple 1-to-1 video chat room example using react with webRTC and share screen API. This is experimental for learnig purposes. I hope that this project help you to learn something. You can test this on https://react-webrtc-example.herokuapp.com put a room id that you want to create and click enter the other peer have to use the same room id. Sometimes it takes a bit longer to connect(no longer than one minute). 4 | 5 | android screenshot 6 | 7 | ### Running locally 8 | 9 | To run this application you need to create a TURN account. You can create one using this service http://numb.viagenie.ca/cgi-bin/numbacct 10 | 11 | Change the `.env.example` to `.env` or `.env.local` 12 | 13 | Put your turn account on the .env file 14 | 15 | #### The folders 16 | 17 | Front-end files are inside `/src` and the server files are inside `/server` folder 18 | 19 | #### Install the dependencies 20 | 21 | ```shell 22 | npm i 23 | ``` 24 | 25 | #### Start the application 26 | 27 | ```shell 28 | npm run start:local 29 | ``` 30 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | interface ProcessEnv { 3 | REACT_APP_SIGNALING_SERVER: string 4 | REACT_APP_STUN_SERVERS: string 5 | REACT_APP_TURN_SERVERS: string 6 | REACT_APP_TURN_USERNAME: string 7 | REACT_APP_TURN_CREDENCIAL: string 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "routes": [ 4 | { 5 | "handle": "filesystem" 6 | }, 7 | { 8 | "src": "/(.*)", 9 | "dest": "/index.html" 10 | } 11 | ], 12 | "env": { 13 | "SIGNALING_SERVER": "@signaling-server", 14 | "REACT_APP_SIGNALING_SERVER": "@webrtc-react-app_signaling_server", 15 | "REACT_APP_STUN_SERVERS": "@webrtc-react-app_stun_servers", 16 | "REACT_APP_TURN_SERVERS": "@webrtc-react-app_turn_servers", 17 | "REACT_APP_TURN_USERNAME": "@webrtc-react-app_turn_username", 18 | "REACT_APP_TURN_CREDENCIAL": "@webrtc-react-app_turn_credencial" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webrtc", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "express": "^4.16.4", 7 | "react": "^16.8.6", 8 | "react-dom": "^16.8.6", 9 | "react-router-dom": "^4.2.2", 10 | "react-scripts": "2.1.8", 11 | "shortid": "^2.2.14", 12 | "simple-peer": "^9.5.0", 13 | "socket.io": "^2.2.0", 14 | "socket.io-client": "^2.2.0" 15 | }, 16 | "alias": [ 17 | "video-chat.now.sh" 18 | ], 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "start:local": "node server/server.js & export NODE_ENV=local && HTTPS=true react-scripts start", 22 | "build": "export NODE_ENV=production && react-scripts build", 23 | "test": "react-scripts test --env=jsdom", 24 | "eject": "react-scripts eject" 25 | }, 26 | "browserslist": [ 27 | ">0.2%", 28 | "not dead", 29 | "not ie <= 11", 30 | "not op_mini all" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diegogurgel/react-webrtc/c8a9c0226a933d9d235fa371d502ab79a545f42f/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /screenshot/android_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diegogurgel/react-webrtc/c8a9c0226a933d9d235fa371d502ab79a545f42f/screenshot/android_screenshot.png -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | .now 2 | .vercel 3 | -------------------------------------------------------------------------------- /server/now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "./server.js", 6 | "use": "@now/next" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "/" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "signaling", 3 | "version": "0.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@now/node": { 8 | "version": "1.4.1", 9 | "resolved": "https://registry.npmjs.org/@now/node/-/node-1.4.1.tgz", 10 | "integrity": "sha512-EjP/pdBMKsEMCGQ1OLLmBGnjA3QZG1erYTrMqmDVqypeQsY1UUFTY4h1C4d6WNq33qk/nMxpcJzuAhxt+nLQyg==", 11 | "dev": true, 12 | "requires": { 13 | "@types/node": "*" 14 | } 15 | }, 16 | "@types/node": { 17 | "version": "13.9.1", 18 | "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.1.tgz", 19 | "integrity": "sha512-E6M6N0blf/jiZx8Q3nb0vNaswQeEyn0XlupO+xN6DtJ6r6IT4nXrTry7zhIfYvFCl3/8Cu6WIysmUBKiqV0bqQ==", 20 | "dev": true 21 | }, 22 | "accepts": { 23 | "version": "1.3.5", 24 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 25 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 26 | "requires": { 27 | "mime-types": "~2.1.18", 28 | "negotiator": "0.6.1" 29 | } 30 | }, 31 | "after": { 32 | "version": "0.8.2", 33 | "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", 34 | "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" 35 | }, 36 | "array-flatten": { 37 | "version": "1.1.1", 38 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 39 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 40 | }, 41 | "arraybuffer.slice": { 42 | "version": "0.0.7", 43 | "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", 44 | "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" 45 | }, 46 | "async-limiter": { 47 | "version": "1.0.0", 48 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 49 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 50 | }, 51 | "backo2": { 52 | "version": "1.0.2", 53 | "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", 54 | "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" 55 | }, 56 | "base64-arraybuffer": { 57 | "version": "0.1.5", 58 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", 59 | "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" 60 | }, 61 | "base64id": { 62 | "version": "1.0.0", 63 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", 64 | "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" 65 | }, 66 | "better-assert": { 67 | "version": "1.0.2", 68 | "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", 69 | "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", 70 | "requires": { 71 | "callsite": "1.0.0" 72 | } 73 | }, 74 | "blob": { 75 | "version": "0.0.5", 76 | "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", 77 | "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" 78 | }, 79 | "body-parser": { 80 | "version": "1.18.3", 81 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", 82 | "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", 83 | "requires": { 84 | "bytes": "3.0.0", 85 | "content-type": "~1.0.4", 86 | "debug": "2.6.9", 87 | "depd": "~1.1.2", 88 | "http-errors": "~1.6.3", 89 | "iconv-lite": "0.4.23", 90 | "on-finished": "~2.3.0", 91 | "qs": "6.5.2", 92 | "raw-body": "2.3.3", 93 | "type-is": "~1.6.16" 94 | } 95 | }, 96 | "bytes": { 97 | "version": "3.0.0", 98 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 99 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 100 | }, 101 | "callsite": { 102 | "version": "1.0.0", 103 | "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", 104 | "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" 105 | }, 106 | "component-bind": { 107 | "version": "1.0.0", 108 | "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", 109 | "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" 110 | }, 111 | "component-emitter": { 112 | "version": "1.2.1", 113 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 114 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" 115 | }, 116 | "component-inherit": { 117 | "version": "0.0.3", 118 | "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", 119 | "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" 120 | }, 121 | "content-disposition": { 122 | "version": "0.5.2", 123 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 124 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 125 | }, 126 | "content-type": { 127 | "version": "1.0.4", 128 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 129 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 130 | }, 131 | "cookie": { 132 | "version": "0.3.1", 133 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 134 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 135 | }, 136 | "cookie-signature": { 137 | "version": "1.0.6", 138 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 139 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 140 | }, 141 | "debug": { 142 | "version": "2.6.9", 143 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 144 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 145 | "requires": { 146 | "ms": "2.0.0" 147 | } 148 | }, 149 | "depd": { 150 | "version": "1.1.2", 151 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 152 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 153 | }, 154 | "destroy": { 155 | "version": "1.0.4", 156 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 157 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 158 | }, 159 | "ee-first": { 160 | "version": "1.1.1", 161 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 162 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 163 | }, 164 | "encodeurl": { 165 | "version": "1.0.2", 166 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 167 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 168 | }, 169 | "engine.io": { 170 | "version": "3.3.2", 171 | "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.3.2.tgz", 172 | "integrity": "sha512-AsaA9KG7cWPXWHp5FvHdDWY3AMWeZ8x+2pUVLcn71qE5AtAzgGbxuclOytygskw8XGmiQafTmnI9Bix3uihu2w==", 173 | "requires": { 174 | "accepts": "~1.3.4", 175 | "base64id": "1.0.0", 176 | "cookie": "0.3.1", 177 | "debug": "~3.1.0", 178 | "engine.io-parser": "~2.1.0", 179 | "ws": "~6.1.0" 180 | }, 181 | "dependencies": { 182 | "debug": { 183 | "version": "3.1.0", 184 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 185 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 186 | "requires": { 187 | "ms": "2.0.0" 188 | } 189 | } 190 | } 191 | }, 192 | "engine.io-client": { 193 | "version": "3.3.2", 194 | "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.3.2.tgz", 195 | "integrity": "sha512-y0CPINnhMvPuwtqXfsGuWE8BB66+B6wTtCofQDRecMQPYX3MYUZXFNKDhdrSe3EVjgOu4V3rxdeqN/Tr91IgbQ==", 196 | "requires": { 197 | "component-emitter": "1.2.1", 198 | "component-inherit": "0.0.3", 199 | "debug": "~3.1.0", 200 | "engine.io-parser": "~2.1.1", 201 | "has-cors": "1.1.0", 202 | "indexof": "0.0.1", 203 | "parseqs": "0.0.5", 204 | "parseuri": "0.0.5", 205 | "ws": "~6.1.0", 206 | "xmlhttprequest-ssl": "~1.5.4", 207 | "yeast": "0.1.2" 208 | }, 209 | "dependencies": { 210 | "debug": { 211 | "version": "3.1.0", 212 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 213 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 214 | "requires": { 215 | "ms": "2.0.0" 216 | } 217 | } 218 | } 219 | }, 220 | "engine.io-parser": { 221 | "version": "2.1.3", 222 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", 223 | "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", 224 | "requires": { 225 | "after": "0.8.2", 226 | "arraybuffer.slice": "~0.0.7", 227 | "base64-arraybuffer": "0.1.5", 228 | "blob": "0.0.5", 229 | "has-binary2": "~1.0.2" 230 | } 231 | }, 232 | "escape-html": { 233 | "version": "1.0.3", 234 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 235 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 236 | }, 237 | "etag": { 238 | "version": "1.8.1", 239 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 240 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 241 | }, 242 | "express": { 243 | "version": "4.16.4", 244 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", 245 | "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", 246 | "requires": { 247 | "accepts": "~1.3.5", 248 | "array-flatten": "1.1.1", 249 | "body-parser": "1.18.3", 250 | "content-disposition": "0.5.2", 251 | "content-type": "~1.0.4", 252 | "cookie": "0.3.1", 253 | "cookie-signature": "1.0.6", 254 | "debug": "2.6.9", 255 | "depd": "~1.1.2", 256 | "encodeurl": "~1.0.2", 257 | "escape-html": "~1.0.3", 258 | "etag": "~1.8.1", 259 | "finalhandler": "1.1.1", 260 | "fresh": "0.5.2", 261 | "merge-descriptors": "1.0.1", 262 | "methods": "~1.1.2", 263 | "on-finished": "~2.3.0", 264 | "parseurl": "~1.3.2", 265 | "path-to-regexp": "0.1.7", 266 | "proxy-addr": "~2.0.4", 267 | "qs": "6.5.2", 268 | "range-parser": "~1.2.0", 269 | "safe-buffer": "5.1.2", 270 | "send": "0.16.2", 271 | "serve-static": "1.13.2", 272 | "setprototypeof": "1.1.0", 273 | "statuses": "~1.4.0", 274 | "type-is": "~1.6.16", 275 | "utils-merge": "1.0.1", 276 | "vary": "~1.1.2" 277 | } 278 | }, 279 | "finalhandler": { 280 | "version": "1.1.1", 281 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 282 | "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 283 | "requires": { 284 | "debug": "2.6.9", 285 | "encodeurl": "~1.0.2", 286 | "escape-html": "~1.0.3", 287 | "on-finished": "~2.3.0", 288 | "parseurl": "~1.3.2", 289 | "statuses": "~1.4.0", 290 | "unpipe": "~1.0.0" 291 | } 292 | }, 293 | "forwarded": { 294 | "version": "0.1.2", 295 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 296 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 297 | }, 298 | "fresh": { 299 | "version": "0.5.2", 300 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 301 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 302 | }, 303 | "has-binary2": { 304 | "version": "1.0.3", 305 | "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", 306 | "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", 307 | "requires": { 308 | "isarray": "2.0.1" 309 | } 310 | }, 311 | "has-cors": { 312 | "version": "1.1.0", 313 | "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", 314 | "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" 315 | }, 316 | "http-errors": { 317 | "version": "1.6.3", 318 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 319 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 320 | "requires": { 321 | "depd": "~1.1.2", 322 | "inherits": "2.0.3", 323 | "setprototypeof": "1.1.0", 324 | "statuses": ">= 1.4.0 < 2" 325 | } 326 | }, 327 | "iconv-lite": { 328 | "version": "0.4.23", 329 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", 330 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", 331 | "requires": { 332 | "safer-buffer": ">= 2.1.2 < 3" 333 | } 334 | }, 335 | "indexof": { 336 | "version": "0.0.1", 337 | "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", 338 | "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" 339 | }, 340 | "inherits": { 341 | "version": "2.0.3", 342 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 343 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 344 | }, 345 | "ipaddr.js": { 346 | "version": "1.8.0", 347 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", 348 | "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" 349 | }, 350 | "isarray": { 351 | "version": "2.0.1", 352 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", 353 | "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" 354 | }, 355 | "media-typer": { 356 | "version": "0.3.0", 357 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 358 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 359 | }, 360 | "merge-descriptors": { 361 | "version": "1.0.1", 362 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 363 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 364 | }, 365 | "methods": { 366 | "version": "1.1.2", 367 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 368 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 369 | }, 370 | "mime": { 371 | "version": "1.4.1", 372 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 373 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 374 | }, 375 | "mime-db": { 376 | "version": "1.38.0", 377 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", 378 | "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" 379 | }, 380 | "mime-types": { 381 | "version": "2.1.22", 382 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", 383 | "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", 384 | "requires": { 385 | "mime-db": "~1.38.0" 386 | } 387 | }, 388 | "ms": { 389 | "version": "2.0.0", 390 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 391 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 392 | }, 393 | "negotiator": { 394 | "version": "0.6.1", 395 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 396 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 397 | }, 398 | "object-component": { 399 | "version": "0.0.3", 400 | "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", 401 | "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" 402 | }, 403 | "on-finished": { 404 | "version": "2.3.0", 405 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 406 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 407 | "requires": { 408 | "ee-first": "1.1.1" 409 | } 410 | }, 411 | "parseqs": { 412 | "version": "0.0.5", 413 | "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", 414 | "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", 415 | "requires": { 416 | "better-assert": "~1.0.0" 417 | } 418 | }, 419 | "parseuri": { 420 | "version": "0.0.5", 421 | "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", 422 | "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", 423 | "requires": { 424 | "better-assert": "~1.0.0" 425 | } 426 | }, 427 | "parseurl": { 428 | "version": "1.3.2", 429 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 430 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 431 | }, 432 | "path-to-regexp": { 433 | "version": "0.1.7", 434 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 435 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 436 | }, 437 | "proxy-addr": { 438 | "version": "2.0.4", 439 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", 440 | "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", 441 | "requires": { 442 | "forwarded": "~0.1.2", 443 | "ipaddr.js": "1.8.0" 444 | } 445 | }, 446 | "qs": { 447 | "version": "6.5.2", 448 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 449 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 450 | }, 451 | "range-parser": { 452 | "version": "1.2.0", 453 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 454 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 455 | }, 456 | "raw-body": { 457 | "version": "2.3.3", 458 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", 459 | "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", 460 | "requires": { 461 | "bytes": "3.0.0", 462 | "http-errors": "1.6.3", 463 | "iconv-lite": "0.4.23", 464 | "unpipe": "1.0.0" 465 | } 466 | }, 467 | "safe-buffer": { 468 | "version": "5.1.2", 469 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 470 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 471 | }, 472 | "safer-buffer": { 473 | "version": "2.1.2", 474 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 475 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 476 | }, 477 | "send": { 478 | "version": "0.16.2", 479 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 480 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 481 | "requires": { 482 | "debug": "2.6.9", 483 | "depd": "~1.1.2", 484 | "destroy": "~1.0.4", 485 | "encodeurl": "~1.0.2", 486 | "escape-html": "~1.0.3", 487 | "etag": "~1.8.1", 488 | "fresh": "0.5.2", 489 | "http-errors": "~1.6.2", 490 | "mime": "1.4.1", 491 | "ms": "2.0.0", 492 | "on-finished": "~2.3.0", 493 | "range-parser": "~1.2.0", 494 | "statuses": "~1.4.0" 495 | } 496 | }, 497 | "serve-static": { 498 | "version": "1.13.2", 499 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 500 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 501 | "requires": { 502 | "encodeurl": "~1.0.2", 503 | "escape-html": "~1.0.3", 504 | "parseurl": "~1.3.2", 505 | "send": "0.16.2" 506 | } 507 | }, 508 | "setprototypeof": { 509 | "version": "1.1.0", 510 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 511 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 512 | }, 513 | "socket.io": { 514 | "version": "2.2.0", 515 | "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.2.0.tgz", 516 | "integrity": "sha512-wxXrIuZ8AILcn+f1B4ez4hJTPG24iNgxBBDaJfT6MsyOhVYiTXWexGoPkd87ktJG8kQEcL/NBvRi64+9k4Kc0w==", 517 | "requires": { 518 | "debug": "~4.1.0", 519 | "engine.io": "~3.3.1", 520 | "has-binary2": "~1.0.2", 521 | "socket.io-adapter": "~1.1.0", 522 | "socket.io-client": "2.2.0", 523 | "socket.io-parser": "~3.3.0" 524 | }, 525 | "dependencies": { 526 | "debug": { 527 | "version": "4.1.1", 528 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 529 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 530 | "requires": { 531 | "ms": "^2.1.1" 532 | } 533 | }, 534 | "ms": { 535 | "version": "2.1.1", 536 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 537 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 538 | } 539 | } 540 | }, 541 | "socket.io-adapter": { 542 | "version": "1.1.1", 543 | "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", 544 | "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=" 545 | }, 546 | "socket.io-client": { 547 | "version": "2.2.0", 548 | "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.2.0.tgz", 549 | "integrity": "sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==", 550 | "requires": { 551 | "backo2": "1.0.2", 552 | "base64-arraybuffer": "0.1.5", 553 | "component-bind": "1.0.0", 554 | "component-emitter": "1.2.1", 555 | "debug": "~3.1.0", 556 | "engine.io-client": "~3.3.1", 557 | "has-binary2": "~1.0.2", 558 | "has-cors": "1.1.0", 559 | "indexof": "0.0.1", 560 | "object-component": "0.0.3", 561 | "parseqs": "0.0.5", 562 | "parseuri": "0.0.5", 563 | "socket.io-parser": "~3.3.0", 564 | "to-array": "0.1.4" 565 | }, 566 | "dependencies": { 567 | "debug": { 568 | "version": "3.1.0", 569 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 570 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 571 | "requires": { 572 | "ms": "2.0.0" 573 | } 574 | } 575 | } 576 | }, 577 | "socket.io-parser": { 578 | "version": "3.3.0", 579 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", 580 | "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", 581 | "requires": { 582 | "component-emitter": "1.2.1", 583 | "debug": "~3.1.0", 584 | "isarray": "2.0.1" 585 | }, 586 | "dependencies": { 587 | "debug": { 588 | "version": "3.1.0", 589 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 590 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 591 | "requires": { 592 | "ms": "2.0.0" 593 | } 594 | } 595 | } 596 | }, 597 | "statuses": { 598 | "version": "1.4.0", 599 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 600 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 601 | }, 602 | "to-array": { 603 | "version": "0.1.4", 604 | "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", 605 | "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" 606 | }, 607 | "type-is": { 608 | "version": "1.6.16", 609 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 610 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 611 | "requires": { 612 | "media-typer": "0.3.0", 613 | "mime-types": "~2.1.18" 614 | } 615 | }, 616 | "unpipe": { 617 | "version": "1.0.0", 618 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 619 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 620 | }, 621 | "utils-merge": { 622 | "version": "1.0.1", 623 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 624 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 625 | }, 626 | "vary": { 627 | "version": "1.1.2", 628 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 629 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 630 | }, 631 | "ws": { 632 | "version": "6.1.4", 633 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", 634 | "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", 635 | "requires": { 636 | "async-limiter": "~1.0.0" 637 | } 638 | }, 639 | "xmlhttprequest-ssl": { 640 | "version": "1.5.5", 641 | "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", 642 | "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" 643 | }, 644 | "yeast": { 645 | "version": "0.1.2", 646 | "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", 647 | "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" 648 | } 649 | } 650 | } 651 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "signaling", 3 | "version": "0.1.0", 4 | "private": true, 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "engines": { 10 | "node": "12.x" 11 | }, 12 | "dependencies": { 13 | "express": "^4.16.4", 14 | "socket.io": "^2.2.0" 15 | }, 16 | "devDependencies": { 17 | "@now/node": "^1.4.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | var app = require('express')(); 2 | var server = require('http').Server(app); 3 | var io = require('socket.io')(server); 4 | 5 | server.listen(process.env.PORT || 80); 6 | 7 | io.on('connection', function (socket) { 8 | socket.on('join', function (data) { 9 | socket.join(data.roomId); 10 | socket.room = data.roomId; 11 | const sockets = io.of('/').in().adapter.rooms[data.roomId]; 12 | if(sockets.length===1){ 13 | socket.emit('init') 14 | }else{ 15 | if (sockets.length===2){ 16 | io.to(data.roomId).emit('ready') 17 | }else{ 18 | socket.room = null 19 | socket.leave(data.roomId) 20 | socket.emit('full') 21 | } 22 | 23 | } 24 | }); 25 | socket.on('signal', (data) => { 26 | io.to(data.room).emit('desc', data.desc) 27 | }) 28 | socket.on('disconnect', () => { 29 | const roomId = Object.keys(socket.adapter.rooms)[0] 30 | if (socket.room){ 31 | io.to(socket.room).emit('disconnected') 32 | } 33 | 34 | }) 35 | }); 36 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | body,html{ 2 | padding: 0; 3 | margin: 0; 4 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Video from './components/video' 3 | import './App.css'; 4 | import './styles/video.css' 5 | import { BrowserRouter, Route } from 'react-router-dom'; 6 | import { goToRoomInput } from './components/goToRoomInput'; 7 | class App extends Component { 8 | render() { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | } 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/Icons.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | export const ShareScreenIcon = () => ( 3 | 4 | ); 5 | 6 | export const MicOnIcon = () => ( 7 | 8 | ) 9 | 10 | export const MicOffIcon = () => ( 11 | 12 | ) 13 | 14 | export const CamOnIcon = () => ( 15 | 16 | ) 17 | 18 | export const CamOffIcon = () => ( 19 | 20 | ) 21 | -------------------------------------------------------------------------------- /src/components/goToRoomInput.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import shortId from 'shortid' 3 | 4 | const goToRoom = (history, roomId) => { 5 | history.push(`/${roomId}`) 6 | } 7 | 8 | 9 | export function goToRoomInput({history}) { 10 | let [roomId, setRoomId] = useState(shortId.generate()); 11 | 12 | return (
13 |
14 | { 15 | setRoomId(event.target.value) 16 | }}/> 17 | 20 |
21 |
) 22 | } -------------------------------------------------------------------------------- /src/components/video.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import VideoCall from '../helpers/simple-peer'; 3 | import '../styles/video.css'; 4 | import io from 'socket.io-client'; 5 | import { getDisplayStream } from '../helpers/media-access'; 6 | import {ShareScreenIcon,MicOnIcon,MicOffIcon,CamOnIcon,CamOffIcon} from './Icons'; 7 | 8 | class Video extends React.Component { 9 | constructor() { 10 | super(); 11 | this.state = { 12 | localStream: {}, 13 | remoteStreamUrl: '', 14 | streamUrl: '', 15 | initiator: false, 16 | peer: {}, 17 | full: false, 18 | connecting: false, 19 | waiting: true, 20 | micState:true, 21 | camState:true, 22 | }; 23 | } 24 | videoCall = new VideoCall(); 25 | 26 | componentDidMount() { 27 | const socket = io(process.env.REACT_APP_SIGNALING_SERVER); 28 | const component = this; 29 | this.setState({ socket }); 30 | const { roomId } = this.props.match.params; 31 | this.getUserMedia().then(() => { 32 | socket.emit('join', { roomId: roomId }); 33 | }); 34 | 35 | socket.on('init', () => { 36 | component.setState({ initiator: true }); 37 | }); 38 | socket.on('ready', () => { 39 | component.enter(roomId); 40 | }); 41 | socket.on('desc', data => { 42 | if (data.type === 'offer' && component.state.initiator) return; 43 | if (data.type === 'answer' && !component.state.initiator) return; 44 | component.call(data); 45 | }); 46 | socket.on('disconnected', () => { 47 | component.setState({ initiator: true }); 48 | }); 49 | socket.on('full', () => { 50 | component.setState({ full: true }); 51 | }); 52 | } 53 | 54 | 55 | getUserMedia(cb) { 56 | return new Promise((resolve, reject) => { 57 | navigator.getUserMedia = navigator.getUserMedia = 58 | navigator.getUserMedia || 59 | navigator.webkitGetUserMedia || 60 | navigator.mozGetUserMedia; 61 | const op = { 62 | video: { 63 | width: { min: 160, ideal: 640, max: 1280 }, 64 | height: { min: 120, ideal: 360, max: 720 } 65 | }, 66 | audio: true 67 | }; 68 | navigator.getUserMedia( 69 | op, 70 | stream => { 71 | this.setState({ streamUrl: stream, localStream: stream }); 72 | this.localVideo.srcObject = stream; 73 | resolve(); 74 | }, 75 | () => {} 76 | ); 77 | }); 78 | } 79 | 80 | setAudioLocal(){ 81 | if(this.state.localStream.getAudioTracks().length>0){ 82 | this.state.localStream.getAudioTracks().forEach(track => { 83 | track.enabled=!track.enabled; 84 | }); 85 | } 86 | this.setState({ 87 | micState:!this.state.micState 88 | }) 89 | } 90 | 91 | setVideoLocal(){ 92 | if(this.state.localStream.getVideoTracks().length>0){ 93 | this.state.localStream.getVideoTracks().forEach(track => { 94 | track.enabled=!track.enabled; 95 | }); 96 | } 97 | this.setState({ 98 | camState:!this.state.camState 99 | }) 100 | } 101 | 102 | getDisplay() { 103 | getDisplayStream().then(stream => { 104 | stream.oninactive = () => { 105 | this.state.peer.removeStream(this.state.localStream); 106 | this.getUserMedia().then(() => { 107 | this.state.peer.addStream(this.state.localStream); 108 | }); 109 | }; 110 | this.setState({ streamUrl: stream, localStream: stream }); 111 | this.localVideo.srcObject = stream; 112 | this.state.peer.addStream(stream); 113 | }); 114 | } 115 | 116 | enter = roomId => { 117 | this.setState({ connecting: true }); 118 | const peer = this.videoCall.init( 119 | this.state.localStream, 120 | this.state.initiator 121 | ); 122 | this.setState({ peer }); 123 | 124 | peer.on('signal', data => { 125 | const signal = { 126 | room: roomId, 127 | desc: data 128 | }; 129 | this.state.socket.emit('signal', signal); 130 | }); 131 | peer.on('stream', stream => { 132 | this.remoteVideo.srcObject = stream; 133 | this.setState({ connecting: false, waiting: false }); 134 | }); 135 | peer.on('error', function(err) { 136 | console.log(err); 137 | }); 138 | }; 139 | 140 | call = otherId => { 141 | this.videoCall.connect(otherId); 142 | }; 143 | renderFull = () => { 144 | if (this.state.full) { 145 | return 'The room is full'; 146 | } 147 | }; 148 | render() { 149 | return ( 150 |
151 |
152 |
159 |
224 | ); 225 | } 226 | } 227 | 228 | export default Video; 229 | -------------------------------------------------------------------------------- /src/helpers/media-access.js: -------------------------------------------------------------------------------- 1 | export async function getDisplayStream(){ 2 | return navigator.mediaDevices.getDisplayMedia(); 3 | } -------------------------------------------------------------------------------- /src/helpers/simple-peer.js: -------------------------------------------------------------------------------- 1 | import Peer from 'simple-peer' 2 | 3 | export default class VideoCall { 4 | peer = null 5 | init = (stream, initiator) => { 6 | this.peer = new Peer({ 7 | initiator: initiator, 8 | stream: stream, 9 | trickle: false, 10 | reconnectTimer: 1000, 11 | iceTransportPolicy: 'relay', 12 | config: { 13 | iceServers: [ 14 | { urls: process.env.REACT_APP_STUN_SERVERS.split(',') }, 15 | { 16 | urls: process.env.REACT_APP_TURN_SERVERS.split(','), 17 | username: process.env.REACT_APP_TURN_USERNAME, 18 | credential: process.env.REACT_APP_TURN_CREDENCIAL 19 | }, 20 | ] 21 | } 22 | }) 23 | return this.peer 24 | } 25 | connect = (otherId) => { 26 | this.peer.signal(otherId) 27 | } 28 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | height: 100%; 6 | } 7 | #root{ 8 | height: 100%; 9 | display: flex; 10 | justify-content: center; 11 | align-content: center; 12 | background: #252839; 13 | } 14 | .enter-room-container{ 15 | margin: auto; 16 | 17 | } 18 | .enter-room-container form{ 19 | display: flex; 20 | } 21 | .enter-room-container input{ 22 | height: 48px; 23 | background: transparent; 24 | color: #FFF; 25 | font-size: 20px; 26 | outline: none; 27 | border:none; 28 | border-bottom: 2px solid greenyellow; 29 | } 30 | .enter-room-container button { 31 | background: greenyellow; 32 | width: 100px; 33 | border: none; 34 | margin-left: 24px; 35 | font-size: 20px; 36 | color: #252839; 37 | text-transform: uppercase; 38 | font-weight: bold; 39 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | registerServiceWorker(); 9 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/styles/video.css: -------------------------------------------------------------------------------- 1 | .local-video-wrapper { 2 | display: flex; 3 | } 4 | #localVideo { 5 | height: 112px; 6 | right: 0; 7 | margin: 24px auto; 8 | display: inline-block; 9 | border-radius: 4px; 10 | } 11 | .video-wrapper { 12 | height: 100vh; 13 | background: #252839; 14 | width: 100%; 15 | } 16 | #remoteVideo { 17 | background: transparent; 18 | margin: 0 auto; 19 | display: block; 20 | max-width: 100vw; 21 | height: 100%; 22 | } 23 | #remoteVideo.hide { 24 | display: none; 25 | } 26 | .video-wrapper .status { 27 | display: flex; 28 | justify-content: center; 29 | align-items: center; 30 | color: greenyellow; 31 | height: 100vh; 32 | } 33 | 34 | .controls{ 35 | position: absolute; 36 | bottom: 24px; 37 | left: 24px; 38 | } 39 | .control-btn{ 40 | position: relative; 41 | margin-right: 24px; 42 | background: transparent; 43 | outline: none; 44 | border: none; 45 | box-shadow: 1px 1px 8px black; 46 | border-radius: 50%; 47 | background-color: #252839; 48 | height: 64px; 49 | width: 64px; 50 | } 51 | 52 | @media screen and (max-width: 480px) { 53 | .video-wrapper { 54 | position: relative; 55 | max-width: 100vw; 56 | } 57 | #localVideo { 58 | height: 80px; 59 | } 60 | #remoteVideo { 61 | width: 100%; 62 | height: auto; 63 | margin-top: 20%; 64 | } 65 | } 66 | @media screen and (orientation: landscape) { 67 | #localVideo { 68 | position: absolute; 69 | left: 0; 70 | top: 0; 71 | margin-left: 0px; 72 | margin-top: 0px; 73 | } 74 | } 75 | @media screen and (min-width: 768px) { 76 | #localVideo { 77 | margin-left: 32px; 78 | margin-top: 12px; 79 | } 80 | } 81 | --------------------------------------------------------------------------------