Client prototype, signaling server & PWA client

This commit is contained in:
gltron
2020-03-26 00:46:45 +01:00
commit ea013cd6fd
52 changed files with 16140 additions and 0 deletions

5
client/.editorconfig Normal file
View File

@@ -0,0 +1,5 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

21
client/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

24
client/README.md Normal file
View File

@@ -0,0 +1,24 @@
# client
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

5
client/babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

12638
client/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

49
client/package.json Normal file
View File

@@ -0,0 +1,49 @@
{
"name": "client",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"buefy": "^0.8.13",
"core-js": "^3.6.4",
"register-service-worker": "^1.6.2",
"vue": "^2.6.11"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.2.0",
"@vue/cli-plugin-eslint": "~4.2.0",
"@vue/cli-plugin-pwa": "~4.2.0",
"@vue/cli-service": "~4.2.0",
"@vue/eslint-config-standard": "^5.1.0",
"babel-eslint": "^10.0.3",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.1.2",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"@vue/standard"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}

BIN
client/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,149 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="16.000000pt" height="16.000000pt" viewBox="0 0 16.000000 16.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,16.000000) scale(0.000320,-0.000320)"
fill="#000000" stroke="none">
<path d="M18 46618 c45 -75 122 -207 122 -211 0 -2 25 -45 55 -95 30 -50 55
-96 55 -102 0 -5 5 -10 10 -10 6 0 10 -4 10 -9 0 -5 73 -135 161 -288 89 -153
173 -298 187 -323 14 -25 32 -57 41 -72 88 -149 187 -324 189 -335 2 -7 8 -13
13 -13 5 0 9 -4 9 -10 0 -5 46 -89 103 -187 175 -302 490 -846 507 -876 8 -16
20 -36 25 -45 28 -46 290 -498 339 -585 13 -23 74 -129 136 -236 61 -107 123
-215 137 -240 14 -25 29 -50 33 -56 5 -5 23 -37 40 -70 18 -33 38 -67 44 -75
11 -16 21 -33 63 -109 14 -25 29 -50 33 -56 4 -5 21 -35 38 -65 55 -100 261
-455 269 -465 4 -5 14 -21 20 -35 15 -29 41 -75 103 -180 24 -41 52 -88 60
-105 9 -16 57 -100 107 -185 112 -193 362 -626 380 -660 8 -14 23 -38 33 -55
11 -16 23 -37 27 -45 4 -8 26 -46 48 -85 23 -38 53 -90 67 -115 46 -81 64
-113 178 -310 62 -107 121 -210 132 -227 37 -67 56 -99 85 -148 16 -27 32 -57
36 -65 4 -8 15 -27 25 -42 9 -15 53 -89 96 -165 44 -76 177 -307 296 -513 120
-206 268 -463 330 -570 131 -227 117 -203 200 -348 36 -62 73 -125 82 -140 10
-15 21 -34 25 -42 4 -8 20 -37 36 -65 17 -27 38 -65 48 -82 49 -85 64 -111 87
-153 13 -25 28 -49 32 -55 4 -5 78 -134 165 -285 87 -151 166 -288 176 -305
10 -16 26 -43 35 -59 9 -17 125 -217 257 -445 132 -229 253 -441 270 -471 17
-30 45 -79 64 -108 18 -29 33 -54 33 -57 0 -2 20 -37 44 -77 24 -40 123 -212
221 -383 97 -170 190 -330 205 -355 16 -25 39 -65 53 -90 13 -25 81 -144 152
-265 70 -121 137 -238 150 -260 12 -22 37 -65 55 -95 18 -30 43 -73 55 -95 12
-22 48 -85 80 -140 77 -132 163 -280 190 -330 13 -22 71 -123 130 -225 59
-102 116 -199 126 -217 10 -17 29 -50 43 -72 15 -22 26 -43 26 -45 0 -2 27
-50 60 -106 33 -56 60 -103 60 -105 0 -2 55 -98 90 -155 8 -14 182 -316 239
-414 13 -22 45 -79 72 -124 27 -46 49 -86 49 -89 0 -2 14 -24 30 -48 16 -24
30 -46 30 -49 0 -5 74 -135 100 -176 5 -8 24 -42 43 -75 50 -88 58 -101 262
-455 104 -179 199 -345 213 -370 14 -25 28 -49 32 -55 4 -5 17 -26 28 -45 10
-19 62 -109 114 -200 114 -197 133 -230 170 -295 16 -27 33 -57 38 -65 17 -28
96 -165 103 -180 4 -8 16 -28 26 -45 10 -16 77 -131 148 -255 72 -124 181
-313 243 -420 62 -107 121 -209 131 -227 35 -62 323 -560 392 -678 38 -66 83
-145 100 -175 16 -30 33 -59 37 -65 4 -5 17 -27 29 -47 34 -61 56 -100 90
-156 17 -29 31 -55 31 -57 0 -2 17 -32 39 -67 21 -35 134 -229 251 -433 117
-203 235 -407 261 -451 27 -45 49 -85 49 -88 0 -4 8 -19 19 -34 15 -21 200
-341 309 -533 10 -19 33 -58 51 -87 17 -29 31 -54 31 -56 0 -2 25 -44 55 -94
30 -50 55 -95 55 -98 0 -4 6 -15 14 -23 7 -9 27 -41 43 -71 17 -30 170 -297
342 -594 171 -296 311 -542 311 -547 0 -5 5 -9 10 -9 6 0 10 -4 10 -10 0 -5
22 -47 49 -92 27 -46 58 -99 68 -118 24 -43 81 -140 93 -160 5 -8 66 -114 135
-235 69 -121 130 -227 135 -235 12 -21 259 -447 283 -490 10 -19 28 -47 38
-62 11 -14 19 -29 19 -32 0 -3 37 -69 83 -148 99 -170 305 -526 337 -583 13
-22 31 -53 41 -70 11 -16 22 -37 26 -45 7 -14 82 -146 103 -180 14 -24 181
-311 205 -355 13 -22 46 -80 75 -130 29 -49 64 -110 78 -135 14 -25 51 -88 82
-140 31 -52 59 -102 63 -110 4 -8 18 -33 31 -55 205 -353 284 -489 309 -535
17 -30 45 -78 62 -106 18 -28 36 -60 39 -72 4 -12 12 -22 17 -22 5 0 9 -4 9
-10 0 -5 109 -197 241 -427 133 -230 250 -431 259 -448 51 -90 222 -385 280
-485 37 -63 78 -135 92 -160 14 -25 67 -117 118 -205 51 -88 101 -175 111
-193 34 -58 55 -95 149 -257 51 -88 101 -173 110 -190 9 -16 76 -131 147 -255
72 -124 140 -241 151 -260 61 -108 281 -489 355 -615 38 -66 77 -133 87 -150
35 -63 91 -161 100 -175 14 -23 99 -169 128 -220 54 -97 135 -235 142 -245 4
-5 20 -32 35 -60 26 -48 238 -416 276 -480 10 -16 26 -46 37 -65 30 -53 382
-661 403 -695 10 -16 22 -37 26 -45 4 -8 26 -48 50 -88 24 -41 43 -75 43 -77
0 -2 22 -40 50 -85 27 -45 50 -84 50 -86 0 -3 38 -69 83 -147 84 -142 302
-520 340 -587 10 -19 34 -60 52 -90 18 -30 44 -75 57 -100 14 -25 45 -79 70
-120 25 -41 56 -96 70 -121 14 -25 77 -133 138 -240 62 -107 122 -210 132
-229 25 -43 310 -535 337 -581 11 -19 26 -45 34 -59 17 -32 238 -414 266 -460
11 -19 24 -41 28 -49 3 -7 75 -133 160 -278 84 -146 153 -269 153 -274 0 -5 5
-9 10 -9 6 0 10 -4 10 -10 0 -5 82 -150 181 -322 182 -314 201 -346 240 -415
12 -21 80 -139 152 -263 71 -124 141 -245 155 -270 14 -25 28 -49 32 -55 6 -8
145 -248 220 -380 37 -66 209 -362 229 -395 11 -19 24 -42 28 -49 4 -8 67
-118 140 -243 73 -125 133 -230 133 -233 0 -2 15 -28 33 -57 19 -29 47 -78 64
-108 17 -30 53 -93 79 -139 53 -90 82 -141 157 -272 82 -142 115 -199 381
-659 142 -245 268 -463 281 -485 12 -22 71 -125 132 -230 60 -104 172 -298
248 -430 76 -132 146 -253 156 -270 11 -16 22 -36 26 -44 3 -8 30 -54 60 -103
29 -49 53 -91 53 -93 0 -3 18 -34 40 -70 22 -36 40 -67 40 -69 0 -2 37 -66 81
-142 45 -77 98 -168 119 -204 20 -36 47 -81 58 -100 12 -19 27 -47 33 -62 6
-16 15 -28 20 -28 5 0 9 -4 9 -9 0 -6 63 -118 140 -251 77 -133 140 -243 140
-245 0 -2 18 -33 41 -70 22 -37 49 -83 60 -101 10 -19 29 -51 40 -71 25 -45
109 -189 126 -218 7 -11 17 -29 22 -40 6 -11 22 -38 35 -60 14 -22 37 -62 52
-90 14 -27 35 -62 45 -77 11 -14 19 -29 19 -32 0 -3 18 -35 40 -71 22 -36 40
-67 40 -69 0 -2 19 -35 42 -72 23 -38 55 -94 72 -124 26 -47 139 -244 171
-298 6 -9 21 -36 34 -60 28 -48 37 -51 51 -19 6 12 19 36 29 52 10 17 27 46
38 65 11 19 104 181 208 360 103 179 199 345 213 370 14 25 42 74 64 109 21
34 38 65 38 67 0 2 18 33 40 69 22 36 40 67 40 69 0 3 177 310 199 346 16 26
136 234 140 244 2 5 25 44 52 88 27 44 49 81 49 84 0 2 18 34 40 70 22 36 40
67 40 69 0 2 20 36 43 77 35 58 169 289 297 513 9 17 50 86 90 155 40 69 86
150 103 180 16 30 35 62 41 70 6 8 16 24 22 35 35 64 72 129 167 293 59 100
116 199 127 220 11 20 30 53 41 72 43 72 1070 1850 1121 1940 14 25 65 113
113 195 48 83 96 166 107 185 10 19 28 50 38 68 11 18 73 124 137 235 64 111
175 303 246 427 71 124 173 299 225 390 52 91 116 202 143 248 27 45 49 85 49
89 0 4 6 14 14 22 7 9 28 43 46 76 26 47 251 436 378 655 11 19 29 51 40 70
11 19 101 176 201 348 99 172 181 317 181 323 0 5 5 9 10 9 6 0 10 5 10 11 0
6 8 23 18 37 11 15 32 52 49 82 16 30 130 228 253 440 122 212 234 405 248
430 13 25 39 70 57 100 39 65 69 117 130 225 25 44 50 87 55 95 12 19 78 134
220 380 61 107 129 224 150 260 161 277 222 382 246 425 15 28 47 83 71 123
24 41 43 78 43 83 0 5 4 9 8 9 4 0 13 12 19 28 7 15 23 45 36 67 66 110 277
478 277 483 0 3 6 13 14 21 7 9 27 41 43 71 17 30 45 80 63 110 34 57 375 649
394 685 6 11 16 27 22 35 6 8 26 42 44 75 18 33 41 74 51 90 10 17 24 41 32
55 54 97 72 128 88 152 11 14 19 28 19 30 0 3 79 141 175 308 96 167 175 305
175 308 0 3 6 13 14 21 7 9 26 39 41 66 33 60 276 483 338 587 24 40 46 80 50
88 4 8 13 24 20 35 14 23 95 163 125 215 11 19 52 91 92 160 40 69 80 139 90
155 9 17 103 179 207 360 105 182 200 346 211 365 103 181 463 802 489 845 7
11 15 27 19 35 4 8 29 51 55 95 64 110 828 1433 848 1470 9 17 24 41 33 55 9
14 29 48 45 77 15 28 52 93 82 145 30 51 62 107 71 123 17 30 231 398 400 690
51 88 103 179 115 202 12 23 26 48 32 55 6 7 24 38 40 68 17 30 61 107 98 170
37 63 84 144 103 180 19 36 41 72 48 81 8 8 14 18 14 21 0 4 27 51 59 106 32
55 72 124 89 154 16 29 71 125 122 213 51 88 104 180 118 205 13 25 28 50 32
55 4 6 17 26 28 45 11 19 45 80 77 135 31 55 66 116 77 135 11 19 88 152 171
295 401 694 620 1072 650 1125 11 19 87 152 170 295 83 143 158 273 166 288 9
16 21 36 26 45 6 9 31 52 55 96 25 43 54 94 66 115 11 20 95 164 186 321 91
157 173 299 182 315 9 17 26 46 37 65 12 19 66 114 121 210 56 96 108 186 117
200 8 14 24 40 34 59 24 45 383 664 412 713 5 9 17 29 26 45 15 28 120 210
241 419 36 61 68 117 72 125 4 8 12 23 19 34 35 57 245 420 262 453 11 20 35
61 53 90 17 29 32 54 32 56 0 3 28 51 62 108 33 57 70 119 80 138 10 19 23 42
28 50 5 8 32 53 59 100 27 47 149 258 271 470 122 212 234 405 248 430 30 53
62 108 80 135 6 11 15 27 19 35 4 8 85 150 181 315 96 165 187 323 202 350 31
56 116 202 130 225 5 8 25 42 43 75 19 33 92 159 162 280 149 257 157 271 202
350 19 33 38 67 43 75 9 14 228 392 275 475 12 22 55 96 95 165 40 69 80 139
90 155 24 42 202 350 221 383 9 15 27 47 41 72 14 25 75 131 136 236 61 106
121 210 134 232 99 172 271 470 279 482 5 8 23 40 40 70 18 30 81 141 142 245
60 105 121 210 135 235 14 25 71 124 127 220 56 96 143 247 194 335 51 88 96
167 102 175 14 24 180 311 204 355 23 43 340 590 356 615 5 8 50 87 101 175
171 301 517 898 582 1008 25 43 46 81 46 83 0 2 12 23 27 47 14 23 40 67 56
97 16 30 35 62 42 70 7 8 15 22 18 30 4 8 20 38 37 65 16 28 33 57 37 65 6 12
111 196 143 250 5 8 55 95 112 193 57 98 113 195 126 215 12 20 27 46 32 57 6
11 14 27 20 35 5 8 76 130 156 270 80 140 165 287 187 325 23 39 52 90 66 115
13 25 30 52 37 61 8 8 14 18 14 21 0 4 41 77 92 165 50 87 175 302 276 478
101 176 208 360 236 408 28 49 67 117 86 152 19 35 41 70 48 77 6 6 12 15 12
19 0 7 124 224 167 291 12 21 23 40 23 42 0 2 21 40 46 83 26 43 55 92 64 109
54 95 327 568 354 614 19 30 45 75 59 100 71 128 82 145 89 148 4 2 8 8 8 13
0 5 42 82 94 172 311 538 496 858 518 897 14 25 40 70 58 100 18 30 42 71 53
90 10 19 79 139 152 265 73 127 142 246 153 265 10 19 43 76 72 125 29 50 63
108 75 130 65 116 80 140 87 143 4 2 8 8 8 12 0 8 114 212 140 250 6 8 14 24
20 35 5 11 54 97 108 190 l100 170 -9611 3 c-5286 1 -9614 -1 -9618 -5 -5 -6
-419 -719 -619 -1068 -89 -155 -267 -463 -323 -560 -38 -66 -81 -140 -95 -165
-31 -56 -263 -457 -526 -910 -110 -190 -224 -388 -254 -440 -29 -52 -61 -109
-71 -125 -23 -39 -243 -420 -268 -465 -11 -19 -204 -352 -428 -740 -224 -388
-477 -826 -563 -975 -85 -148 -185 -322 -222 -385 -37 -63 -120 -207 -185
-320 -65 -113 -177 -306 -248 -430 -72 -124 -172 -297 -222 -385 -51 -88 -142
-245 -202 -350 -131 -226 -247 -427 -408 -705 -65 -113 -249 -432 -410 -710
-160 -278 -388 -673 -506 -877 -118 -205 -216 -373 -219 -373 -3 0 -52 82
-109 183 -58 100 -144 250 -192 332 -95 164 -402 696 -647 1120 -85 149 -228
396 -317 550 -212 365 -982 1700 -1008 1745 -10 19 -43 76 -72 125 -29 50 -64
110 -77 135 -14 25 -63 110 -110 190 -47 80 -96 165 -110 190 -14 25 -99 171
-188 325 -89 154 -174 300 -188 325 -13 25 -64 113 -112 195 -48 83 -140 242
-205 355 -65 113 -183 317 -263 454 -79 137 -152 264 -163 282 -50 89 -335
583 -354 614 -12 19 -34 58 -50 85 -15 28 -129 226 -253 440 -124 215 -235
408 -247 430 -12 22 -69 121 -127 220 -58 99 -226 389 -373 645 -148 256 -324
561 -392 678 -67 117 -134 232 -147 255 -13 23 -33 59 -46 80 l-22 37 -9615 0
-9615 0 20 -32z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

17
client/public/index.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

2
client/public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow:

37
client/src/App.vue Normal file
View File

@@ -0,0 +1,37 @@
<template>
<div id="app">
<Video v-if="connection" v-bind:connection="connection"/>
<Configure v-else v-on:connect="offer"/>
</div>
</template>
<script>
import Video from './components/video'
import Configure from './components/configure'
export default {
name: 'App',
components: {
Configure,
Video
},
data () {
return {
connectedUsers: null,
connectedToName: null,
connection: null
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 10px;
}
</style>

BIN
client/src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,41 @@
<template>
<container>
<img alt="Vue logo" src="../assets/logo.png">
<h1 class="title">Lil' Streamy</h1>
<RoomSelector/>
<UsernameChooser/>
</container>
</template>
<script>
import RoomSelector from './roomSelector'
import UsernameChooser from './usernameChooser'
export default {
name: 'Video',
components: {
RoomSelector,
UsernameChooser
},
data () {
return {
isLoading: true
}
},
methods: {
async saveName (user) {
},
async saveConnection (connection) {
},
async connect () {
this.$emit('connect', this.name)
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,36 @@
<template>
<div class="container">
<section>
<h3>Choose a room</h3>
<b-field position="is-centered">
<b-select placeholder="Select a room">
<option>
</option>
</b-select>
<button class="button is-success" @click="connect">Connect</button>
</b-field>
</section>
<b-loading :active.sync="isLoading" :is-full-page=false />
</div>
</template>
<script>
export default {
name: 'ClientSelector',
data () {
return {
isLoading: false,
name: ''
}
},
methods: {
async connect () {
this.$emit('connect', this.name)
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,29 @@
<template>
<div>
<h3>Enter a username</h3>
<b-field position="is-centered">
<b-input :value="name" v-model="name" placeholder="..."></b-input>
<button class="button is-success" @click="connect">Login</button>
</b-field>
</div>
</template>
<script>
export default {
name: 'UsernameChooser',
data () {
return {
name: ''
}
},
methods: {
async sendName () {
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,24 @@
<template>
<container>
</container>
</template>
<script>
export default {
name: 'Video',
data () {
return {
isLoading: true
}
},
methods: {
async connect () {
this.$emit('connect', this.name)
}
}
}
</script>
<style>
</style>

17
client/src/main.js Normal file
View File

@@ -0,0 +1,17 @@
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import Buefy from 'buefy'
import Rtc from './scripts/rtc'
import 'buefy/dist/buefy.css'
Vue.config.productionTip = false
Vue.use(Buefy)
Vue.$rtc = Rtc
new Vue({
render: h => h(App)
}).$mount('#app')

View File

@@ -0,0 +1,32 @@
/* eslint-disable no-console */
import { register } from 'register-service-worker'
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready () {
console.log(
'App is being served from cache by a service worker.\n' +
'For more details, visit https://goo.gl/AFskqB'
)
},
registered () {
console.log('Service worker has been registered.')
},
cached () {
console.log('Content has been cached for offline use.')
},
updatefound () {
console.log('New content is downloading.')
},
updated () {
console.log('New content is available; please refresh.')
},
offline () {
console.log('No internet connection found. App is running in offline mode.')
},
error (error) {
console.error('Error during service worker registration:', error)
}
})
}

250
client/src/scripts/rtc.js Normal file
View File

@@ -0,0 +1,250 @@
var name
var connections = {}
const configuration = {
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
}
const offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
}
function handleLogin (success) {
if (success === false) {
alert('try a different username')
} else {
}
}
async function createPeerConnection (target) {
console.log('CREATED PEER CONNECTION')
var connection = new RTCPeerConnection(configuration)
connections[target] = connection
connection.onicecandidate = function(event) {
if (event.candidate) {
send({
type: 'candidate',
name: name,
target: target,
candidate: event.candidate
})
}
}
connection.onnegotiationneeded = function() { handleNegotiationNeededEvent(target); }
connection.ontrack = function(event) { handleTrackEvent(event); }
connection.onsignalingstatechange = function() { handleSignalingStateChangeEvent(connection); }
connection.oniceconnectionstatechange = function() { handleICEConnectionStateChangeEvent(connection); }
connection.onicegatheringstatechange = function() { handleICEGatheringStateChangeEvent(connection); }
}
function handleICEConnectionStateChangeEvent (connection) {
console.log('ICE CONNECTION CHANGE '+connection.iceConnectionState)
}
function handleICEGatheringStateChangeEvent (connection) {
console.log('ICE GATHERING CHANGE '+connection.iceGatheringState)
}
async function makeOffer (target) {
createPeerConnection(target)
var connection = connections[target]
var offer = await connection.createOffer()
send({
type: 'offer',
name: name,
target: target,
offer: offer
});
await connection.setLocalDescription(offer)
}
async function handleOffer (offer, target) {
console.log('GOT OFFER FROM '+target)
await createPeerConnection(target)
var connection = connections[target]
await connection.setRemoteDescription(new RTCSessionDescription(offer))
if (stream) {
console.log('STREAM DETECTED')
stream.getTracks().forEach((track) => {
console.log('ADDED TRACK')
connection.addTrack(track, stream)
});
}
//create an answer to an offer
var answer = await connection.createAnswer()
await connection.setLocalDescription(answer)
send({
type: 'answer',
name: name,
target: target,
answer: answer
});
};
async function handleAnswer (answer, target) {
console.log('GOT ANSWER FROM '+target)
var connection = connections[target]
await connection.setRemoteDescription(new RTCSessionDescription(answer))
};
async function handleCandidate (candidate, target) {
console.log('GOT CANDIDATE FROM '+target)
var connection = connections[target]
await connection.addIceCandidate(new RTCIceCandidate(candidate))
};
async function handleNegotiationNeededEvent (target) {
console.log('NEGOTIATION NEEDED FROM '+target)
var connection = connections[target]
var offer = await connection.createOffer(offerOptions)
await connection.setLocalDescription(offer)
send({
type: 'video-offer',
name: name,
target: target,
sdp: connection.localDescription
});
}
function handleLeave () {
connections.foreach( (connection) => {
connection.close()
connection.onicecandidate = null
//connection.onaddstream = null
connection = null
});
connections = {};
remoteVideo.src = null
};
function handleUserlist (list) {
console.log('GOT USER LIST')
}
async function handleVideoOffer (sdp, target) {
console.log('GOT VIDEO OFFER FROM '+target)
await createPeerConnection(target)
var connection = connections[target]
await connection.setRemoteDescription(new RTCSessionDescription(sdp))
if (stream) {
console.log('STREAM DETECTED')
stream.getTracks().forEach((track) => {
console.log('ADDED TRACK')
connection.addTrack(track, stream)
});
}
var answer = await connection.createAnswer()
await connection.setLocalDescription(answer)
send({
type: 'video-answer',
name: name,
target: target,
sdp: answer
});
}
async function handleVideoAnswer (sdp, target) {
console.log('GOT VIDEO ANSWER FROM '+target)
var connection = connections[target]
await connection.setRemoteDescription(new RTCSessionDescription(sdp))
}
async function handleSignalingStateChangeEvent (connection) {
console.log('STATE CHANGED TO : ' + connection.signalingState)
switch(connection.signalingState) {
case 'closed':
await connection.close()
break
}
}
function handleTrackEvent (event) {
console.log('GOT TRACK')
remoteVideo.srcObject = event.streams[0]
}
//connecting to our signaling server
var conn = new WebSocket('ws://localhost:9090')
conn.onopen = function () {
console.log('Connected to the signaling server')
};
//when we got a message from a signaling server
conn.onmessage = function (msg) {
console.log('Got message', msg.data)
var data = JSON.parse(msg.data)
switch(data.type) {
case 'login':
handleLogin(data.success)
break
case 'offer':
handleOffer(data.offer, data.name)
break
case 'answer':
handleAnswer(data.answer, data.name)
break
case 'candidate':
handleCandidate(data.candidate, data.name)
break
case 'userlist':
handleUserlist(data)
break
case 'leave':
handleLeave()
break
case 'video-offer':
handleVideoOffer(data.sdp, data.name)
break
case 'video-answer':
handleVideoAnswer(data.sdp, data.name)
break
default:
break
}
}
conn.onerror = function (err) {
console.log('Got error', err)
}
function send (message) {
console.log('Sended message', message)
conn.send(JSON.stringify(message))
}
export {
send,
makeOffer
}

View File

1
clientV/bulma.min.css vendored Normal file

File diff suppressed because one or more lines are too long

34
clientV/index.html Normal file
View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="bulma.min.css" />
<title>Lil'Streamy</title>
</head>
<body>
<button id="loginBt" >Login</button>
<input id="loginInput" placeholder="..." type="text"/>
<hr>
<video autoplay controls id="video"></video>
<hr>
<button id="callBt" >Call</button>
<input id="callInput" placeholder="..." type="text"/>
<button id="disconnectBt" >Disconnect</button>
<hr>
<input id="videoInput" type="file" accept="video/*"/>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src = "scripts/script.js"></script>
<script src = "scripts/rtc3.js"></script>
<script src = "scripts/signal.js"></script>
</body>
</html>

82
clientV/index_old.html Normal file
View File

@@ -0,0 +1,82 @@
<html>
<head>
<title>WebRTC Video Demo</title>
<link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/>
</head>
<style>
body {
background: #eee;
padding: 5% 0;
}
video {
background: black;
border: 1px solid gray;
}
.call-page {
position: relative;
display: block;
margin: 0 auto;
width: 500px;
height: 500px;
}
#localVideo {
width: 150px;
height: 150px;
position: absolute;
top: 15px;
right: 15px;
}
#remoteVideo {
width: 500px;
height: 500px;
}
</style>
<body>
<div id = "loginPage" class = "container text-center">
<div class = "row">
<div class = "col-md-4 col-md-offset-4">
<h2>WebRTC Video Demo. Please sign in</h2>
<label for = "usernameInput" class = "sr-only">Login</label>
<input type = "email" id = "usernameInput" c
lass = "form-control formgroup" placeholder = "Login"
required = "" autofocus = "">
<button id = "loginBtn" class = "btn btn-lg btn-primary btnblock">
Sign in</button>
</div>
</div>
</div>
<div id = "callPage" class = "call-page">
<video id = "localVideo" autoplay></video>
<video id = "remoteVideo" autoplay></video>
<div class = "row text-center">
<div class = "col-md-12">
<input id = "callToUsernameInput" type = "text"
placeholder = "username to call" />
<button id = "callBtn" class = "btn-success btn">Call</button>
<button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button>
</div>
</div>
</div>
<script src = "scripts/rtc_old.js"></script>
</body>
</html>

220
clientV/scripts/rtc.js Normal file
View File

@@ -0,0 +1,220 @@
var name;
var connectedUser;
var loginInput = document.querySelector('#loginInput');
var loginBt = document.querySelector('#loginBt');
var callToUsernameInput = document.querySelector('#callInput');
var callBtn = document.querySelector('#callBt');
var hangUpBtn = document.querySelector('#disconnectBt');
const remoteVideo = document.querySelector('#video');
const remoteVideo2 = document.querySelector('#video2');
var videoInput = document.querySelector('#videoInput');
var yourConn;
var stream;
var reader;
const configuration = {
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
};
const offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
videoInput.addEventListener("change", function (event) {
var file = this.files[0]
var type = file.type
var videoNode = remoteVideo2
var canPlay = videoNode.canPlayType(type)
if (canPlay === '') canPlay = 'no'
var message = 'Can play type "' + type + '": ' + canPlay
var isError = canPlay === 'no'
//displayMessage(message, isError)
if (isError) {
return
}
var fileURL = URL.createObjectURL(file)
videoNode.src = fileURL
});
remoteVideo.onplay = function() {
if(remoteVideo.mozCaptureStream()) stream = remoteVideo.mozCaptureStream();
else stream = remoteVideo.captureStream();
//remoteVideo2.srcObject = stream;
stream.getTracks().forEach((track) => {
console.log("ADDED TRACK");
yourConn.addTrack(track, stream);
});
//yourConn.addStream(stream);
}
// Login when the user clicks the button
loginBt.addEventListener("click", function (event) {
name = loginInput.value;
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
//**********************
//Starting a peer connection
//**********************
yourConn = new RTCPeerConnection(configuration);
yourConn.ontrack = function (event) {
console.log("GOT TRACK");
remoteVideo.srcObject = event.streams[0];
}
if (stream) {
remoteVideo2.srcObject = stream;
stream.getTracks().forEach((track) => {
yourConn.addTrack(track, stream);
});
}
// setup stream listening
//yourConn.addStream(stream);
// Setup ice handling
yourConn.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
yourConn.onnegotiationneeded = handleNegotiationNeededEvent;
window.setInterval(getConnectionStats, 1000);
}
};
function getConnectionStats() {
/*yourConn.getStats().then(stats => {
var statsOutput = "";
stats.forEach(report => {
statsOutput += report;
});
console.log(statsOutput);
});*/
console.log(yourConn.connectionState);
}
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
},
offerOptions);
}
});
//when somebody sends us an offer
function handleOffer(offer, name) {
console.log("GOT OFFER");
connectedUser = name;
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
//create an answer to an offer
yourConn.createAnswer(function (answer) {
yourConn.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("Error when creating an answer");
});
};
//when we got an answer from a remote user
function handleAnswer(answer) {
console.log("GOT ANSWER");
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
};
//when we got an ice candidate from a remote user
function handleCandidate(candidate) {
console.log("GOT CANDIDATE");
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
};
function handleNegotiationNeededEvent() {
console.log("NEGOTIATION NEEDED");
yourConn.createOffer().then(function(offer) {
return yourConn.setLocalDescription(offer);
})
.then(function() {
send({
type: "video-offer",
sdp: yourConn.localDescription
});
});
}
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
remoteVideo.src = null;
yourConn.close();
yourConn.onicecandidate = null;
//yourConn.onaddstream = null;
};

