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