658
clientV/scripts/rtc2.js Normal file
View File

@@ -0,0 +1,658 @@
"use strict";
// Get our hostname
var myHostname = window.location.hostname;
if (!myHostname) {
myHostname = "localhost";
}
log("Hostname: " + myHostname);
// WebSocket chat/signaling channel variables.
var connection = null;
var clientID = 0;
// The media constraints object describes what sort of stream we want
// to request from the local A/V hardware (typically a webcam and
// microphone). Here, we specify only that we want both audio and
// video; however, you can be more specific. It's possible to state
// that you would prefer (or require) specific resolutions of video,
// whether to prefer the user-facing or rear-facing camera (if available),
// and so on.
//
// See also:
// https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
//
var mediaConstraints = {
audio: true, // We want an audio track
video: {
aspectRatio: {
ideal: 1.333333 // 3:2 aspect is preferred
}
}
};
var myUsername = null;
var targetUsername = null; // To store username of other peer
var myPeerConnection = null; // RTCPeerConnection
var transceiver = null; // RTCRtpTransceiver
var webcamStream = null; // MediaStream from webcam
// Output logging information to console.
function log(text) {
var time = new Date();
console.log("[" + time.toLocaleTimeString() + "] " + text);
}
// Output an error message to console.
function log_error(text) {
var time = new Date();
console.trace("[" + time.toLocaleTimeString() + "] " + text);
}
// Send a JavaScript object by converting it to JSON and sending
// it as a message on the WebSocket connection.
function sendToServer(msg) {
var msgJSON = JSON.stringify(msg);
log("Sending '" + msg.type + "' message: " + msgJSON);
connection.send(msgJSON);
}
// Called when the "id" message is received; this message is sent by the
// server to assign this login session a unique ID number; in response,
// this function sends a "username" message to set our username for this
// session.
function setUsername() {
myUsername = document.getElementById("name").value;
sendToServer({
name: myUsername,
date: Date.now(),
id: clientID,
type: "username"
});
}
// Open and configure the connection to the WebSocket server.
function connect() {
var serverUrl;
var scheme = "ws";
// If this is an HTTPS connection, we have to use a secure WebSocket
// connection too, so add another "s" to the scheme.
if (document.location.protocol === "https:") {
scheme += "s";
}
serverUrl = scheme + "://" + myHostname + ":6503";
log(`Connecting to server: ${serverUrl}`);
connection = new WebSocket(serverUrl, "json");
connection.onopen = function(evt) {
document.getElementById("text").disabled = false;
document.getElementById("send").disabled = false;
};
connection.onerror = function(evt) {
console.dir(evt);
}
connection.onmessage = function(evt) {
var chatBox = document.querySelector(".chatbox");
var text = "";
var msg = JSON.parse(evt.data);
log("Message received: ");
console.dir(msg);
var time = new Date(msg.date);
var timeStr = time.toLocaleTimeString();
switch(msg.type) {
case "id":
clientID = msg.id;
setUsername();
break;
case "username":
text = "<b>User <em>" + msg.name + "</em> signed in at " + timeStr + "</b><br>";
break;
case "message":
text = "(" + timeStr + ") <b>" + msg.name + "</b>: " + msg.text + "<br>";
break;
case "rejectusername":
myUsername = msg.name;
text = "<b>Your username has been set to <em>" + myUsername +
"</em> because the name you chose is in use.</b><br>";
break;
case "userlist": // Received an updated user list
handleUserlistMsg(msg);
break;
// Signaling messages: these messages are used to trade WebRTC
// signaling information during negotiations leading up to a video
// call.
case "video-offer": // Invitation and offer to chat
handleVideoOfferMsg(msg);
break;
case "video-answer": // Callee has answered our offer
handleVideoAnswerMsg(msg);
break;
case "new-ice-candidate": // A new ICE candidate has been received
handleNewICECandidateMsg(msg);
break;
case "hang-up": // The other peer has hung up the call
handleHangUpMsg(msg);
break;
// Unknown message; output to console for debugging.
default:
log_error("Unknown message received:");
log_error(msg);
}
// If there's text to insert into the chat buffer, do so now, then
// scroll the chat panel so that the new text is visible.
if (text.length) {
chatBox.innerHTML += text;
chatBox.scrollTop = chatBox.scrollHeight - chatBox.clientHeight;
}
};
}
// Handles a click on the Send button (or pressing return/enter) by
// building a "message" object and sending it to the server.
function handleSendButton() {
var msg = {
text: document.getElementById("text").value,
type: "message",
id: clientID,
date: Date.now()
};
sendToServer(msg);
document.getElementById("text").value = "";
}
// Handler for keyboard events. This is used to intercept the return and
// enter keys so that we can call send() to transmit the entered text
// to the server.
function handleKey(evt) {
if (evt.keyCode === 13 || evt.keyCode === 14) {
if (!document.getElementById("send").disabled) {
handleSendButton();
}
}
}
// Create the RTCPeerConnection which knows how to talk to our
// selected STUN/TURN server and then uses getUserMedia() to find
// our camera and microphone and add that stream to the connection for
// use in our video call. Then we configure event handlers to get
// needed notifications on the call.
async function createPeerConnection() {
log("Setting up a connection...");
// Create an RTCPeerConnection which knows to use our chosen
// STUN server.
myPeerConnection = new RTCPeerConnection({
iceServers: [ // Information about ICE servers - Use your own!
{
urls: "turn:" + myHostname, // A TURN server
username: "webrtc",
credential: "turnserver"
}
]
});
// Set up event handlers for the ICE negotiation process.
myPeerConnection.onicecandidate = handleICECandidateEvent;
myPeerConnection.oniceconnectionstatechange = handleICEConnectionStateChangeEvent;
myPeerConnection.onicegatheringstatechange = handleICEGatheringStateChangeEvent;
myPeerConnection.onsignalingstatechange = handleSignalingStateChangeEvent;
myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent;
myPeerConnection.ontrack = handleTrackEvent;
}
// Called by the WebRTC layer to let us know when it's time to
// begin, resume, or restart ICE negotiation.
async function handleNegotiationNeededEvent() {
log("*** Negotiation needed");
try {
log("---> Creating offer");
const offer = await myPeerConnection.createOffer();
// If the connection hasn't yet achieved the "stable" state,
// return to the caller. Another negotiationneeded event
// will be fired when the state stabilizes.
if (myPeerConnection.signalingState != "stable") {
log(" -- The connection isn't stable yet; postponing...")
return;
}
// Establish the offer as the local peer's current
// description.
log("---> Setting local description to the offer");
await myPeerConnection.setLocalDescription(offer);
// Send the offer to the remote peer.
log("---> Sending the offer to the remote peer");
sendToServer({
name: myUsername,
target: targetUsername,
type: "video-offer",
sdp: myPeerConnection.localDescription
});
} catch(err) {
log("*** The following error occurred while handling the negotiationneeded event:");
reportError(err);
};
}
// Called by the WebRTC layer when events occur on the media tracks
// on our WebRTC call. This includes when streams are added to and
// removed from the call.
//
// track events include the following fields:
//
// RTCRtpReceiver receiver
// MediaStreamTrack track
// MediaStream[] streams
// RTCRtpTransceiver transceiver
//
// In our case, we're just taking the first stream found and attaching
// it to the <video> element for incoming media.
function handleTrackEvent(event) {
log("*** Track event");
document.getElementById("received_video").srcObject = event.streams[0];
document.getElementById("hangup-button").disabled = false;
}
// Handles |icecandidate| events by forwarding the specified
// ICE candidate (created by our local ICE agent) to the other
// peer through the signaling server.
function handleICECandidateEvent(event) {
if (event.candidate) {
log("*** Outgoing ICE candidate: " + event.candidate.candidate);
sendToServer({
type: "new-ice-candidate",
target: targetUsername,
candidate: event.candidate
});
}
}
// Handle |iceconnectionstatechange| events. This will detect
// when the ICE connection is closed, failed, or disconnected.
//
// This is called when the state of the ICE agent changes.
function handleICEConnectionStateChangeEvent(event) {
log("*** ICE connection state changed to " + myPeerConnection.iceConnectionState);
switch(myPeerConnection.iceConnectionState) {
case "closed":
case "failed":
case "disconnected":
closeVideoCall();
break;
}
}
// Set up a |signalingstatechange| event handler. This will detect when
// the signaling connection is closed.
//
// NOTE: This will actually move to the new RTCPeerConnectionState enum
// returned in the property RTCPeerConnection.connectionState when
// browsers catch up with the latest version of the specification!
function handleSignalingStateChangeEvent(event) {
log("*** WebRTC signaling state changed to: " + myPeerConnection.signalingState);
switch(myPeerConnection.signalingState) {
case "closed":
closeVideoCall();
break;
}
}
// Handle the |icegatheringstatechange| event. This lets us know what the
// ICE engine is currently working on: "new" means no networking has happened
// yet, "gathering" means the ICE engine is currently gathering candidates,
// and "complete" means gathering is complete. Note that the engine can
// alternate between "gathering" and "complete" repeatedly as needs and
// circumstances change.
//
// We don't need to do anything when this happens, but we log it to the
// console so you can see what's going on when playing with the sample.
function handleICEGatheringStateChangeEvent(event) {
log("*** ICE gathering state changed to: " + myPeerConnection.iceGatheringState);
}
// Given a message containing a list of usernames, this function
// populates the user list box with those names, making each item
// clickable to allow starting a video call.
function handleUserlistMsg(msg) {
var i;
var listElem = document.querySelector(".userlistbox");
// Remove all current list members. We could do this smarter,
// by adding and updating users instead of rebuilding from
// scratch but this will do for this sample.
while (listElem.firstChild) {
listElem.removeChild(listElem.firstChild);
}
// Add member names from the received list.
msg.users.forEach(function(username) {
var item = document.createElement("li");
item.appendChild(document.createTextNode(username));
item.addEventListener("click", invite, false);
listElem.appendChild(item);
});
}
// Close the RTCPeerConnection and reset variables so that the user can
// make or receive another call if they wish. This is called both
// when the user hangs up, the other user hangs up, or if a connection
// failure is detected.
function closeVideoCall() {
var localVideo = document.getElementById("local_video");
log("Closing the call");
// Close the RTCPeerConnection
if (myPeerConnection) {
log("--> Closing the peer connection");
// Disconnect all our event listeners; we don't want stray events
// to interfere with the hangup while it's ongoing.
myPeerConnection.ontrack = null;
myPeerConnection.onnicecandidate = null;
myPeerConnection.oniceconnectionstatechange = null;
myPeerConnection.onsignalingstatechange = null;
myPeerConnection.onicegatheringstatechange = null;
myPeerConnection.onnotificationneeded = null;
// Stop all transceivers on the connection
myPeerConnection.getTransceivers().forEach(transceiver => {
transceiver.stop();
});
// Stop the webcam preview as well by pausing the <video>
// element, then stopping each of the getUserMedia() tracks
// on it.
if (localVideo.srcObject) {
localVideo.pause();
localVideo.srcObject.getTracks().forEach(track => {
track.stop();
});
}
// Close the peer connection
myPeerConnection.close();
myPeerConnection = null;
webcamStream = null;
}
// Disable the hangup button
document.getElementById("hangup-button").disabled = true;
targetUsername = null;
}
// Handle the "hang-up" message, which is sent if the other peer
// has hung up the call or otherwise disconnected.
function handleHangUpMsg(msg) {
log("*** Received hang up notification from other peer");
closeVideoCall();
}
// Hang up the call by closing our end of the connection, then
// sending a "hang-up" message to the other peer (keep in mind that
// the signaling is done on a different connection). This notifies
// the other peer that the connection should be terminated and the UI
// returned to the "no call in progress" state.
function hangUpCall() {
closeVideoCall();
sendToServer({
name: myUsername,
target: targetUsername,
type: "hang-up"
});
}
// Handle a click on an item in the user list by inviting the clicked
// user to video chat. Note that we don't actually send a message to
// the callee here -- calling RTCPeerConnection.addTrack() issues
// a |notificationneeded| event, so we'll let our handler for that
// make the offer.
async function invite(evt) {
log("Starting to prepare an invitation");
if (myPeerConnection) {
alert("You can't start a call because you already have one open!");
} else {
var clickedUsername = evt.target.textContent;
// Don't allow users to call themselves, because weird.
if (clickedUsername === myUsername) {
alert("I'm afraid I can't let you talk to yourself. That would be weird.");
return;
}
// Record the username being called for future reference
targetUsername = clickedUsername;
log("Inviting user " + targetUsername);
// Call createPeerConnection() to create the RTCPeerConnection.
// When this returns, myPeerConnection is our RTCPeerConnection
// and webcamStream is a stream coming from the camera. They are
// not linked together in any way yet.
log("Setting up connection to invite user: " + targetUsername);
createPeerConnection();
// Get access to the webcam stream and attach it to the
// "preview" box (id "local_video").
try {
webcamStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
document.getElementById("local_video").srcObject = webcamStream;
} catch(err) {
handleGetUserMediaError(err);
return;
}
// Add the tracks from the stream to the RTCPeerConnection
try {
webcamStream.getTracks().forEach(
transceiver = track => myPeerConnection.addTransceiver(track, {streams: [webcamStream]})
);
} catch(err) {
handleGetUserMediaError(err);
}
}
}
// Accept an offer to video chat. We configure our local settings,
// create our RTCPeerConnection, get and attach our local camera
// stream, then create and send an answer to the caller.
async function handleVideoOfferMsg(msg) {
targetUsername = msg.name;
// If we're not already connected, create an RTCPeerConnection
// to be linked to the caller.
log("Received video chat offer from " + targetUsername);
if (!myPeerConnection) {
createPeerConnection();
}
// We need to set the remote description to the received SDP offer
// so that our local WebRTC layer knows how to talk to the caller.
var desc = new RTCSessionDescription(msg.sdp);
// If the connection isn't stable yet, wait for it...
if (myPeerConnection.signalingState != "stable") {
log(" - But the signaling state isn't stable, so triggering rollback");
// Set the local and remove descriptions for rollback; don't proceed
// until both return.
await Promise.all([
myPeerConnection.setLocalDescription({type: "rollback"}),
myPeerConnection.setRemoteDescription(desc)
]);
return;
} else {
log (" - Setting remote description");
await myPeerConnection.setRemoteDescription(desc);
}
// Get the webcam stream if we don't already have it
if (!webcamStream) {
try {
webcamStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
} catch(err) {
handleGetUserMediaError(err);
return;
}
document.getElementById("local_video").srcObject = webcamStream;
// Add the camera stream to the RTCPeerConnection
try {
webcamStream.getTracks().forEach(
transceiver = track => myPeerConnection.addTransceiver(track, {streams: [webcamStream]})
);
} catch(err) {
handleGetUserMediaError(err);
}
}
log("---> Creating and sending answer to caller");
await myPeerConnection.setLocalDescription(await myPeerConnection.createAnswer());
sendToServer({
name: myUsername,
target: targetUsername,
type: "video-answer",
sdp: myPeerConnection.localDescription
});
}
// Responds to the "video-answer" message sent to the caller
// once the callee has decided to accept our request to talk.
async function handleVideoAnswerMsg(msg) {
log("*** Call recipient has accepted our call");
// Configure the remote description, which is the SDP payload
// in our "video-answer" message.
var desc = new RTCSessionDescription(msg.sdp);
await myPeerConnection.setRemoteDescription(desc).catch(reportError);
}
// A new ICE candidate has been received from the other peer. Call
// RTCPeerConnection.addIceCandidate() to send it along to the
// local ICE framework.
async function handleNewICECandidateMsg(msg) {
var candidate = new RTCIceCandidate(msg.candidate);
log("*** Adding received ICE candidate: " + JSON.stringify(candidate));
try {
await myPeerConnection.addIceCandidate(candidate)
} catch(err) {
reportError(err);
}
}
// Handle errors which occur when trying to access the local media
// hardware; that is, exceptions thrown by getUserMedia(). The two most
// likely scenarios are that the user has no camera and/or microphone
// or that they declined to share their equipment when prompted. If
// they simply opted not to share their media, that's not really an
// error, so we won't present a message in that situation.
function handleGetUserMediaError(e) {
log_error(e);
switch(e.name) {
case "NotFoundError":
alert("Unable to open your call because no camera and/or microphone" +
"were found.");
break;
case "SecurityError":
case "PermissionDeniedError":
// Do nothing; this is the same as the user canceling the call.
break;
default:
alert("Error opening your camera and/or microphone: " + e.message);
break;
}
// Make sure we shut down our end of the RTCPeerConnection so we're
// ready to try again.
closeVideoCall();
}
// Handles reporting errors. Currently, we just dump stuff to console but
// in a real-world application, an appropriate (and user-friendly)
// error message should be displayed.
function reportError(errMessage) {
log_error(`Error ${errMessage.name}: ${errMessage.message}`);
}

195
clientV/scripts/rtc3.js Normal file
View File

@@ -0,0 +1,195 @@
var name;
var connections = {};
const configuration = {
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
};
const offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
function handleLogin(success) {
if (success === false) {
alert("try a different username");
} else {
}
};
async function createPeerConnection(target) {
console.log("CREATED PEER CONNECTION");
var connection = new RTCPeerConnection(configuration);
connections[target] = connection;
connection.onicecandidate = function(event) {
if (event.candidate) {
send({
type: "candidate",
name: name,
target: target,
candidate: event.candidate
});
}
};
connection.onnegotiationneeded = function() { handleNegotiationNeededEvent(target); };
connection.ontrack = function(event) { handleTrackEvent(event); }
connection.onsignalingstatechange = function() { handleSignalingStateChangeEvent(connection); }
connection.oniceconnectionstatechange = function() { handleICEConnectionStateChangeEvent(connection); }
connection.onicegatheringstatechange = function() { handleICEGatheringStateChangeEvent(connection); }
//window.setInterval(getConnectionStats, 1000);
}
function handleICEConnectionStateChangeEvent(connection) {
console.log("ICE CONNECTION CHANGE "+connection.iceConnectionState);
}
function handleICEGatheringStateChangeEvent(connection) {
console.log("ICE GATHERING CHANGE "+connection.iceGatheringState);
}
async function makeOffer(target) {
createPeerConnection(target);
var connection = connections[target];
var offer = await connection.createOffer();
send({
type: "offer",
name: name,
target: target,
offer: offer
});
await connection.setLocalDescription(offer);
}
async function handleOffer(offer, target) {
console.log("GOT OFFER FROM "+target);
await createPeerConnection(target);
var connection = connections[target];
await connection.setRemoteDescription(new RTCSessionDescription(offer));
if (stream) {
console.log("STREAM DETECTED");
stream.getTracks().forEach((track) => {
console.log("ADDED TRACK");
connection.addTrack(track, stream);
});
}
//create an answer to an offer
var answer = await connection.createAnswer();
await connection.setLocalDescription(answer);
send({
type: "answer",
name: name,
target: target,
answer: answer
});
};
async function handleAnswer(answer, target) {
console.log("GOT ANSWER FROM "+target);
var connection = connections[target];
await connection.setRemoteDescription(new RTCSessionDescription(answer));
};
async function handleCandidate(candidate, target) {
console.log("GOT CANDIDATE FROM "+target);
var connection = connections[target];
await connection.addIceCandidate(new RTCIceCandidate(candidate));
};
async function handleNegotiationNeededEvent(target) {
console.log("NEGOTIATION NEEDED FROM "+target);
var connection = connections[target];
var offer = await connection.createOffer(offerOptions);
await connection.setLocalDescription(offer);
send({
type: "video-offer",
name: name,
target: target,
sdp: connection.localDescription
});
}
function handleLeave() {
connections.foreach( (connection) => {
connection.close();
connection.onicecandidate = null;
//connection.onaddstream = null;
connection = null;
});
connections = {};
remoteVideo.src = null;
};
function handleUserlist(list) {
console.log("GOT USER LIST");
}
async function handleVideoOffer(sdp, target) {
console.log("GOT VIDEO OFFER FROM "+target);
await createPeerConnection(target);
var connection = connections[target];
await connection.setRemoteDescription(new RTCSessionDescription(sdp))
if (stream) {
console.log("STREAM DETECTED");
stream.getTracks().forEach((track) => {
console.log("ADDED TRACK");
connection.addTrack(track, stream);
});
}
var answer = await connection.createAnswer();
await connection.setLocalDescription(answer);
send({
type: "video-answer",
name: name,
target: target,
sdp: answer
});
}
async function handleVideoAnswer(sdp, target) {
console.log("GOT VIDEO ANSWER FROM "+target);
var connection = connections[target];
await connection.setRemoteDescription(new RTCSessionDescription(sdp));
}
async function handleSignalingStateChangeEvent(connection) {
console.log("STATE CHANGED TO : " + connection.signalingState);
switch(connection.signalingState) {
case "closed":
await connection.close();
break;
}
}
function handleTrackEvent(event) {
console.log("GOT TRACK");
remoteVideo.srcObject = event.streams[0];
//document.getElementById("hangup-button").disabled = false;
}
function getConnectionStats() {
for ([connection, target] in connections) {
console.log("[" + target + "] " + connection.connectionState);
}
}

203
clientV/scripts/rtc_old.js Normal file
View File

@@ -0,0 +1,203 @@
//our username
var name;
var connectedUser;
//connecting to our signaling server
var conn = new WebSocket('ws://localhost:9090');
conn.onopen = function () {
console.log("Connected to the signaling server");
};
//when we got a message from a signaling server
conn.onmessage = function (msg) {
console.log("Got message", msg.data);
var data = JSON.parse(msg.data);
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
conn.onerror = function (err) {
console.log("Got error", err);
};
//alias for sending JSON encoded messages
function send(message) {
//attach the other peer username to our messages
if (connectedUser) {
message.name = connectedUser;
}
conn.send(JSON.stringify(message));
};
//******
//UI selectors block
//******
var loginPage = document.querySelector('#loginPage');
var usernameInput = document.querySelector('#usernameInput');
var loginBtn = document.querySelector('#loginBtn');
var callPage = document.querySelector('#callPage');
var callToUsernameInput = document.querySelector('#callToUsernameInput');
var callBtn = document.querySelector('#callBtn');
var hangUpBtn = document.querySelector('#hangUpBtn');
var localVideo = document.querySelector('#localVideo');
var remoteVideo = document.querySelector('#remoteVideo');
var yourConn;
var stream;
callPage.style.display = "none";
// Login when the user clicks the button
loginBtn.addEventListener("click", function (event) {
name = usernameInput.value;
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
function handleLogin(success) {
if (success === false) {
alert("Ooops...try a different username");
} else {
loginPage.style.display = "none";
callPage.style.display = "block";
//**********************
//Starting a peer connection
//**********************
//stream = myStream;
//displaying local video stream on the page
//localVideo.src = window.URL.createObjectURL(stream);
//using Google public stun server
var configuration = {
"iceServers": [{ "url": "stun:stun2.1.google.com:19302" }]
};
yourConn = new RTCPeerConnection(configuration);
// setup stream listening
//yourConn.addStream(stream);
//when a remote user adds stream to the peer connection, we display it
yourConn.onaddstream = function (e) {
//remoteVideo.src = window.URL.createObjectURL(e.stream);
};
// Setup ice handling
yourConn.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
}
};
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
//when somebody sends us an offer
function handleOffer(offer, name) {
connectedUser = name;
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
//create an answer to an offer
yourConn.createAnswer(function (answer) {
yourConn.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("Error when creating an answer");
});
};
//when we got an answer from a remote user
function handleAnswer(answer) {
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
};
//when we got an ice candidate from a remote user
function handleCandidate(candidate) {
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
};
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
remoteVideo.src = null;
yourConn.close();
yourConn.onicecandidate = null;
yourConn.onaddstream = null;
};

64
clientV/scripts/script.js Normal file
View File

@@ -0,0 +1,64 @@
var loginInput = document.querySelector('#loginInput');
var loginBt = document.querySelector('#loginBt');
var callInput = document.querySelector('#callInput');
var callBt = document.querySelector('#callBt');
var disconnectBt = document.querySelector('#disconnectBt');
const remoteVideo = document.querySelector('#video');
var videoInput = document.querySelector('#videoInput');
var stream;
loginBt.addEventListener("click", function (event) {
name = loginInput.value;
if (name.length > 0) {
send({
type: "login",
name: name
});
}
});
callBt.addEventListener("click", function () {
var callToUsername = callInput.value;
if (callToUsername.length > 0) {
makeOffer(callToUsername);
}
});
disconnectBt.addEventListener("click", function () {
send({
type: "leave",
name: name
});
handleLeave();
});
videoInput.addEventListener("change", function (event) {
var file = this.files[0]
var type = file.type
var videoNode = remoteVideo2
var canPlay = videoNode.canPlayType(type)
if (canPlay === '') canPlay = 'no'
var message = 'Can play type "' + type + '": ' + canPlay
var isError = canPlay === 'no'
//displayMessage(message, isError)
if (isError) {
return
}
var fileURL = URL.createObjectURL(file)
videoNode.src = fileURL
});
remoteVideo.onplay = function() {
console.log("ADD STREAM");
if(remoteVideo.mozCaptureStream()) stream = remoteVideo.mozCaptureStream();
else stream = remoteVideo.captureStream();
}

60
clientV/scripts/signal.js Normal file
View File

@@ -0,0 +1,60 @@
//connecting to our signaling server
var conn = new WebSocket('ws://localhost:9090');
conn.onopen = function () {
console.log("Connected to the signaling server");
};
//when we got a message from a signaling server
conn.onmessage = function (msg) {
console.log("Got message", msg.data);
var data = JSON.parse(msg.data);
switch(data.type) {
case "login":
handleLogin(data.success);
break;
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer, data.name);
break;
case "candidate":
handleCandidate(data.candidate, data.name);
break;
case "userlist":
handleUserlist(data);
break;
case "leave":
handleLeave();
break;
case "video-offer":
handleVideoOffer(data.sdp, data.name);
break;
case "video-answer":
handleVideoAnswer(data.sdp, data.name);
break;
default:
break;
}
};
conn.onerror = function (err) {
console.log("Got error", err);
};
//alias for sending JSON encoded messages
function send(message) {
console.log("Sended message", message);
conn.send(JSON.stringify(message));
};

0
clientV/style.css Normal file
View File

21
server/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
server/config.json Normal file
View File

@@ -0,0 +1,3 @@
{
"port" : 8080
}

602
server/package-lock.json generated Normal file
View File

@@ -0,0 +1,602 @@
{
"name": "lilstreamy-server",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"requires": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
"requires": {
"bytes": "3.1.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"qs": "6.7.0",
"raw-body": "2.4.0",
"type-is": "~1.6.17"
}
},
"buffer": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz",
"integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4"
}
},
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
},
"circular-json": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.9.tgz",
"integrity": "sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ=="
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
"requires": {
"safe-buffer": "5.1.2"
}
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"requires": {
"object-assign": "^4",
"vary": "^1"
}
},
"crc": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz",
"integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==",
"requires": {
"buffer": "^5.1.0"
}
},
"date-format": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz",
"integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"requires": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
"body-parser": "1.19.0",
"content-disposition": "0.5.3",
"content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.1.2",
"send": "0.17.1",
"serve-static": "1.14.1",
"setprototypeof": "1.1.1",
"statuses": "~1.5.0",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
}
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"statuses": "~1.5.0",
"unpipe": "~1.0.0"
}
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
},
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
}
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"js-yaml": {
"version": "3.12.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz",
"integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"log4js": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/log4js/-/log4js-3.0.6.tgz",
"integrity": "sha512-ezXZk6oPJCWL483zj64pNkMuY/NcRX5MPiB0zE6tjZM137aeusrOnW1ecxgF9cmwMWkBMhjteQxBPoZBh9FDxQ==",
"requires": {
"circular-json": "^0.5.5",
"date-format": "^1.2.0",
"debug": "^3.1.0",
"rfdc": "^1.1.2",
"streamroller": "0.7.0"
},
"dependencies": {
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
"version": "1.43.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
"integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
},
"mime-types": {
"version": "2.1.26",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
"integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
"requires": {
"mime-db": "1.43.0"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"requires": {
"minimist": "^1.2.5"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"node-turn": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/node-turn/-/node-turn-0.0.4.tgz",
"integrity": "sha512-ZmMbEt9146syRhBqrpgOTTdI5xn5seXpB6+/hkRLQV86ZHWuvF3TNHfN3TTFs3pf/ceY9rbdBs+OnaFQqqGU7g==",
"requires": {
"crc": "^3.8.0",
"js-yaml": "~3.12.0",
"log4js": "~3.0.5"
}
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
"requires": {
"ee-first": "1.1.1"
}
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"proxy-addr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
"requires": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.9.1"
}
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"raw-body": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
"requires": {
"bytes": "3.1.0",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
}
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"rfdc": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz",
"integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug=="
},
"rtcpeerconnection-shim": {
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz",
"integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==",
"requires": {
"sdp": "^2.6.0"
}
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sdp": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz",
"integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw=="
},
"send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
"requires": {
"debug": "2.6.9",
"depd": "~1.1.2",
"destroy": "~1.0.4",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "~1.7.2",
"mime": "1.6.0",
"ms": "2.1.1",
"on-finished": "~2.3.0",
"range-parser": "~1.2.1",
"statuses": "~1.5.0"
},
"dependencies": {
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
"serve-static": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
"requires": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.17.1"
}
},
"setprototypeof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"streamroller": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz",
"integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==",
"requires": {
"date-format": "^1.2.0",
"debug": "^3.1.0",
"mkdirp": "^0.5.1",
"readable-stream": "^2.3.0"
},
"dependencies": {
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
},
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"requires": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
}
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
},
"webrtc-adapter": {
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.5.1.tgz",
"integrity": "sha512-R5LkIR/APjODkstSXFOztOmINXQ0nqIGfUoKTtCzjyiDXHNgwhkqZ9vi8UzGyjfUBibuZ0ZzVyV10qtuLGW3CQ==",
"requires": {
"rtcpeerconnection-shim": "^1.2.15",
"sdp": "^2.12.0"
}
},
"ws": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz",
"integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ=="
}
}
}

19
server/package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "lilstreamy-server",
"version": "1.0.0",
"description": "A simple webRTC server",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.17.1",
"node-turn": "0.0.4",
"webrtc-adapter": "^7.5.1",
"ws": "^7.2.3"
}
}

18
server/src/server.js Normal file
View File

@@ -0,0 +1,18 @@
const express = require('express');
const bodyParser = require("body-parser");
const signal = require("./signal3");
const cors = require("cors");
const config = require("../config.json");
const app = express()
const {port} = config
app.use(cors())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(express.static("../clientV"))
app.listen(port, () => {
console.log(`Launching Lil'Streamy on ${port}`)
})

154
server/src/signal.js Normal file
View File

@@ -0,0 +1,154 @@
const WebSocketServer = require('ws').Server;
var wss = new WebSocketServer({port: 9090});
var users = {};
wss.on('connection', function(connection) {
console.log("User connected");
//when server gets a message from a connected user
connection.on('message', (message) => onMessage(connection, message));
//when user exits, for example closes a browser window
//this may help if we are still in "offer","answer" or "candidate" state
connection.on("close", () => onClose(connection));
});
function sendTo(connection, message) {
connection.send(JSON.stringify(message));
}
function onClose(connection) {
if(connection.name) {
delete users[connection.name];
if(connection.otherName) {
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
}
}
}
function onMessage(connection, message) {
var data;
//accepting only JSON messages
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
//switching type of the user message
switch (data.type) {
//when a user tries to login
case "login":
console.log("User logged", data.name);
//if anyone is logged in with this username then refuse
if(users[data.name]) {
sendTo(connection, {
type: "login",
success: false
});
} else {
//save user connection on the server
users[data.target] = connection;
connection.name = data.target;
sendTo(connection, {
type: "login",
success: true
});
}
break;
case "offer":
//for ex. UserA wants to call UserB
console.log("Sending offer to: ", data.target);
//if UserB exists then send him offer details
var conn = users[data.target];
if(conn != null) {
//setting that UserA connected with UserB
connection.otherName = data.target;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.target
});
}
break;
case "answer":
console.log("Sending answer to: ", data.target);
//for ex. UserB answers UserA
var conn = users[data.target];
if(conn != null) {
connection.otherName = data.target;
sendTo(conn, {
type: "answer",
answer: data.answer
});
}
break;
case "candidate":
console.log("Sending candidate to:",data.target);
var conn = users[data.name];
if(conn != null) {
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
case "leave":
console.log("Disconnecting from", data.target);
var conn = users[data.target];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
break;
case "video-offer":
console.log("Send video offer to", data.target);
var conn = users[data.name];
if(conn != null) {
connection.otherName = data.target;
sendTo(conn, {
type: "video-offer",
answer: data.answer
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Command not found: " + data.type
});
break;
}
}

330
server/src/signal2.js Normal file
View File

@@ -0,0 +1,330 @@
"use strict";
var http = require('http');
var https = require('https');
var fs = require('fs');
var WebSocketServer = require('ws').Server;
// Pathnames of the SSL key and certificate files to use for
// HTTPS connections.
const keyFilePath = "/etc/pki/tls/private/mdn-samples.mozilla.org.key";
const certFilePath = "/etc/pki/tls/certs/mdn-samples.mozilla.org.crt";
// Used for managing the text chat user list.
var connectionArray = [];
var nextID = Date.now();
var appendToMakeUnique = 1;
// Output logging information to console
function log(text) {
var time = new Date();
console.log("[" + time.toLocaleTimeString() + "] " + text);
}
// If you want to implement support for blocking specific origins, this is
// where you do it. Just return false to refuse WebSocket connections given
// the specified origin.
function originIsAllowed(origin) {
return true; // We will accept all connections
}
// Scans the list of users and see if the specified name is unique. If it is,
// return true. Otherwise, returns false. We want all users to have unique
// names.
function isUsernameUnique(name) {
var isUnique = true;
var i;
for (i=0; i<connectionArray.length; i++) {
if (connectionArray[i].username === name) {
isUnique = false;
break;
}
}
return isUnique;
}
// Sends a message (which is already stringified JSON) to a single
// user, given their username. We use this for the WebRTC signaling,
// and we could use it for private text messaging.
function sendToOneUser(target, msgString) {
var isUnique = true;
var i;
for (i=0; i<connectionArray.length; i++) {
if (connectionArray[i].username === target) {
connectionArray[i].sendUTF(msgString);
break;
}
}
}
// Scan the list of connections and return the one for the specified
// clientID. Each login gets an ID that doesn't change during the session,
// so it can be tracked across username changes.
function getConnectionForID(id) {
var connect = null;
var i;
for (i=0; i<connectionArray.length; i++) {
if (connectionArray[i].clientID === id) {
connect = connectionArray[i];
break;
}
}
return connect;
}
// Builds a message object of type "userlist" which contains the names of
// all connected users. Used to ramp up newly logged-in users and,
// inefficiently, to handle name change notifications.
function makeUserListMessage() {
var userListMsg = {
type: "userlist",
users: []
};
var i;
// Add the users to the list
for (i=0; i<connectionArray.length; i++) {
userListMsg.users.push(connectionArray[i].username);
}
return userListMsg;
}
// Sends a "userlist" message to all chat members. This is a cheesy way
// to ensure that every join/drop is reflected everywhere. It would be more
// efficient to send simple join/drop messages to each user, but this is
// good enough for this simple example.
function sendUserListToAll() {
var userListMsg = makeUserListMessage();
var userListMsgStr = JSON.stringify(userListMsg);
var i;
for (i=0; i<connectionArray.length; i++) {
connectionArray[i].sendUTF(userListMsgStr);
}
}
// Try to load the key and certificate files for SSL so we can
// do HTTPS (required for non-local WebRTC).
var httpsOptions = {
key: null,
cert: null
};
try {
httpsOptions.key = fs.readFileSync(keyFilePath);
try {
httpsOptions.cert = fs.readFileSync(certFilePath);
} catch(err) {
httpsOptions.key = null;
httpsOptions.cert = null;
}
} catch(err) {
httpsOptions.key = null;
httpsOptions.cert = null;
}
// If we were able to get the key and certificate files, try to
// start up an HTTPS server.
var webServer = null;
try {
if (httpsOptions.key && httpsOptions.cert) {
webServer = https.createServer(httpsOptions, handleWebRequest);
}
} catch(err) {
webServer = null;
}
if (!webServer) {
try {
webServer = http.createServer({}, handleWebRequest);
} catch(err) {
webServer = null;
log(`Error attempting to create HTTP(s) server: ${err.toString()}`);
}
}
// Our HTTPS server does nothing but service WebSocket
// connections, so every request just returns 404. Real Web
// requests are handled by the main server on the box. If you
// want to, you can return real HTML here and serve Web content.
function handleWebRequest(request, response) {
log ("Received request for " + request.url);
response.writeHead(404);
response.end();
}
// Spin up the HTTPS server on the port assigned to this sample.
// This will be turned into a WebSocket port very shortly.
webServer.listen(6503, function() {
log("Server is listening on port 6503");
});
// Create the WebSocket server by converting the HTTPS server into one.
var wsServer = new WebSocketServer({
httpServer: webServer,
autoAcceptConnections: false
});
if (!wsServer) {
log("ERROR: Unable to create WbeSocket server!");
}
// Set up a "connect" message handler on our WebSocket server. This is
// called whenever a user connects to the server's port using the
// WebSocket protocol.
wsServer.on('request', function(request) {
if (!originIsAllowed(request.origin)) {
request.reject();
log("Connection from " + request.origin + " rejected.");
return;
}
// Accept the request and get a connection.
var connection = request.accept("json", request.origin);
// Add the new connection to our list of connections.
log("Connection accepted from " + connection.remoteAddress + ".");
connectionArray.push(connection);
connection.clientID = nextID;
nextID++;
// Send the new client its token; it send back a "username" message to
// tell us what username they want to use.
var msg = {
type: "id",
id: connection.clientID
};
connection.sendUTF(JSON.stringify(msg));
// Set up a handler for the "message" event received over WebSocket. This
// is a message sent by a client, and may be text to share with other
// users, a private message (text or signaling) for one user, or a command
// to the server.
connection.on('message', function(message) {
if (message.type === 'utf8') {
log("Received Message: " + message.utf8Data);
// Process incoming data.
var sendToClients = true;
msg = JSON.parse(message.utf8Data);
var connect = getConnectionForID(msg.id);
// Take a look at the incoming object and act on it based
// on its type. Unknown message types are passed through,
// since they may be used to implement client-side features.
// Messages with a "target" property are sent only to a user
// by that name.
switch(msg.type) {
// Public, textual message
case "message":
msg.name = connect.username;
msg.text = msg.text.replace(/(<([^>]+)>)/ig, "");
break;
// Username change
case "username":
var nameChanged = false;
var origName = msg.name;
// Ensure the name is unique by appending a number to it
// if it's not; keep trying that until it works.
while (!isUsernameUnique(msg.name)) {
msg.name = origName + appendToMakeUnique;
appendToMakeUnique++;
nameChanged = true;
}
// If the name had to be changed, we send a "rejectusername"
// message back to the user so they know their name has been
// altered by the server.
if (nameChanged) {
var changeMsg = {
id: msg.id,
type: "rejectusername",
name: msg.name
};
connect.sendUTF(JSON.stringify(changeMsg));
}
// Set this connection's final username and send out the
// updated user list to all users. Yeah, we're sending a full
// list instead of just updating. It's horribly inefficient
// but this is a demo. Don't do this in a real app.
connect.username = msg.name;
sendUserListToAll();
sendToClients = false; // We already sent the proper responses
break;
}
// Convert the revised message back to JSON and send it out
// to the specified client or all clients, as appropriate. We
// pass through any messages not specifically handled
// in the select block above. This allows the clients to
// exchange signaling and other control objects unimpeded.
if (sendToClients) {
var msgString = JSON.stringify(msg);
var i;
// If the message specifies a target username, only send the
// message to them. Otherwise, send it to every user.
if (msg.target && msg.target !== undefined && msg.target.length !== 0) {
sendToOneUser(msg.target, msgString);
} else {
for (i=0; i<connectionArray.length; i++) {
connectionArray[i].sendUTF(msgString);
}
}
}
}
});
// Handle the WebSocket "close" event; this means a user has logged off
// or has been disconnected.
connection.on('close', function(reason, description) {
// First, remove the connection from the list of connections.
connectionArray = connectionArray.filter(function(el, idx, ar) {
return el.connected;
});
// Now send the updated user list. Again, please don't do this in a
// real application. Your users won't like you very much.
sendUserListToAll();
// Build and output log output for close information.
var logMessage = "Connection closed: " + connection.remoteAddress + " (" +
reason;
if (description !== null && description.length !== 0) {
logMessage += ": " + description;
}
logMessage += ")";
log(logMessage);
});
});

100
server/src/signal3.js Normal file
View File

@@ -0,0 +1,100 @@
const WebSocketServer = require('ws').Server;
var wss = new WebSocketServer({port: 9090});
var users = {};
wss.on('connection', function(connection) {
console.log("User connected");
connection.on('message', (message) => onMessage(connection, message));
connection.on("close", () => onClose(connection));
});
function sendTo(connection, message) {
connection.send(JSON.stringify(message));
}
function onClose(connection) {
if(connection.name) {
delete users[connection.name];
if(connection.otherName) {
console.log("Disconnecting from ", connection.otherName);
var conn = users[connection.otherName];
conn.otherName = null;
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}
}
}
}
function onMessage(connection, message) {
var data;
try {
data = JSON.parse(message);
} catch (e) {
console.log("Invalid JSON");
data = {};
}
switch (data.type) {
case "login" :
console.log("User logged", data.name);
if(users[data.name]) {
sendTo(connection, {
type: "login",
success: false
});
} else {
users[data.name] = connection;
connection.name = data.name;
sendTo(connection, {
type: "login",
success: true
});
}
break;
case "leave" :
console.log("Disconnecting from", data.name);
var conn = users[data.name];
conn.otherName = null;
//notify the other user so he can disconnect his peer connection
/*
if(conn != null) {
sendTo(conn, {
type: "leave"
});
}*/
break;
case "userlist" :
console.log("Send list to", data.name);
sendTo(connection, {
type: "userlist",
list: users
});
break;
default:
if (data.target){
var targetConnection = users[data.target];
console.log("Forward message from " + data.name + " to " + data.target + " (" + data.type + ")");
sendTo(targetConnection, data);
} else {
sendTo(connection, {
type: "error",
message: "Command not found: " + data.type
});
}
}
}