From cabf32a8791e7925138d37778a1b6aa3f257c1bd Mon Sep 17 00:00:00 2001 From: mcottontensor <80377552+mcottontensor@users.noreply.github.com> Date: Wed, 13 Sep 2023 12:55:25 +1000 Subject: [PATCH 01/21] Touching ui-library to trigger build action. Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com> --- Frontend/ui-library/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Frontend/ui-library/package.json b/Frontend/ui-library/package.json index 9a441f1b..2d96753e 100644 --- a/Frontend/ui-library/package.json +++ b/Frontend/ui-library/package.json @@ -46,3 +46,4 @@ "access": "public" } } + From f6d724a3fefc269e1ed5059aacaf001c22aa46cb Mon Sep 17 00:00:00 2001 From: mcottontensor <80377552+mcottontensor@users.noreply.github.com> Date: Wed, 13 Sep 2023 12:56:26 +1000 Subject: [PATCH 02/21] Update RELEASE_VERSION Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com> --- RELEASE_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_VERSION b/RELEASE_VERSION index 81340c7e..8acdd82b 100644 --- a/RELEASE_VERSION +++ b/RELEASE_VERSION @@ -1 +1 @@ -0.0.4 +0.0.1 From 3816f4b535bb8f126968793a60f823682d863caa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 14 Sep 2023 10:47:07 +1000 Subject: [PATCH 03/21] Remove unit conversion for bitrate from URL. URL is already in kbps (#369) (#372) (cherry picked from commit eb9a665a0a4333eccc616acaade2ccf5e653b0aa) Co-authored-by: William Belcher --- Frontend/library/src/PixelStreaming/PixelStreaming.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Frontend/library/src/PixelStreaming/PixelStreaming.ts b/Frontend/library/src/PixelStreaming/PixelStreaming.ts index c2c39944..5b9d3908 100644 --- a/Frontend/library/src/PixelStreaming/PixelStreaming.ts +++ b/Frontend/library/src/PixelStreaming/PixelStreaming.ts @@ -603,13 +603,13 @@ export class PixelStreaming { this.config.setNumericSetting( NumericParameters.WebRTCMinBitrate, (useUrlParams && urlParams.has(NumericParameters.WebRTCMinBitrate)) - ? Number.parseInt(urlParams.get(NumericParameters.WebRTCMinBitrate)) / 1000 /* bps to kbps */ + ? Number.parseInt(urlParams.get(NumericParameters.WebRTCMinBitrate)) : settings.WebRTCSettings.MinBitrate / 1000 /* bps to kbps */ ); this.config.setNumericSetting( NumericParameters.WebRTCMaxBitrate, (useUrlParams && urlParams.has(NumericParameters.WebRTCMaxBitrate)) - ? Number.parseInt(urlParams.get(NumericParameters.WebRTCMaxBitrate)) / 1000 /* bps to kbps */ + ? Number.parseInt(urlParams.get(NumericParameters.WebRTCMaxBitrate)) : settings.WebRTCSettings.MaxBitrate / 1000 /* bps to kbps */ ); From 828123c6c8fa7ac2b3efd35f322e9499613eacf8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:10:42 +1000 Subject: [PATCH 04/21] Bump postcss in /Frontend/implementations/typescript (#383) Bumps [postcss](https://github.com/postcss/postcss) from 8.4.21 to 8.4.31. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.21...8.4.31) --- updated-dependencies: - dependency-name: postcss dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit 1a749cea8a23aa482b095a62e426ee8ebe72b0b3) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../typescript/package-lock.json | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/Frontend/implementations/typescript/package-lock.json b/Frontend/implementations/typescript/package-lock.json index f4eb7a47..c9f2fffb 100644 --- a/Frontend/implementations/typescript/package-lock.json +++ b/Frontend/implementations/typescript/package-lock.json @@ -2207,9 +2207,16 @@ } }, "node_modules/nanoid": { - "version": "3.3.4", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -2505,8 +2512,9 @@ } }, "node_modules/postcss": { - "version": "8.4.21", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "funding": [ { @@ -2516,10 +2524,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -5463,8 +5475,9 @@ } }, "nanoid": { - "version": "3.3.4", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true }, "negotiator": { @@ -5680,11 +5693,12 @@ } }, "postcss": { - "version": "8.4.21", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "requires": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } From ad85b02b5edb74f2d4390b7d8d0345fc871feca6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:24:19 +1000 Subject: [PATCH 05/21] Bump postcss from 8.4.21 to 8.4.31 in /Frontend/implementations/react (#386) Bumps [postcss](https://github.com/postcss/postcss) from 8.4.21 to 8.4.31. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.21...8.4.31) --- updated-dependencies: - dependency-name: postcss dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit 55b771e6331cffd898d4aeb6c1d22d1429881817) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: William Belcher --- .../implementations/react/package-lock.json | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Frontend/implementations/react/package-lock.json b/Frontend/implementations/react/package-lock.json index f9be0988..6a8d9689 100644 --- a/Frontend/implementations/react/package-lock.json +++ b/Frontend/implementations/react/package-lock.json @@ -2500,10 +2500,16 @@ } }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -2831,9 +2837,9 @@ } }, "node_modules/postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "funding": [ { @@ -2843,10 +2849,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, From 1301fde89a78dbfc293c164323f5f645d6c44bb8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:26:44 +1000 Subject: [PATCH 06/21] Bump @babel/traverse from 7.21.3 to 7.23.2 in /Frontend/library (#388) Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.21.3 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit 81c3f52f8450683e915c86f9eedfa4bf28a0212a) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: William Belcher --- Frontend/library/package-lock.json | 379 +++++++++++++++++++---------- 1 file changed, 255 insertions(+), 124 deletions(-) diff --git a/Frontend/library/package-lock.json b/Frontend/library/package-lock.json index 6a207401..76e764d5 100644 --- a/Frontend/library/package-lock.json +++ b/Frontend/library/package-lock.json @@ -56,17 +56,89 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", @@ -116,12 +188,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", - "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.21.3", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -174,34 +246,34 @@ "dev": true }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -260,30 +332,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -313,13 +385,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -398,9 +470,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", - "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -572,33 +644,33 @@ } }, "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", - "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.3", - "@babel/types": "^7.21.3", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -616,13 +688,13 @@ } }, "node_modules/@babel/types": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", - "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -6817,12 +6889,71 @@ } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "@babel/compat-data": { @@ -6863,12 +6994,12 @@ } }, "@babel/generator": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", - "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.21.3", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -6911,28 +7042,28 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-module-imports": { @@ -6976,24 +7107,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -7014,13 +7145,13 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "dependencies": { @@ -7083,9 +7214,9 @@ } }, "@babel/parser": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", - "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -7206,30 +7337,30 @@ } }, "@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", - "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.3", - "@babel/types": "^7.21.3", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -7243,13 +7374,13 @@ } }, "@babel/types": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", - "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, From 16d80e27e14edf13ec9a7fb015da42d2f4a16036 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:44:18 +1000 Subject: [PATCH 07/21] Handle statsPanel or settingsPanel being undefined. (#394) Signed-off-by: timbotimbo (cherry picked from commit af5339bec840c9ba8f5da5d4107a5a35ea641f1d) Co-authored-by: timbotimbo --- .../ui-library/src/Application/Application.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Frontend/ui-library/src/Application/Application.ts b/Frontend/ui-library/src/Application/Application.ts index 8ef496f5..d1950435 100644 --- a/Frontend/ui-library/src/Application/Application.ts +++ b/Frontend/ui-library/src/Application/Application.ts @@ -487,7 +487,7 @@ export class Application { * Shows or hides the settings panel if clicked */ settingsClicked() { - this.statsPanel.hide(); + this.statsPanel?.hide(); this.settingsPanel.toggleVisibility(); } @@ -495,7 +495,7 @@ export class Application { * Shows or hides the stats panel if clicked */ statsClicked() { - this.settingsPanel.hide(); + this.settingsPanel?.hide(); this.statsPanel.toggleVisibility(); } @@ -583,7 +583,7 @@ export class Application { ); } // disable starting a latency checks - this.statsPanel.onDisconnect(); + this.statsPanel?.onDisconnect(); } /** @@ -630,7 +630,7 @@ export class Application { if (!this.stream.config.isFlagEnabled(Flags.AutoPlayVideo)) { this.showPlayOverlay(); } - this.statsPanel.onVideoInitialized(this.stream); + this.statsPanel?.onVideoInitialized(this.stream); } /** @@ -646,25 +646,25 @@ export class Application { onInitialSettings(settings: InitialSettings) { if (settings.PixelStreamingSettings) { - this.statsPanel.configure(settings.PixelStreamingSettings); + this.statsPanel?.configure(settings.PixelStreamingSettings); } } onStatsReceived(aggregatedStats: AggregatedStats) { // Grab all stats we can off the aggregated stats - this.statsPanel.handleStats(aggregatedStats); + this.statsPanel?.handleStats(aggregatedStats); } onLatencyTestResults(latencyTimings: LatencyTestResults) { - this.statsPanel.latencyTest.handleTestResult(latencyTimings); + this.statsPanel?.latencyTest.handleTestResult(latencyTimings); } onDataChannelLatencyTestResults(result: DataChannelLatencyTestResult) { - this.statsPanel.dataChannelLatencyTest.handleTestResult(result); + this.statsPanel?.dataChannelLatencyTest.handleTestResult(result); } onPlayerCount(playerCount: number) { - this.statsPanel.handlePlayerCount(playerCount); + this.statsPanel?.handlePlayerCount(playerCount); } handleStreamerListMessage(messageStreamingList: MessageStreamerList, autoSelectedStreamerId: string | null) { From 952b309c71fab8e19c68b54a6ad28464c7f4a4c3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:48:19 +1000 Subject: [PATCH 08/21] Expose JSS InsertionPoint (#397) Signed-off-by: timbotimbo Co-authored-by: William Belcher (cherry picked from commit 8ba410154d81d365bb3344250c7ae5969c0ba844) Co-authored-by: timbotimbo --- .../src/Styles/PixelStreamingApplicationStyles.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Frontend/ui-library/src/Styles/PixelStreamingApplicationStyles.ts b/Frontend/ui-library/src/Styles/PixelStreamingApplicationStyles.ts index 2f77bc71..128d89a6 100644 --- a/Frontend/ui-library/src/Styles/PixelStreamingApplicationStyles.ts +++ b/Frontend/ui-library/src/Styles/PixelStreamingApplicationStyles.ts @@ -526,14 +526,16 @@ export class PixelStreamingApplicationStyle { customStyles?: Partial; lightModePalette?: ColorPalette; darkModePalette?: ColorPalette; + jssInsertionPoint?: string | HTMLElement; }) { - const { customStyles, lightModePalette, darkModePalette } = + const { customStyles, lightModePalette, darkModePalette, jssInsertionPoint } = options ?? {}; // One time setup with default plugins and settings. const jssOptions = { // JSS has many interesting plugins we may wish to turn on //plugins: [functions(), template(), global(), extend(), nested(), compose(), camelCase(), defaultUnit(options.defaultUnit), expand(), vendorPrefixer(), propsSort()] - plugins: [global(), camelCase()] + plugins: [global(), camelCase()], + insertionPoint: jssInsertionPoint }; jss.setup(jssOptions); From ee82bd398c775396c64e7b4320adb6851d36dea5 Mon Sep 17 00:00:00 2001 From: William Belcher Date: Thu, 19 Oct 2023 14:40:52 +1000 Subject: [PATCH 09/21] Update SignallingWebServer platform scripts to support Mac (#389) * Update SignallingWebServer platform scripts to support Mac x86_64 and Arm64 * Update bash scripts to default to Linux * Update coturn URLs to use the binaries provided by the PixelStreamingInfrastructure --- .../platform_scripts/bash/Start_TURNServer.sh | 7 +- .../platform_scripts/bash/setup.sh | 66 ++++++++++++++----- .../platform_scripts/cmd/setup_coturn.bat | 2 +- 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/SignallingWebServer/platform_scripts/bash/Start_TURNServer.sh b/SignallingWebServer/platform_scripts/bash/Start_TURNServer.sh index 811a8b9f..f1d04309 100755 --- a/SignallingWebServer/platform_scripts/bash/Start_TURNServer.sh +++ b/SignallingWebServer/platform_scripts/bash/Start_TURNServer.sh @@ -25,7 +25,12 @@ echo "" # Hmm, plain text realm="PixelStreaming" -process="turnserver" +process="" +if [ "$(uname)" == "Darwin" ]; then + process="${BASH_LOCATION}/coturn/bin/turnserver" +else + process="turnserver" +fi arguments="-c turnserver.conf --allowed-peer-ip=$localip -p ${turnport} -r $realm -X $publicip -E $localip -L $localip --no-cli --no-tls --no-dtls --pidfile /var/run/turnserver.pid -f -a -v -u ${turnusername}:${turnpassword}" # Add arguments passed to script to arguments for executable diff --git a/SignallingWebServer/platform_scripts/bash/setup.sh b/SignallingWebServer/platform_scripts/bash/setup.sh index bfb196df..dee1c1bd 100755 --- a/SignallingWebServer/platform_scripts/bash/setup.sh +++ b/SignallingWebServer/platform_scripts/bash/setup.sh @@ -1,6 +1,7 @@ #!/bin/bash # Copyright Epic Games, Inc. All Rights Reserved. BASH_LOCATION=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +NODE_VERSION=v18.17.0 pushd "${BASH_LOCATION}" > /dev/null @@ -128,10 +129,25 @@ node_version="" if [[ -f "${BASH_LOCATION}/node/bin/node" ]]; then node_version=$("${BASH_LOCATION}/node/bin/node" --version) fi -check_and_install "node" "$node_version" "v18.17.0" "curl https://nodejs.org/dist/v18.17.0/node-v18.17.0-linux-x64.tar.gz --output node.tar.xz - && tar -xf node.tar.xz - && rm node.tar.xz - && mv node-v*-linux-x64 \"${BASH_LOCATION}/node\"" + +node_url="" +if [ "$(uname)" == "Darwin" ]; then + arch=$(uname -m) + if [[ $arch == x86_64* ]]; then + node_url="https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-darwin-x64.tar.gz" + elif [[ $arch == arm* ]]; then + node_url="https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-darwin-arm64.tar.gz" + else + echo 'Incompatible architecture. Only x86_64 and ARM64 are supported' + exit -1 + fi +else + node_url="https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-linux-x64.tar.gz" +fi +check_and_install "node" "$node_version" "$NODE_VERSION" "curl $node_url --output node.tar.xz + && tar -xf node.tar.xz + && rm node.tar.xz + && mv node-v*-*-* \"${BASH_LOCATION}/node\"" PATH="${BASH_LOCATION}/node/bin:$PATH" "${BASH_LOCATION}/node/lib/node_modules/npm/bin/npm-cli.js" install @@ -144,20 +160,38 @@ setup_frontend popd > /dev/null # BASH_SOURCE -#command #dep_name #get_version_string #version_min #install command -coturn_version=$(if command -v turnserver &> /dev/null; then echo 1; else echo 0; fi) -if [ $coturn_version -eq 0 ]; then - if ! command -v apt-get &> /dev/null; then - echo "Setup for the scripts is designed for use with distros that use the apt-get package manager" \ - "if you are seeing this message you will have to update \"${BASH_LOCATION}/setup.sh\" with\n" \ - "a package manger and the equivalent packages for your distribution. Please follow the\n" \ - "instructions found at https://pkgs.org/search/?q=coturn to install Coturn for your specific distribution" - exit 1 +if [ "$(uname)" == "Darwin" ]; then + if [ -d "${BASH_LOCATION}/coturn" ]; then + echo 'CoTURN directory found...skipping install.' else - if [ `id -u` -eq 0 ]; then - check_and_install "coturn" "$coturn_version" "1" "apt-get install -y coturn" + echo 'CoTURN directory not found...beginning CoTURN download for Mac.' + coturn_url="" + if [[ $arch == x86_64* ]]; then + coturn_url="https://github.com/EpicGames/PixelStreamingInfrastructure/releases/download/v4.6.2-coturn-mac-x86_64/turnserver.zip" + elif [[ $arch == arm* ]]; then + coturn_url="https://github.com/EpicGames/PixelStreamingInfrastructure/releases/download/v4.6.2-coturn-mac-arm64/turnserver.zip" + fi + curl -L -o ./turnserver.zip "$coturn_url" + mkdir "${BASH_LOCATION}/coturn" + tar -xf turnserver.zip -C "${BASH_LOCATION}/coturn" + rm turnserver.zip + fi +else + #command #dep_name #get_version_string #version_min #install command + coturn_version=$(if command -v turnserver &> /dev/null; then echo 1; else echo 0; fi) + if [ $coturn_version -eq 0 ]; then + if ! command -v apt-get &> /dev/null; then + echo "Setup for the scripts is designed for use with distros that use the apt-get package manager" \ + "if you are seeing this message you will have to update \"${BASH_LOCATION}/setup.sh\" with\n" \ + "a package manger and the equivalent packages for your distribution. Please follow the\n" \ + "instructions found at https://pkgs.org/search/?q=coturn to install Coturn for your specific distribution" + exit 1 else - check_and_install "coturn" "$coturn_version" "1" "sudo apt-get install -y coturn" + if [ `id -u` -eq 0 ]; then + check_and_install "coturn" "$coturn_version" "1" "apt-get install -y coturn" + else + check_and_install "coturn" "$coturn_version" "1" "sudo apt-get install -y coturn" + fi fi fi fi diff --git a/SignallingWebServer/platform_scripts/cmd/setup_coturn.bat b/SignallingWebServer/platform_scripts/cmd/setup_coturn.bat index fd9f2e7e..d2640b35 100644 --- a/SignallingWebServer/platform_scripts/cmd/setup_coturn.bat +++ b/SignallingWebServer/platform_scripts/cmd/setup_coturn.bat @@ -12,7 +12,7 @@ if exist coturn\ ( echo CoTURN directory not found...beginning CoTURN download for Windows. @Rem Download nodejs and follow redirects. - curl -L -o ./turnserver.zip "https://github.com/mcottontensor/coturn/releases/download/v4.5.2-windows/turnserver.zip" + curl -L -o ./turnserver.zip "https://github.com/EpicGames/PixelStreamingInfrastructure/releases/download/v4.5.2-coturn-windows/turnserver.zip" @Rem Unarchive the .zip to a directory called "turnserver" mkdir coturn & tar -xf turnserver.zip -C coturn From df2ef8ba044b8a68a1eccb204d70d68ae9df8a6e Mon Sep 17 00:00:00 2001 From: Matthew Cotton Date: Mon, 23 Oct 2023 11:29:27 +1100 Subject: [PATCH 10/21] Small fix to allow the matchmaker start scripts to find the custom install of node. (cherry picked from commit c76284041e17fd159bc4eb043e028abe2421d102) --- Matchmaker/platform_scripts/bash/run.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/Matchmaker/platform_scripts/bash/run.sh b/Matchmaker/platform_scripts/bash/run.sh index 71dd38f6..3e254b44 100755 --- a/Matchmaker/platform_scripts/bash/run.sh +++ b/Matchmaker/platform_scripts/bash/run.sh @@ -18,6 +18,7 @@ echo "Starting Matchmaker use ctrl-c to exit" echo "-----------------------------------------" echo "" +PATH="${BASH_LOCATION}/node/bin:$PATH" start_process $process popd > /dev/null # ../.. From 23eb2601e1f3e87331f16f0a140e2e0821bfec21 Mon Sep 17 00:00:00 2001 From: Bramford Horton Date: Tue, 10 Oct 2023 14:42:38 +1300 Subject: [PATCH 11/21] Fix/allow video autoplay without click (cherry picked from commit 75cd9754005612c3d03386e2dc9bdf0ab2e95570) --- .../src/VideoPlayer/StreamController.ts | 1 + .../library/src/VideoPlayer/VideoPlayer.ts | 10 ++++- .../WebRtcPlayer/WebRtcPlayerController.ts | 42 ++++++++++--------- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/Frontend/library/src/VideoPlayer/StreamController.ts b/Frontend/library/src/VideoPlayer/StreamController.ts index 813ed900..879ab1a2 100644 --- a/Frontend/library/src/VideoPlayer/StreamController.ts +++ b/Frontend/library/src/VideoPlayer/StreamController.ts @@ -18,6 +18,7 @@ export class StreamController { constructor(videoElementProvider: VideoPlayer) { this.videoElementProvider = videoElementProvider; this.audioElement = document.createElement('Audio') as HTMLAudioElement; + this.videoElementProvider.setAudioElement(this.audioElement); } /** diff --git a/Frontend/library/src/VideoPlayer/VideoPlayer.ts b/Frontend/library/src/VideoPlayer/VideoPlayer.ts index d389a06d..af6089a4 100644 --- a/Frontend/library/src/VideoPlayer/VideoPlayer.ts +++ b/Frontend/library/src/VideoPlayer/VideoPlayer.ts @@ -18,6 +18,7 @@ declare global { export class VideoPlayer { private config: Config; private videoElement: HTMLVideoElement; + private audioElement?: HTMLAudioElement; private orientationChangeTimeout: number; private lastTimeResized = new Date().getTime(); @@ -52,8 +53,11 @@ export class VideoPlayer { ); }; - // set play for video + // set play for video (and audio) this.videoElement.onclick = () => { + if (this.audioElement != undefined && this.audioElement.paused) { + this.audioElement.play(); + } if (this.videoElement.paused) { this.videoElement.play(); } @@ -70,6 +74,10 @@ export class VideoPlayer { ); } + public setAudioElement(audioElement: HTMLAudioElement) : void { + this.audioElement = audioElement; + } + /** * Sets up the video element with any application config and plays the video element. * @returns A promise for if playing the video was successful or not. diff --git a/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts b/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts index 167e624a..b18b31b1 100644 --- a/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts +++ b/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts @@ -1109,26 +1109,30 @@ export class WebRtcPlayerController { this.pixelStreaming.dispatchEvent(new PlayStreamEvent()); if (this.streamController.audioElement.srcObject) { - this.streamController.audioElement.muted = - this.config.isFlagEnabled(Flags.StartVideoMuted); + const startMuted = this.config.isFlagEnabled(Flags.StartVideoMuted) + this.streamController.audioElement.muted = startMuted; - this.streamController.audioElement - .play() - .then(() => { - this.playVideo(); - }) - .catch((onRejectedReason) => { - Logger.Log(Logger.GetStackTrace(), onRejectedReason); - Logger.Log( - Logger.GetStackTrace(), - 'Browser does not support autoplaying video without interaction - to resolve this we are going to show the play button overlay.' - ); - this.pixelStreaming.dispatchEvent( - new PlayStreamRejectedEvent({ - reason: onRejectedReason - }) - ); - }); + if (startMuted) { + this.playVideo(); + } else { + this.streamController.audioElement + .play() + .then(() => { + this.playVideo(); + }) + .catch((onRejectedReason) => { + Logger.Log(Logger.GetStackTrace(), onRejectedReason); + Logger.Log( + Logger.GetStackTrace(), + 'Browser does not support autoplaying video without interaction - to resolve this we are going to show the play button overlay.' + ); + this.pixelStreaming.dispatchEvent( + new PlayStreamRejectedEvent({ + reason: onRejectedReason + }) + ); + }); + } } else { this.playVideo(); } From 1e5d075d5fa500c24cb62e15de15cac907cca0c4 Mon Sep 17 00:00:00 2001 From: Matthew Cotton Date: Tue, 24 Oct 2023 11:42:30 +1100 Subject: [PATCH 12/21] Better handling of streamer ids. Specifically legacy ids. (cherry picked from commit 127feac2e44e5904f6f3752d7514fb50178fed59) --- SignallingWebServer/cirrus.js | 79 +++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/SignallingWebServer/cirrus.js b/SignallingWebServer/cirrus.js index 5dc2b03e..c4294b85 100644 --- a/SignallingWebServer/cirrus.js +++ b/SignallingWebServer/cirrus.js @@ -353,7 +353,8 @@ class Player { let streamers = new Map(); // streamerId <-> streamer socket let players = new Map(); // playerId <-> player, where player is either a web-browser or a native webrtc player const SFUPlayerId = "SFU"; -const LegacyStreamerId = "__LEGACY__"; // old streamers that dont know how to ID will be assigned this id. +const LegacyStreamerPrefix = "__LEGACY_STREAMER__"; // old streamers that dont know how to ID will be assigned this id prefix. +const streamerIdTimeoutSecs = 5; function sfuIsConnected() { const sfuPlayer = players.get(SFUPlayerId); @@ -401,30 +402,65 @@ function getPlayerIdFromMessage(msg) { return sanitizePlayerId(msg.playerId); } +function getUniqueLegacyId() { + for (let i = 0; i < 99; ++i) { + const testId = LegacyStreamerPrefix + i; + if (!streamers.has(testId)) { + return testId; + } + } + return ""; // no available id +} + +function requestStreamerId(streamer) { + // first we ask the streamer to id itself. + // if it doesnt reply within a time limit we assume it's an older streamer + // and assign it an id. + + // request id + const msg = { type: "identify" }; + logOutgoing(streamer.id, msg); + streamer.ws.send(JSON.stringify(msg)); + + streamer.idTimer = setTimeout(function() { + // streamer did not respond in time. give it a legacy id. + const newLegacyId = getUniqueLegacyId(); + if (newLegacyId.length == 0) { + const error = `Ran out of legacy ids.`; + console.error(error); + streamer.ws.close(1008, error); + } else { + registerStreamer(newLegacyId, streamer); + } + + }, streamerIdTimeoutSecs * 1000); +} + function registerStreamer(id, streamer) { streamer.id = id; streamers.set(streamer.id, streamer); + if (!!streamer.idTimer) { + clearTimeout(streamer.idTimer); + delete streamer.idTimer; + } + console.logColor(logging.Green, `Registered new streamer: ${streamer.id}`); } function onStreamerDisconnected(streamer) { - if (!streamer.id) { + if (!streamer.id || !streamers.has(streamer.id)) { return; } - if (!streamers.has(streamer.id)) { - console.error(`Disconnecting streamer ${streamer.id} does not exist.`); - } else { - sendStreamerDisconnectedToMatchmaker(); - let sfuPlayer = getSFU(); - if (sfuPlayer) { - const msg = { type: "streamerDisconnected" }; - logOutgoing(sfuPlayer.id, msg); - sfuPlayer.sendTo(msg); - disconnectAllPlayers(sfuPlayer.id); - } - disconnectAllPlayers(streamer.id); - streamers.delete(streamer.id); + sendStreamerDisconnectedToMatchmaker(); + let sfuPlayer = getSFU(); + if (sfuPlayer) { + const msg = { type: "streamerDisconnected" }; + logOutgoing(sfuPlayer.id, msg); + sfuPlayer.sendTo(msg); + disconnectAllPlayers(sfuPlayer.id); } + disconnectAllPlayers(streamer.id); + streamers.delete(streamer.id); } function onStreamerMessageId(streamer, msg) { @@ -438,9 +474,6 @@ function onStreamerMessageId(streamer, msg) { if (sfuPlayer) { sfuPlayer.subscribe(streamer.id); } - - // if any streamer id's assume the legacy streamer is not needed. - streamers.delete(LegacyStreamerId); } function onStreamerMessagePing(streamer, msg) { @@ -495,7 +528,7 @@ streamerServer.on('connection', function (ws, req) { console.logColor(logging.Green, `Streamer connected: ${req.connection.remoteAddress}`); sendStreamerConnectedToMatchmaker(); - let streamer = { ws: ws }; + let streamer = { id: req.connection.remoteAddress, ws: ws }; ws.on('message', (msgRaw) => { var msg; @@ -535,13 +568,7 @@ streamerServer.on('connection', function (ws, req) { }); ws.send(JSON.stringify(clientConfig)); - - // request id - const msg = { type: "identify" }; - logOutgoing("unknown", msg); - ws.send(JSON.stringify(msg)); - - registerStreamer(LegacyStreamerId, streamer); + requestStreamerId(streamer); }); function forwardSFUMessageToPlayer(msg) { From f8de08ab61a17973e2c2a9aba03c2579146dc241 Mon Sep 17 00:00:00 2001 From: Matthew Cotton Date: Tue, 24 Oct 2023 15:01:34 +1100 Subject: [PATCH 13/21] working on handling multiple sfus gracefully (cherry picked from commit 01d8056bee4eb910ad739f1cb5ed9feadc0ba2a0) --- SFU/config.js | 3 + SFU/sfu_server.js | 62 ++++++++++- SignallingWebServer/cirrus.js | 195 +++++++++++++++++++++++----------- 3 files changed, 197 insertions(+), 63 deletions(-) diff --git a/SFU/config.js b/SFU/config.js index 1387c88c..1820b037 100644 --- a/SFU/config.js +++ b/SFU/config.js @@ -12,6 +12,9 @@ for(let arg of process.argv){ const config = { signallingURL: "ws://localhost:8889", + SFUId: "SFU", + subscribeStreamerId: "DefaultStreamer", + retrySubscribeDelaySecs: 10, mediasoup: { worker: { diff --git a/SFU/sfu_server.js b/SFU/sfu_server.js index 3401395f..fe7cd88f 100644 --- a/SFU/sfu_server.js +++ b/SFU/sfu_server.js @@ -3,6 +3,10 @@ const WebSocket = require('ws'); const mediasoup = require('mediasoup_prebuilt'); const mediasoupSdp = require('mediasoup-sdp-bridge'); +if (!config.retrySubscribeDelaySecs) { + config.retrySubscribeDelaySecs = 10; +} + let signalServer = null; let mediasoupRouter; let streamer = null; @@ -11,7 +15,7 @@ let peers = new Map(); function connectSignalling(server) { console.log("Connecting to Signalling Server at %s", server); signalServer = new WebSocket(server); - signalServer.addEventListener("open", _ => { console.log(`Connected to signalling server`); }); + signalServer.addEventListener("open", _ => onSignallingConnected()); signalServer.addEventListener("error", result => { console.log(`Error: ${result.message}`); }); signalServer.addEventListener("message", result => onSignallingMessage(result.data)); signalServer.addEventListener("close", result => { @@ -24,6 +28,42 @@ function connectSignalling(server) { }); } +async function onSignallingConnected() { + console.log(`Connected to signalling server`); + //signalServer.send(JSON.stringify({type: 'listStreamers'})); +} + +async function onStreamerList(msg) { + let success = false; + + // subscribe to either the configured streamer, or if not configured, just grab the first id + if (msg.ids.length > 0) { + if (!!config.subscribeStreamerId) { + if (msg.ids.includes(config.subscribeStreamerId)) { + signalServer.send(JSON.stringify({type: 'subscribe', streamerId: config.subscribeStreamerId})); + success = true; + } + } else { + signalServer.send(JSON.stringify({type: 'subscribe', streamerId: msg.ids[0]})); + success = true; + } + } + + if (!success) { + // did not subscribe to anything + console.log(`No subscribe (${config.retrySubscribeDelaySecs}`) + setTimeout(function() { + signalServer.send(JSON.stringify({type: 'listStreamers'})); + }, config.retrySubscribeDelaySecs * 1000); + } +} + +async function onIdentify(msg) { + console.log(JSON.stringify({type: 'endpointId', id: config.SFUId})); + signalServer.send(JSON.stringify({type: 'endpointId', id: config.SFUId})); + signalServer.send(JSON.stringify({type: 'listStreamers'})); +} + async function onStreamerOffer(sdp) { console.log("Got offer from streamer"); @@ -228,7 +268,7 @@ function onLayerPreference(msg) { } async function onSignallingMessage(message) { - //console.log(`Got MSG: ${message}`); + console.log(`Got MSG: ${message}`); const msg = JSON.parse(message); if (msg.type == 'offer') { @@ -255,6 +295,14 @@ async function onSignallingMessage(message) { else if (msg.type == 'layerPreference') { onLayerPreference(msg); } + else if (msg.type == 'streamerList') { + console.log('WA WA WEE WOO ----------------------------------------------------------------------'); + onStreamerList(msg); + } + else if (msg.type == 'identify') { + console.log('identifying...'); + onIdentify(msg); + } } async function startMediasoup() { @@ -276,6 +324,14 @@ async function startMediasoup() { return mediasoupRouter; } +async function onICEStateChange(identifier, iceState) { + console.log("%s ICE state changed to %s", identifier, iceState); + + if (identifier == 'Streamer' && iceState == 'completed') { + signalServer.send(JSON.stringify({type: 'startStreaming'})); + } +} + async function createWebRtcTransport(identifier) { const { listenIps, @@ -291,7 +347,7 @@ async function createWebRtcTransport(identifier) { initialAvailableOutgoingBitrate: initialAvailableOutgoingBitrate }); - transport.on("icestatechange", (iceState) => { console.log("%s ICE state changed to %s", identifier, iceState); }); + transport.on("icestatechange", (iceState) => onICEStateChange(identifier, iceState)); transport.on("iceselectedtuplechange", (iceTuple) => { console.log("%s ICE selected tuple %s", identifier, JSON.stringify(iceTuple)); }); transport.on("sctpstatechange", (sctpState) => { console.log("%s SCTP state changed to %s", identifier, sctpState); }); diff --git a/SignallingWebServer/cirrus.js b/SignallingWebServer/cirrus.js index c4294b85..90eb64fc 100644 --- a/SignallingWebServer/cirrus.js +++ b/SignallingWebServer/cirrus.js @@ -304,6 +304,14 @@ class Player { return; } this.streamerId = streamerId; + if (this.type == PlayerType.SFU) { + let streamer = streamers.get(this.streamerId); + if (!!streamer.SFUId) { + console.error(`Streamer ${this.streamerId} already has an SFU (${streamer.SFUId}) but we're trying to register player ${this.id} as an SFU.`); + } else { + streamer.SFUId = this.id; + } + } const msg = { type: 'playerConnected', playerId: this.id, dataChannel: true, sfu: this.type == PlayerType.SFU, sendOffer: !this.browserSendOffer }; logOutgoing(this.streamerId, msg); this.sendFrom(msg); @@ -311,6 +319,14 @@ class Player { unsubscribe() { if (this.streamerId && streamers.has(this.streamerId)) { + if (this.type == PlayerType.SFU) { + let streamer = streamers.get(this.streamerId); + if (!streamer.SFUId || streamer.SFUId != this.id) { + console.error(`Trying to unsibscribe SFU player ${this.id} from streamer ${streamer.id} but the current SFUId does not match (${streamer.SFUId}).`) + } else { + delete streamer.SFUId; + } + } const msg = { type: 'playerDisconnected', playerId: this.id }; logOutgoing(this.streamerId, msg); this.sendFrom(msg); @@ -352,17 +368,18 @@ class Player { let streamers = new Map(); // streamerId <-> streamer socket let players = new Map(); // playerId <-> player, where player is either a web-browser or a native webrtc player -const SFUPlayerId = "SFU"; const LegacyStreamerPrefix = "__LEGACY_STREAMER__"; // old streamers that dont know how to ID will be assigned this id prefix. const streamerIdTimeoutSecs = 5; -function sfuIsConnected() { - const sfuPlayer = players.get(SFUPlayerId); - return sfuPlayer && sfuPlayer.ws && sfuPlayer.ws.readyState == 1; -} - -function getSFU() { - return players.get(SFUPlayerId); +function getSFUForStreamer(streamerId) { + if (!streamers.has(streamerId)) { + return null; + } + const streamer = streamers.get(streamerId); + if (!streamer.SFUId) { + return null; + } + return players.get(streamer.SFUId); } function logIncoming(sourceName, msg) { @@ -412,6 +429,25 @@ function getUniqueLegacyId() { return ""; // no available id } +function getUniqueSFUId() { + for (let i = 0; i < 99; ++i) { + const testId = SFUStreamerPrefix + i; + let available = true; + for (let player of players) { + if (player.type == PlayerType.SFU) { + if (player.streamer.id == testId) { + available = false; + break; + } + } + } + if (available) { + return testId; + } + } + return ""; // no available id +} + function requestStreamerId(streamer) { // first we ask the streamer to id itself. // if it doesnt reply within a time limit we assume it's an older streamer @@ -452,7 +488,7 @@ function onStreamerDisconnected(streamer) { } sendStreamerDisconnectedToMatchmaker(); - let sfuPlayer = getSFU(); + let sfuPlayer = getSFUForStreamer(streamer.id); if (sfuPlayer) { const msg = { type: "streamerDisconnected" }; logOutgoing(sfuPlayer.id, msg); @@ -468,12 +504,6 @@ function onStreamerMessageId(streamer, msg) { let streamerId = msg.id; registerStreamer(streamerId, streamer); - - // subscribe any sfu to the latest connected streamer - const sfuPlayer = getSFU(); - if (sfuPlayer) { - sfuPlayer.subscribe(streamer.id); - } } function onStreamerMessagePing(streamer, msg) { @@ -494,7 +524,7 @@ function onStreamerMessageDisconnectPlayer(streamer, msg) { } function onStreamerMessageLayerPreference(streamer, msg) { - let sfuPlayer = getSFU(); + let sfuPlayer = getSFUForStreamer(streamer.id); if (sfuPlayer) { logOutgoing(sfuPlayer.id, msg); sfuPlayer.sendTo(msg); @@ -571,60 +601,109 @@ streamerServer.on('connection', function (ws, req) { requestStreamerId(streamer); }); -function forwardSFUMessageToPlayer(msg) { +function forwardSFUMessageToPlayer(sfuPlayer, msg) { const playerId = getPlayerIdFromMessage(msg); const player = players.get(playerId); if (player) { - logForward(SFUPlayerId, playerId, msg); + logForward(sfuPlayer.streamer.id, playerId, msg); player.sendTo(msg); } } -function forwardSFUMessageToStreamer(msg) { - const sfuPlayer = getSFU(); - if (sfuPlayer) { - logForward(SFUPlayerId, sfuPlayer.streamerId, msg); - msg.sfuId = SFUPlayerId; - sfuPlayer.sendFrom(msg); - } +function forwardSFUMessageToStreamer(sfuPlayer, msg) { + logForward(sfuPlayer.streamer.id, sfuPlayer.streamerId, msg); + msg.sfuId = sfuPlayer.id; + sfuPlayer.sendFrom(msg); } -function onPeerDataChannelsSFUMessage(msg) { +function onPeerDataChannelsSFUMessage(sfuPlayer, msg) { // sfu is telling a peer what stream id to use for a data channel const playerId = getPlayerIdFromMessage(msg); const player = players.get(playerId); if (player) { - logForward(SFUPlayerId, playerId, msg); + logForward(sfuPlayer.streamer.id, playerId, msg); player.sendTo(msg); player.datachannel = true; } } -function onSFUDisconnected() { - console.log("disconnecting SFU from streamer"); - disconnectAllPlayers(SFUPlayerId); - const sfuPlayer = getSFU(); - if (sfuPlayer) { - sfuPlayer.unsubscribe(); - sfuPlayer.ws.close(4000, "SFU Disconnected"); +// basically a duplicate of the streamer id request but this one does not register the streamer +function requestSFUStreamerId(sfuPlayer) { + // request id + const msg = { type: "identify" }; + logOutgoing(sfuPlayer.streamer.id, msg); + sfuPlayer.streamer.ws.send(JSON.stringify(msg)); + + sfuPlayer.streamer.idTimer = setTimeout(function() { + // streamer did not respond in time. give it a legacy id. + const newLegacyId = getUniqueSFUId(); + if (newLegacyId.length == 0) { + const error = `Ran out of legacy ids.`; + console.error(error); + sfuPlayer.ws.close(1008, error); + } else { + sfuPlayer.streamer.id = newLegacyId; + } + }, streamerIdTimeoutSecs * 1000); +} + +function onSFUMessageId(sfuPlayer, msg) { + logIncoming(sfuPlayer.streamer.id, msg); + sfuPlayer.streamer.id = msg.id; + + if (!!sfuPlayer.streamer.idTimer) { + clearTimeout(sfuPlayer.streamer.idTimer); + delete sfuPlayer.streamer.idTimer; } - players.delete(SFUPlayerId); - streamers.delete(SFUPlayerId); } +function onSFUMessageStartStreaming(sfuPlayer, msg) { + if (streamers.has(sfuPlayer.streamer.id)) { + console.error(`SFU ${sfuPlayer.streamer.id} is already registered as a streamer and streaming.`) + return; + } + + registerStreamer(sfuPlayer.streamer.id, sfuPlayer.streamer); +} + +function onSFUMessageStopStreaming(sfuPlayer, msg) { +if (!streamers.has(sfuPlayer.streamer.id)) { + console.error(`SFU ${sfuPlayer.streamer.id} is not registered as a streamer or streaming.`) + return; + } + + onStreamerDisconnected(sfuPlayer.streamer); +} + +function onSFUDisconnected(sfuPlayer) { + console.log("disconnecting SFU from streamer"); + disconnectAllPlayers(sfuPlayer.id); + sfuPlayer.unsubscribe(); + sfuPlayer.ws.close(4000, "SFU Disconnected"); + players.delete(sfuPlayer.id); + streamers.delete(sfuPlayer.id); +} + +sfuMessageHandlers.set('listStreamers', onPlayerMessageListStreamers); +sfuMessageHandlers.set('subscribe', onPlayerMessageSubscribe); +sfuMessageHandlers.set('unsubscribe', onPlayerMessageUnsubscribe); sfuMessageHandlers.set('offer', forwardSFUMessageToPlayer); sfuMessageHandlers.set('answer', forwardSFUMessageToStreamer); sfuMessageHandlers.set('streamerDataChannels', forwardSFUMessageToStreamer); sfuMessageHandlers.set('peerDataChannels', onPeerDataChannelsSFUMessage); +sfuMessageHandlers.set('endpointId', onSFUMessageId); +sfuMessageHandlers.set('startStreaming', onSFUMessageStartStreaming); +sfuMessageHandlers.set('stopStreaming', onSFUMessageStopStreaming); console.logColor(logging.Green, `WebSocket listening for SFU connections on :${sfuPort}`); let sfuServer = new WebSocket.Server({ port: sfuPort }); sfuServer.on('connection', function (ws, req) { - // reject if we already have an sfu - if (sfuIsConnected()) { - ws.close(1013, 'Already have an SFU'); - return; - } + + let playerId = sanitizePlayerId(nextPlayerId++); + console.logColor(logging.Green, `SFU (${req.connection.remoteAddress}) connected `); + let player = new Player(playerId, ws, PlayerType.SFU, false); + player.streamer = { id: req.connection.remoteAddress, ws: ws }; // SFU also has a streamer component + players.set(playerId, player); ws.on('message', (msgRaw) => { var msg; @@ -636,26 +715,33 @@ sfuServer.on('connection', function (ws, req) { return; } + let sfuPlayer = players.get(playerId); + if (!sfuPlayer) { + console.error(`Received a message from an SFU not in the player list ${playerId}`); + ws.close(1001, 'Broken'); + return; + } + let handler = sfuMessageHandlers.get(msg.type); if (!handler || (typeof handler != 'function')) { if (config.LogVerbose) { - console.logColor(logging.White, "\x1b[37m-> %s\x1b[34m: %s", SFUPlayerId, msgRaw); + console.logColor(logging.White, "\x1b[37m-> %s\x1b[34m: %s", sfuPlayer.id, msgRaw); } console.error(`unsupported SFU message type: ${msg.type}`); ws.close(1008, 'Unsupported message type'); return; } - handler(msg); + handler(sfuPlayer, msg); }); ws.on('close', function(code, reason) { console.error(`SFU disconnected: ${code} - ${reason}`); - onSFUDisconnected(); + onSFUDisconnected(player); }); ws.on('error', function(error) { console.error(`SFU connection error: ${error}`); - onSFUDisconnected(); + onSFUDisconnected(player); try { ws.close(1006 /* abnormal closure */, error); } catch(err) { @@ -663,18 +749,7 @@ sfuServer.on('connection', function (ws, req) { } }); - let sfuPlayer = new Player(SFUPlayerId, ws, PlayerType.SFU, false); - players.set(SFUPlayerId, sfuPlayer); - console.logColor(logging.Green, `SFU (${req.connection.remoteAddress}) connected `); - - // TODO subscribe it to one of any of the streamers for now - for (let [streamerId, streamer] of streamers) { - sfuPlayer.subscribe(streamerId); - break; - } - - // sfu also acts as a streamer - registerStreamer(SFUPlayerId, { ws: ws }); + requestStreamerId(player.streamer); }); let playerCount = 0; @@ -815,9 +890,9 @@ function disconnectAllPlayers(streamerId) { for (let player of clone.values()) { if (player.streamerId == streamerId) { // disconnect players but just unsubscribe the SFU - if (player.id == SFUPlayerId) { - // because we're working on a clone here we have to access directly - getSFU().unsubscribe(); + const sfuPlayer = getSFUForStreamer(streamerId); + if (player.id == sfuPlayer.id) { + sfuPlayer.unsubscribe(); } else { player.ws.close(); } From ddb4c4776e27cd863f7f0cf74bfbfba76760b7ec Mon Sep 17 00:00:00 2001 From: Matthew Cotton Date: Wed, 25 Oct 2023 11:49:55 +1100 Subject: [PATCH 14/21] Allowing SFU to work with multiple streamers. (cherry picked from commit c0e715ca9dcfef46d8490cde42d037347edbcffb) --- .../implementations/typescript/package-lock.json | 4 ++-- .../src/WebRtcPlayer/WebRtcPlayerController.ts | 7 ++++++- SFU/config.js | 7 +++++++ SFU/sfu_server.js | 14 +++++++------- SignallingWebServer/cirrus.js | 5 ++++- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Frontend/implementations/typescript/package-lock.json b/Frontend/implementations/typescript/package-lock.json index c9f2fffb..94705322 100644 --- a/Frontend/implementations/typescript/package-lock.json +++ b/Frontend/implementations/typescript/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@epicgames-ps/reference-pixelstreamingfrontend-ue5.3", + "name": "@epicgames-ps/reference-pixelstreamingfrontend-ue5.4", "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "@epicgames-ps/reference-pixelstreamingfrontend-ue5.3", + "name": "@epicgames-ps/reference-pixelstreamingfrontend-ue5.4", "version": "0.0.1", "devDependencies": { "css-loader": "^6.7.3", diff --git a/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts b/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts index b18b31b1..3f1601d0 100644 --- a/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts +++ b/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts @@ -1398,6 +1398,10 @@ export class WebRtcPlayerController { ) { // If there's a streamer ID in the URL and a streamer with this ID is connected, set it as the selected streamer autoSelectedStreamerId = urlParams.get(OptionParameters.StreamerId); + } else if (messageStreamerList.ids.length > 0 && this.config.isFlagEnabled(Flags.WaitForStreamer)) { + // we're waiting for a streamer and there are multiple connected but none were auto selected + // select the first + autoSelectedStreamerId = messageStreamerList.ids[0]; } if (autoSelectedStreamerId !== null) { this.config.setOptionSettingValue( @@ -1407,7 +1411,8 @@ export class WebRtcPlayerController { } else { // no auto selected streamer if (this.config.isFlagEnabled(Flags.WaitForStreamer)) { - this.startAutoJoinTimer() + this.closeSignalingServer(); + this.startAutoJoinTimer(); } } this.pixelStreaming.dispatchEvent( diff --git a/SFU/config.js b/SFU/config.js index 1820b037..fbf11ff8 100644 --- a/SFU/config.js +++ b/SFU/config.js @@ -11,9 +11,16 @@ for(let arg of process.argv){ } const config = { + // The URL of the signalling server to connect to signallingURL: "ws://localhost:8889", + + // The ID for this SFU to use. This will show up as a streamer ID on the signalling server SFUId: "SFU", + + // The ID of the streamer to subscribe to. If you leave this blank it will subscribe to the first streamer it sees. subscribeStreamerId: "DefaultStreamer", + + // Delay between list requests when looking for a specifc streamer. retrySubscribeDelaySecs: 10, mediasoup: { diff --git a/SFU/sfu_server.js b/SFU/sfu_server.js index fe7cd88f..5201c298 100644 --- a/SFU/sfu_server.js +++ b/SFU/sfu_server.js @@ -30,7 +30,6 @@ function connectSignalling(server) { async function onSignallingConnected() { console.log(`Connected to signalling server`); - //signalServer.send(JSON.stringify({type: 'listStreamers'})); } async function onStreamerList(msg) { @@ -38,7 +37,7 @@ async function onStreamerList(msg) { // subscribe to either the configured streamer, or if not configured, just grab the first id if (msg.ids.length > 0) { - if (!!config.subscribeStreamerId) { + if (!!config.subscribeStreamerId && config.subscribeStreamerId.length != 0) { if (msg.ids.includes(config.subscribeStreamerId)) { signalServer.send(JSON.stringify({type: 'subscribe', streamerId: config.subscribeStreamerId})); success = true; @@ -51,7 +50,6 @@ async function onStreamerList(msg) { if (!success) { // did not subscribe to anything - console.log(`No subscribe (${config.retrySubscribeDelaySecs}`) setTimeout(function() { signalServer.send(JSON.stringify({type: 'listStreamers'})); }, config.retrySubscribeDelaySecs * 1000); @@ -59,7 +57,6 @@ async function onStreamerList(msg) { } async function onIdentify(msg) { - console.log(JSON.stringify({type: 'endpointId', id: config.SFUId})); signalServer.send(JSON.stringify({type: 'endpointId', id: config.SFUId})); signalServer.send(JSON.stringify({type: 'listStreamers'})); } @@ -97,6 +94,11 @@ function onStreamerDisconnected() { } streamer.transport.close(); streamer = null; + signalServer.send(JSON.stringify({type: 'stopStreaming'})); + + setTimeout(function() { + signalServer.send(JSON.stringify({type: 'listStreamers'})); + }, config.retrySubscribeDelaySecs * 1000); } } @@ -268,7 +270,7 @@ function onLayerPreference(msg) { } async function onSignallingMessage(message) { - console.log(`Got MSG: ${message}`); + //console.log(`Got MSG: ${message}`); const msg = JSON.parse(message); if (msg.type == 'offer') { @@ -296,11 +298,9 @@ async function onSignallingMessage(message) { onLayerPreference(msg); } else if (msg.type == 'streamerList') { - console.log('WA WA WEE WOO ----------------------------------------------------------------------'); onStreamerList(msg); } else if (msg.type == 'identify') { - console.log('identifying...'); onIdentify(msg); } } diff --git a/SignallingWebServer/cirrus.js b/SignallingWebServer/cirrus.js index 90eb64fc..847c6b3d 100644 --- a/SignallingWebServer/cirrus.js +++ b/SignallingWebServer/cirrus.js @@ -658,6 +658,7 @@ function onSFUMessageId(sfuPlayer, msg) { } function onSFUMessageStartStreaming(sfuPlayer, msg) { + logIncoming(sfuPlayer.streamer.id, msg); if (streamers.has(sfuPlayer.streamer.id)) { console.error(`SFU ${sfuPlayer.streamer.id} is already registered as a streamer and streaming.`) return; @@ -667,6 +668,7 @@ function onSFUMessageStartStreaming(sfuPlayer, msg) { } function onSFUMessageStopStreaming(sfuPlayer, msg) { + logIncoming(sfuPlayer.streamer.id, msg); if (!streamers.has(sfuPlayer.streamer.id)) { console.error(`SFU ${sfuPlayer.streamer.id} is not registered as a streamer or streaming.`) return; @@ -678,6 +680,7 @@ if (!streamers.has(sfuPlayer.streamer.id)) { function onSFUDisconnected(sfuPlayer) { console.log("disconnecting SFU from streamer"); disconnectAllPlayers(sfuPlayer.id); + onStreamerDisconnected(sfuPlayer.streamer); sfuPlayer.unsubscribe(); sfuPlayer.ws.close(4000, "SFU Disconnected"); players.delete(sfuPlayer.id); @@ -891,7 +894,7 @@ function disconnectAllPlayers(streamerId) { if (player.streamerId == streamerId) { // disconnect players but just unsubscribe the SFU const sfuPlayer = getSFUForStreamer(streamerId); - if (player.id == sfuPlayer.id) { + if (sfuPlayer && player.id == sfuPlayer.id) { sfuPlayer.unsubscribe(); } else { player.ws.close(); From 48b256753a09ba3c006dd2fdf7739bce1c360f4d Mon Sep 17 00:00:00 2001 From: Matthew Cotton Date: Wed, 25 Oct 2023 11:50:33 +1100 Subject: [PATCH 15/21] Fixing the windows build script nuking the PATH env variable. (cherry picked from commit 090cc89b08d94505f639718b966561bb32f4482a) --- .../platform_scripts/cmd/refreshenv.cmd | 66 ------------------ .../platform_scripts/cmd/setenv/License.txt | 24 ------- .../platform_scripts/cmd/setenv/ReadMe.txt | 46 ------------ .../platform_scripts/cmd/setenv/SetEnv.exe | Bin 126976 -> 0 bytes .../platform_scripts/cmd/setup_frontend.bat | 12 ++-- 5 files changed, 6 insertions(+), 142 deletions(-) delete mode 100644 SignallingWebServer/platform_scripts/cmd/refreshenv.cmd delete mode 100644 SignallingWebServer/platform_scripts/cmd/setenv/License.txt delete mode 100644 SignallingWebServer/platform_scripts/cmd/setenv/ReadMe.txt delete mode 100644 SignallingWebServer/platform_scripts/cmd/setenv/SetEnv.exe diff --git a/SignallingWebServer/platform_scripts/cmd/refreshenv.cmd b/SignallingWebServer/platform_scripts/cmd/refreshenv.cmd deleted file mode 100644 index e0a272c0..00000000 --- a/SignallingWebServer/platform_scripts/cmd/refreshenv.cmd +++ /dev/null @@ -1,66 +0,0 @@ -:: -:: RefreshEnv.cmd -:: -:: Batch file to read environment variables from registry and -:: set session variables to these values. -:: -:: With this batch file, there should be no need to reload command -:: environment every time you want environment changes to propagate - -::echo "RefreshEnv.cmd only works from cmd.exe, please install the Chocolatey Profile to take advantage of refreshenv from PowerShell" -echo | set /p dummy="Refreshing environment variables from registry for cmd.exe. Please wait..." - -goto main - -:: Set one environment variable from registry key -:SetFromReg - "%WinDir%\System32\Reg" QUERY "%~1" /v "%~2" > "%TEMP%\_envset.tmp" 2>NUL - for /f "usebackq skip=2 tokens=2,*" %%A IN ("%TEMP%\_envset.tmp") do ( - echo/set "%~3=%%B" - ) - goto :EOF - -:: Get a list of environment variables from registry -:GetRegEnv - "%WinDir%\System32\Reg" QUERY "%~1" > "%TEMP%\_envget.tmp" - for /f "usebackq skip=2" %%A IN ("%TEMP%\_envget.tmp") do ( - if /I not "%%~A"=="Path" ( - call :SetFromReg "%~1" "%%~A" "%%~A" - ) - ) - goto :EOF - -:main - echo/@echo off >"%TEMP%\_env.cmd" - - :: Slowly generating final file - call :GetRegEnv "HKLM\System\CurrentControlSet\Control\Session Manager\Environment" >> "%TEMP%\_env.cmd" - call :GetRegEnv "HKCU\Environment">>"%TEMP%\_env.cmd" >> "%TEMP%\_env.cmd" - - :: Special handling for PATH - mix both User and System - call :SetFromReg "HKLM\System\CurrentControlSet\Control\Session Manager\Environment" Path Path_HKLM >> "%TEMP%\_env.cmd" - call :SetFromReg "HKCU\Environment" Path Path_HKCU >> "%TEMP%\_env.cmd" - - :: Caution: do not insert space-chars before >> redirection sign - echo/set "Path=%%Path_HKLM%%;%%Path_HKCU%%" >> "%TEMP%\_env.cmd" - - :: Cleanup - del /f /q "%TEMP%\_envset.tmp" 2>nul - del /f /q "%TEMP%\_envget.tmp" 2>nul - - :: capture user / architecture - SET "OriginalUserName=%USERNAME%" - SET "OriginalArchitecture=%PROCESSOR_ARCHITECTURE%" - - :: Set these variables - call "%TEMP%\_env.cmd" - - :: Cleanup - del /f /q "%TEMP%\_env.cmd" 2>nul - - :: reset user / architecture - SET "USERNAME=%OriginalUserName%" - SET "PROCESSOR_ARCHITECTURE=%OriginalArchitecture%" - - echo | set /p dummy="Finished." - echo ... \ No newline at end of file diff --git a/SignallingWebServer/platform_scripts/cmd/setenv/License.txt b/SignallingWebServer/platform_scripts/cmd/setenv/License.txt deleted file mode 100644 index ff66d6bc..00000000 --- a/SignallingWebServer/platform_scripts/cmd/setenv/License.txt +++ /dev/null @@ -1,24 +0,0 @@ -License -------- - -Copyright (C) 1999-2008 - Jonathan Wilkes -http://www.xanya.net - -Installing and using this software (or source code) signifies acceptance of these terms and the conditions of the license. -This license applies to everything in this package (Including any supplied Source Code), except where otherwise noted. - -License Agreement ------------------ - -This software is provided 'as-is', without any express or implied warranty. -In no event will the author be held liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software/source code. -(If you use the supplied source code (if any) in a product, then an acknowledgment in the product documentation would be appreciated but is not required.) - -2. If you have downloaded the Source Code for this application (where available) then altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any distribution of the software. -(If you use the supplied source code (if any) in a product, including commercial applications, then you do NOT need to distribute this license with your product.) diff --git a/SignallingWebServer/platform_scripts/cmd/setenv/ReadMe.txt b/SignallingWebServer/platform_scripts/cmd/setenv/ReadMe.txt deleted file mode 100644 index dc193079..00000000 --- a/SignallingWebServer/platform_scripts/cmd/setenv/ReadMe.txt +++ /dev/null @@ -1,46 +0,0 @@ - -SetEnv -Version 1.09 - ( For Windows 9x/NT/2000/XP/S2K3/Vista ) - -Copyright (C) 2005-2008 - Jonathan Wilkes - All Rights Reserved. -http://www.xanya.net - -================================================================================ - -1. Installation - - Simply download and run the Setup_SetEnv.exe application to install SetEnv. - -2. Using SetEnv - - The SetEnv is a free tool for setting/updating/deleting System Environment Variables. - Type the following at a command prompt (assumes SetEnv.exe is in current path), for command line usage information. - - setenv -? - - See our website for full usage details, http://www.xanya.net/site/utils/setenv.php - -3. Version History - - 1.09 [Fix] - (Feb 9, 2008) - Fixed a problem on Windows 98 where it sometimes failed to open the Autoexec.bat file. - 1.08 [New] - (May 31, 2007) - Added how to delete a USER environment variable to the usage information. - 1.07 [Fix] - (Jan 25, 2007) - Fixed a bug found by depaolim. - 1.06 [New] - (Jan 14, 2007) - Added dynamic expansion support (same as using ~ with setx) - - Originally requested by Andre Amaral, further Request by Synetech - 1.05 [New] - (Sep 06, 2006) - Added support to prepend (rather than append) a value to an expanded string - - Requested by Masuia - 1.04 [New] - (May 30, 2006) - Added support for User environment variables. - 1.03 [Fix] - (Apr 20, 2006) - Bug fix in ProcessWinXP() discovered by attiasr - 1.01 [Fix] - (Nov 15, 2005) - Bug fix in IsWinME() discovered by frankd - 1.00 [New] - (Oct 29, 2005) - Initial Public Release. - -4. License and Terms of Use - - Please see the License.txt file for licensing information. - -5. Reporting Problems - - If you encounter any problems whilst using SetEnv, please try downloading the latest version from http://www.xanya.net to see if the problem has already been resolved. - If this does not help, then please send an e-mail to darka@xanya.net with details describing the problem. - -================================================================================ \ No newline at end of file diff --git a/SignallingWebServer/platform_scripts/cmd/setenv/SetEnv.exe b/SignallingWebServer/platform_scripts/cmd/setenv/SetEnv.exe deleted file mode 100644 index b1d5d5554ef2c5896cdf54b94c3b1b971cbba002..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 126976 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 Tqt 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
  • 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 Date: Wed, 25 Oct 2023 11:59:45 +1100 Subject: [PATCH 16/21] Just some small cleanup (cherry picked from commit 2ce53023ee98d8aff851caa5f8f8f22c77a85b7d) --- SFU/sfu_server.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/SFU/sfu_server.js b/SFU/sfu_server.js index 5201c298..cc26cfa2 100644 --- a/SFU/sfu_server.js +++ b/SFU/sfu_server.js @@ -15,7 +15,7 @@ let peers = new Map(); function connectSignalling(server) { console.log("Connecting to Signalling Server at %s", server); signalServer = new WebSocket(server); - signalServer.addEventListener("open", _ => onSignallingConnected()); + signalServer.addEventListener("open", _ => { console.log(`Connected to signalling server`); }); signalServer.addEventListener("error", result => { console.log(`Error: ${result.message}`); }); signalServer.addEventListener("message", result => onSignallingMessage(result.data)); signalServer.addEventListener("close", result => { @@ -28,10 +28,6 @@ function connectSignalling(server) { }); } -async function onSignallingConnected() { - console.log(`Connected to signalling server`); -} - async function onStreamerList(msg) { let success = false; From 78d68a8a8b1bbe6168fe02dccf28844aad59d177 Mon Sep 17 00:00:00 2001 From: Matthew Cotton Date: Thu, 26 Oct 2023 10:01:31 +1100 Subject: [PATCH 17/21] Removing PreferSFU option since this is now handled with the stream selection option. Fixing browser behaviour when multiple streamers detected (previous failed tests). (cherry picked from commit bbcfe8a6b572648fac2a9c21389cc8c254345da5) --- Frontend/library/src/Config/Config.ts | 12 ------------ .../src/WebRtcPlayer/WebRtcPlayerController.ts | 12 +----------- Frontend/ui-library/src/Config/ConfigUI.ts | 4 ---- 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/Frontend/library/src/Config/Config.ts b/Frontend/library/src/Config/Config.ts index 8f2c8e7d..b4892b6a 100644 --- a/Frontend/library/src/Config/Config.ts +++ b/Frontend/library/src/Config/Config.ts @@ -23,7 +23,6 @@ export class Flags { static FakeMouseWithTouches = 'FakeMouseWithTouches' as const; static IsQualityController = 'ControlsQuality' as const; static MatchViewportResolution = 'MatchViewportRes' as const; - static PreferSFU = 'preferSFU' as const; static StartVideoMuted = 'StartVideoMuted' as const; static SuppressBrowserKeys = 'SuppressBrowserKeys' as const; static UseMic = 'UseMic' as const; @@ -315,17 +314,6 @@ export class Config { ) ); - this.flags.set( - Flags.PreferSFU, - new SettingFlag( - Flags.PreferSFU, - 'Prefer SFU', - 'Try to connect to the SFU instead of P2P.', - false, - useUrlParams - ) - ); - this.flags.set( Flags.IsQualityController, new SettingFlag( diff --git a/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts b/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts index 3f1601d0..f45a6aeb 100644 --- a/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts +++ b/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts @@ -1384,12 +1384,6 @@ export class WebRtcPlayerController { if (messageStreamerList.ids.length == 1) { // If there's only a single streamer, subscribe to it regardless of what is in the URL autoSelectedStreamerId = messageStreamerList.ids[0]; - } else if ( - this.config.isFlagEnabled(Flags.PreferSFU) && - messageStreamerList.ids.includes('SFU') - ) { - // If the SFU toggle is on and there's an SFU connected, subscribe to it regardless of what is in the URL - autoSelectedStreamerId = 'SFU'; } else if ( urlParams.has(OptionParameters.StreamerId) && messageStreamerList.ids.includes( @@ -1398,10 +1392,6 @@ export class WebRtcPlayerController { ) { // If there's a streamer ID in the URL and a streamer with this ID is connected, set it as the selected streamer autoSelectedStreamerId = urlParams.get(OptionParameters.StreamerId); - } else if (messageStreamerList.ids.length > 0 && this.config.isFlagEnabled(Flags.WaitForStreamer)) { - // we're waiting for a streamer and there are multiple connected but none were auto selected - // select the first - autoSelectedStreamerId = messageStreamerList.ids[0]; } if (autoSelectedStreamerId !== null) { this.config.setOptionSettingValue( @@ -1410,7 +1400,7 @@ export class WebRtcPlayerController { ); } else { // no auto selected streamer - if (this.config.isFlagEnabled(Flags.WaitForStreamer)) { + if (messageStreamerList.ids.length == 0 && this.config.isFlagEnabled(Flags.WaitForStreamer)) { this.closeSignalingServer(); this.startAutoJoinTimer(); } diff --git a/Frontend/ui-library/src/Config/ConfigUI.ts b/Frontend/ui-library/src/Config/ConfigUI.ts index f4ea65bd..e28c1aff 100644 --- a/Frontend/ui-library/src/Config/ConfigUI.ts +++ b/Frontend/ui-library/src/Config/ConfigUI.ts @@ -174,10 +174,6 @@ export class ConfigUI { psSettingsSection, this.flagsUi.get(Flags.StartVideoMuted) ); - this.addSettingFlag( - psSettingsSection, - this.flagsUi.get(Flags.PreferSFU) - ); this.addSettingFlag( psSettingsSection, this.flagsUi.get(Flags.IsQualityController) From 63f8a320e14b8622e502722f9ea65910253101c8 Mon Sep 17 00:00:00 2001 From: Matthew Cotton Date: Fri, 27 Oct 2023 15:15:41 +1100 Subject: [PATCH 18/21] Cleanup and fixing sfu behaviour. (cherry picked from commit adfca6c42d9fd2d40376a4519ae2ec8fd5aeb234) --- SignallingWebServer/cirrus.js | 209 ++++++++++++++++++++++++++-------- 1 file changed, 162 insertions(+), 47 deletions(-) diff --git a/SignallingWebServer/cirrus.js b/SignallingWebServer/cirrus.js index 847c6b3d..24decda1 100644 --- a/SignallingWebServer/cirrus.js +++ b/SignallingWebServer/cirrus.js @@ -288,14 +288,83 @@ console.logColor(logging.Cyan, `Running Cirrus - The Pixel Streaming reference i let nextPlayerId = 1; +const StreamerType = { Regular: 0, SFU: 1 }; + +class Streamer { + constructor(initialId, ws, type) { + this.id = initialId; + this.ws = ws; + this.type = type; + this.idCommitted = false; + } + + // registers this streamers id + commitId(id) { + this.id = id; + this.idCommitted = true; + } + + // returns true if we have a valid id + isIdCommitted() { + return this.idCommitted; + } + + // links this streamer to a subscribed SFU player (player component of an SFU) + addSFUPlayer(sfuPlayerId) { + if (!!this.SFUPlayerId && this.SFUPlayerId != sfuPlayerId) { + console.error(`Streamer ${this.id} already has an SFU ${this.SFUPlayerId}. Trying to add ${sfuPlayerId} as SFU.`); + return; + } + this.SFUPlayerId = sfuPlayerId; + } + + // removes the previously subscribed SFU player + removeSFUPlayer() { + delete this.SFUPlayerId; + } + + // gets the player id of the subscribed SFU if any + getSFUPlayerId() { + return this.SFUPlayerId; + } + + // returns true if this streamer is forwarding another streamer + isSFU() { + return this.type == StreamerType.SFU; + } + + // links this streamer to a player, used for SFU connections since they have both components + setSFUPlayerComponent(playerComponent) { + if (!this.isSFU()) { + console.error(`Trying to add an SFU player component ${playerComponent.id} to streamer ${this.id} but it is not an SFU type.`); + return; + } + this.sfuPlayerComponent = playerComponent; + } + + // gets the player component for this sfu + getSFUPlayerComponent() { + if (!this.isSFU()) { + console.error(`Trying to get an SFU player component from streamer ${this.id} but it is not an SFU type.`); + return null; + } + return this.sfuPlayerComponent; + } +} + const PlayerType = { Regular: 0, SFU: 1 }; +const WhoSendsOffer = { Streamer: 0, Browser: 1 }; class Player { - constructor(id, ws, type, browserSendOffer) { + constructor(id, ws, type, whoSendsOffer) { this.id = id; this.ws = ws; this.type = type; - this.browserSendOffer = browserSendOffer; + this.whoSendsOffer = whoSendsOffer; + } + + isSFU() { + return this.type == PlayerType.SFU; } subscribe(streamerId) { @@ -306,13 +375,9 @@ class Player { this.streamerId = streamerId; if (this.type == PlayerType.SFU) { let streamer = streamers.get(this.streamerId); - if (!!streamer.SFUId) { - console.error(`Streamer ${this.streamerId} already has an SFU (${streamer.SFUId}) but we're trying to register player ${this.id} as an SFU.`); - } else { - streamer.SFUId = this.id; - } + streamer.addSFUPlayer(this.id); } - const msg = { type: 'playerConnected', playerId: this.id, dataChannel: true, sfu: this.type == PlayerType.SFU, sendOffer: !this.browserSendOffer }; + const msg = { type: 'playerConnected', playerId: this.id, dataChannel: true, sfu: this.type == PlayerType.SFU, sendOffer: this.whoSendsOffer == WhoSendsOffer.Streamer }; logOutgoing(this.streamerId, msg); this.sendFrom(msg); } @@ -321,10 +386,10 @@ class Player { if (this.streamerId && streamers.has(this.streamerId)) { if (this.type == PlayerType.SFU) { let streamer = streamers.get(this.streamerId); - if (!streamer.SFUId || streamer.SFUId != this.id) { - console.error(`Trying to unsibscribe SFU player ${this.id} from streamer ${streamer.id} but the current SFUId does not match (${streamer.SFUId}).`) + if (streamer.getSFUPlayerId() != this.id) { + console.error(`Trying to unsibscribe SFU player ${this.id} from streamer ${streamer.id} but the current SFUId does not match (${streamer.getSFUPlayerId()}).`) } else { - delete streamer.SFUId; + streamer.removeSFUPlayer(); } } const msg = { type: 'playerDisconnected', playerId: this.id }; @@ -364,22 +429,40 @@ class Player { const msgString = JSON.stringify(message); this.ws.send(msgString); } + + setSFUStreamerComponent(streamerComponent) { + if (!this.isSFU()) { + console.error(`Trying to add an SFU streamer component ${streamerComponent.id} to player ${this.id} but it is not an SFU type.`); + return; + } + this.sfuStreamerComponent = streamerComponent; + } + + getSFUStreamerComponent() { + if (!this.isSFU()) { + console.error(`Trying to get an SFU streamer component from player ${this.id} but it is not an SFU type.`); + return null; + } + return this.sfuStreamerComponent; + } }; -let streamers = new Map(); // streamerId <-> streamer socket -let players = new Map(); // playerId <-> player, where player is either a web-browser or a native webrtc player +let streamers = new Map(); // streamerId <-> streamer +let players = new Map(); // playerId <-> player/peer/viewer const LegacyStreamerPrefix = "__LEGACY_STREAMER__"; // old streamers that dont know how to ID will be assigned this id prefix. const streamerIdTimeoutSecs = 5; +// gets the SFU subscribed to this streamer if any. function getSFUForStreamer(streamerId) { if (!streamers.has(streamerId)) { return null; } const streamer = streamers.get(streamerId); - if (!streamer.SFUId) { + const sfuPlayerId = streamer.getSFUPlayerId(); + if (!!sfuPlayerId) { return null; } - return players.get(streamer.SFUId); + return players.get(sfuPlayerId); } function logIncoming(sourceName, msg) { @@ -472,13 +555,34 @@ function requestStreamerId(streamer) { }, streamerIdTimeoutSecs * 1000); } +function sanitizeStreamerId(id) { + let maxPostfix = -1; + for (let [streamerId, streamer] of streamers) { + const idMatchRegex = /^(.*?)(\d*)$/; + const [, baseId, postfix] = streamerId.match(idMatchRegex); + if (baseId != id) { + continue; + } + const numPostfix = Number(postfix); + if (numPostfix > maxPostfix) { + maxPostfix = numPostfix + } + } + if (maxPostfix >= 0) { + return id + (maxPostfix + 1); + } + return id; +} + function registerStreamer(id, streamer) { - streamer.id = id; - streamers.set(streamer.id, streamer); + // make sure the id is unique + const uniqueId = sanitizeStreamerId(id); + streamer.commitId(uniqueId); if (!!streamer.idTimer) { clearTimeout(streamer.idTimer); delete streamer.idTimer; } + streamers.set(uniqueId, streamer); console.logColor(logging.Green, `Registered new streamer: ${streamer.id}`); } @@ -558,7 +662,8 @@ streamerServer.on('connection', function (ws, req) { console.logColor(logging.Green, `Streamer connected: ${req.connection.remoteAddress}`); sendStreamerConnectedToMatchmaker(); - let streamer = { id: req.connection.remoteAddress, ws: ws }; + const temporaryId = req.connection.remoteAddress; + let streamer = new Streamer(temporaryId, ws, StreamerType.Regular); ws.on('message', (msgRaw) => { var msg; @@ -569,6 +674,7 @@ streamerServer.on('connection', function (ws, req) { ws.close(1008, 'Cannot parse'); return; } + console.log(msgRaw); let handler = streamerMessageHandlers.get(msg.type); if (!handler || (typeof handler != 'function')) { @@ -605,13 +711,13 @@ function forwardSFUMessageToPlayer(sfuPlayer, msg) { const playerId = getPlayerIdFromMessage(msg); const player = players.get(playerId); if (player) { - logForward(sfuPlayer.streamer.id, playerId, msg); + logForward(sfuPlayer.getSFUStreamerComponent().id, playerId, msg); player.sendTo(msg); } } function forwardSFUMessageToStreamer(sfuPlayer, msg) { - logForward(sfuPlayer.streamer.id, sfuPlayer.streamerId, msg); + logForward(sfuPlayer.getSFUStreamerComponent().id, sfuPlayer.streamerId, msg); msg.sfuId = sfuPlayer.id; sfuPlayer.sendFrom(msg); } @@ -621,7 +727,7 @@ function onPeerDataChannelsSFUMessage(sfuPlayer, msg) { const playerId = getPlayerIdFromMessage(msg); const player = players.get(playerId); if (player) { - logForward(sfuPlayer.streamer.id, playerId, msg); + logForward(sfuPlayer.getSFUStreamerComponent().id, playerId, msg); player.sendTo(msg); player.datachannel = true; } @@ -631,10 +737,11 @@ function onPeerDataChannelsSFUMessage(sfuPlayer, msg) { function requestSFUStreamerId(sfuPlayer) { // request id const msg = { type: "identify" }; - logOutgoing(sfuPlayer.streamer.id, msg); - sfuPlayer.streamer.ws.send(JSON.stringify(msg)); + const sfuStreamerComponent = sfuPlayer.getSFUStreamerComponent(); + logOutgoing(sfuStreamerComponent.id, msg); + sfuStreamerComponent.ws.send(JSON.stringify(msg)); - sfuPlayer.streamer.idTimer = setTimeout(function() { + sfuStreamerComponent.idTimer = setTimeout(function() { // streamer did not respond in time. give it a legacy id. const newLegacyId = getUniqueSFUId(); if (newLegacyId.length == 0) { @@ -642,45 +749,48 @@ function requestSFUStreamerId(sfuPlayer) { console.error(error); sfuPlayer.ws.close(1008, error); } else { - sfuPlayer.streamer.id = newLegacyId; + sfuStreamerComponent.id = newLegacyId; } }, streamerIdTimeoutSecs * 1000); } function onSFUMessageId(sfuPlayer, msg) { - logIncoming(sfuPlayer.streamer.id, msg); - sfuPlayer.streamer.id = msg.id; + const sfuStreamerComponent = sfuPlayer.getSFUStreamerComponent(); + logIncoming(sfuStreamerComponent.id, msg); + sfuStreamerComponent.id = msg.id; - if (!!sfuPlayer.streamer.idTimer) { - clearTimeout(sfuPlayer.streamer.idTimer); - delete sfuPlayer.streamer.idTimer; + if (!!sfuStreamerComponent.idTimer) { + clearTimeout(sfuStreamerComponent.idTimer); + delete sfuStreamerComponent.idTimer; } } function onSFUMessageStartStreaming(sfuPlayer, msg) { - logIncoming(sfuPlayer.streamer.id, msg); - if (streamers.has(sfuPlayer.streamer.id)) { - console.error(`SFU ${sfuPlayer.streamer.id} is already registered as a streamer and streaming.`) + const sfuStreamerComponent = sfuPlayer.getSFUStreamerComponent(); + logIncoming(sfuStreamerComponent.id, msg); + if (streamers.has(sfuStreamerComponent.id)) { + console.error(`SFU ${sfuStreamerComponent.id} is already registered as a streamer and streaming.`) return; } - registerStreamer(sfuPlayer.streamer.id, sfuPlayer.streamer); + registerStreamer(sfuStreamerComponent.id, sfuStreamerComponent); } function onSFUMessageStopStreaming(sfuPlayer, msg) { - logIncoming(sfuPlayer.streamer.id, msg); -if (!streamers.has(sfuPlayer.streamer.id)) { - console.error(`SFU ${sfuPlayer.streamer.id} is not registered as a streamer or streaming.`) + const sfuStreamerComponent = sfuPlayer.getSFUStreamerComponent(); + logIncoming(sfuStreamerComponent.id, msg); +if (!streamers.has(sfuStreamerComponent.id)) { + console.error(`SFU ${sfuStreamerComponent.id} is not registered as a streamer or streaming.`) return; } - onStreamerDisconnected(sfuPlayer.streamer); + onStreamerDisconnected(sfuStreamerComponent); } function onSFUDisconnected(sfuPlayer) { console.log("disconnecting SFU from streamer"); disconnectAllPlayers(sfuPlayer.id); - onStreamerDisconnected(sfuPlayer.streamer); + onStreamerDisconnected(sfuPlayer.getSFUStreamerComponent()); sfuPlayer.unsubscribe(); sfuPlayer.ws.close(4000, "SFU Disconnected"); players.delete(sfuPlayer.id); @@ -704,9 +814,14 @@ sfuServer.on('connection', function (ws, req) { let playerId = sanitizePlayerId(nextPlayerId++); console.logColor(logging.Green, `SFU (${req.connection.remoteAddress}) connected `); - let player = new Player(playerId, ws, PlayerType.SFU, false); - player.streamer = { id: req.connection.remoteAddress, ws: ws }; // SFU also has a streamer component - players.set(playerId, player); + + let streamerComponent = new Streamer(req.connection.remoteAddress, ws, StreamerType.SFU); + let playerComponent = new Player(playerId, ws, PlayerType.SFU, WhoSendsOffer.Streamer); + + streamerComponent.setSFUPlayerComponent(playerComponent); + playerComponent.setSFUStreamerComponent(streamerComponent); + + players.set(playerId, playerComponent); ws.on('message', (msgRaw) => { var msg; @@ -739,12 +854,12 @@ sfuServer.on('connection', function (ws, req) { ws.on('close', function(code, reason) { console.error(`SFU disconnected: ${code} - ${reason}`); - onSFUDisconnected(player); + onSFUDisconnected(playerComponent); }); ws.on('error', function(error) { console.error(`SFU connection error: ${error}`); - onSFUDisconnected(player); + onSFUDisconnected(playerComponent); try { ws.close(1006 /* abnormal closure */, error); } catch(err) { @@ -752,7 +867,7 @@ sfuServer.on('connection', function (ws, req) { } }); - requestStreamerId(player.streamer); + requestStreamerId(playerComponent.getSFUStreamerComponent()); }); let playerCount = 0; @@ -823,7 +938,7 @@ playerServer.on('connection', function (ws, req) { var url = require('url'); const parsedUrl = url.parse(req.url); const urlParams = new URLSearchParams(parsedUrl.search); - const browserSendOffer = urlParams.has('OfferToReceive') && urlParams.get('OfferToReceive') !== 'false'; + const whoSendsOffer = urlParams.has('OfferToReceive') && urlParams.get('OfferToReceive') !== 'false' ? WhoSendsOffer.Browser : WhoSendsOffer.Streamer; if (playerCount + 1 > maxPlayerCount && maxPlayerCount !== -1) { @@ -835,7 +950,7 @@ playerServer.on('connection', function (ws, req) { ++playerCount; let playerId = sanitizePlayerId(nextPlayerId++); console.logColor(logging.Green, `player ${playerId} (${req.connection.remoteAddress}) connected`); - let player = new Player(playerId, ws, PlayerType.Regular, browserSendOffer); + let player = new Player(playerId, ws, PlayerType.Regular, whoSendsOffer); players.set(playerId, player); ws.on('message', (msgRaw) =>{ From 3d7bfb07231e4582128b900be02388598199a12d Mon Sep 17 00:00:00 2001 From: Matthew Cotton Date: Mon, 30 Oct 2023 16:52:26 +1100 Subject: [PATCH 19/21] Updating the handling of generating new legacy streamer and sfu ids. (cherry picked from commit 403fe39f4be6cbf363dfc23220484165a868e90e) --- SignallingWebServer/cirrus.js | 36 +++++++++++------------------------ 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/SignallingWebServer/cirrus.js b/SignallingWebServer/cirrus.js index 24decda1..79d49298 100644 --- a/SignallingWebServer/cirrus.js +++ b/SignallingWebServer/cirrus.js @@ -450,6 +450,7 @@ class Player { let streamers = new Map(); // streamerId <-> streamer let players = new Map(); // playerId <-> player/peer/viewer const LegacyStreamerPrefix = "__LEGACY_STREAMER__"; // old streamers that dont know how to ID will be assigned this id prefix. +const LegacySFUPrefix = "__LEGACY_SFU__"; // same as streamer version but for SFUs const streamerIdTimeoutSecs = 5; // gets the SFU subscribed to this streamer if any. @@ -502,33 +503,18 @@ function getPlayerIdFromMessage(msg) { return sanitizePlayerId(msg.playerId); } -function getUniqueLegacyId() { - for (let i = 0; i < 99; ++i) { - const testId = LegacyStreamerPrefix + i; - if (!streamers.has(testId)) { - return testId; - } - } - return ""; // no available id +let uniqueLegacyStreamerPostfix = 0; +function getUniqueLegacyStreamerId() { + const finalId = LegacyStreamerPrefix + uniqueLegacyStreamerPostfix; + ++uniqueLegacyStreamerPostfix; + return finalId; } -function getUniqueSFUId() { - for (let i = 0; i < 99; ++i) { - const testId = SFUStreamerPrefix + i; - let available = true; - for (let player of players) { - if (player.type == PlayerType.SFU) { - if (player.streamer.id == testId) { - available = false; - break; - } - } - } - if (available) { - return testId; - } - } - return ""; // no available id +let uniqueLegacySFUPostfix = 0; +function getUniqueLegacySFUId() { + const finalId = LegacySFUPrefix + uniqueLegacySFUPostfix; + ++uniqueLegacySFUPostfix; + return finalId; } function requestStreamerId(streamer) { From fdb6d6d8a04c5d340af1c4a39fda031a12c83054 Mon Sep 17 00:00:00 2001 From: Matthew Cotton Date: Tue, 31 Oct 2023 09:17:26 +1100 Subject: [PATCH 20/21] Catching case where we're sanitizing a streamer id that is numeric. Updating docs to remove PreferSFU (SFU is just selected as a streamer now). (cherry picked from commit 339aa088cc6c74e3647614a018a5a68a64ae6ad9) --- Frontend/Docs/Settings Panel.md | 1 - SignallingWebServer/cirrus.js | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Frontend/Docs/Settings Panel.md b/Frontend/Docs/Settings Panel.md index d636f76a..54fd26e1 100644 --- a/Frontend/Docs/Settings Panel.md +++ b/Frontend/Docs/Settings Panel.md @@ -19,7 +19,6 @@ This page will be updated with new features and commands as they become availabl | **Browser send offer** | The browser will start the WebRTC handshake instead of the Unreal Engine application. This is an advanced setting for users customising the frontend. Primarily for backwards compatibility for 4.x versions of the engine. | | **Use microphone** | Will start receiving audio input from your microphone and transmit it to the Unreal Engine. | | **Start video muted** | Muted audio when the stream starts. | -| **Prefer SFU** | Will attempt to use the Selective Forwarding Unit (SFU), if you have one running. | | **Is quality controller?** | Makes the encoder of the Pixel Streaming Plugin use the current browser connection to determine the bandwidth available, and therefore the quality of the stream encoding. **See notes below** | | **Force mono audio** | Force the browser to request mono audio in the SDP. | | **Force TURN** | Will attempt to connect exclusively via the TURN server. Will not work without an active TURN server. | diff --git a/SignallingWebServer/cirrus.js b/SignallingWebServer/cirrus.js index 79d49298..fd2c0fa6 100644 --- a/SignallingWebServer/cirrus.js +++ b/SignallingWebServer/cirrus.js @@ -546,7 +546,8 @@ function sanitizeStreamerId(id) { for (let [streamerId, streamer] of streamers) { const idMatchRegex = /^(.*?)(\d*)$/; const [, baseId, postfix] = streamerId.match(idMatchRegex); - if (baseId != id) { + // if the id is numeric then base id will be empty and we need to compare with the postfix + if ((baseId != '' && baseId != id) || (baseId == '' && postfix != id)) { continue; } const numPostfix = Number(postfix); From 22de24e61a41fe7383c5ab524f94b486db73757f Mon Sep 17 00:00:00 2001 From: Matthew Cotton Date: Tue, 31 Oct 2023 12:54:41 +1100 Subject: [PATCH 21/21] Fixing sfu forwarding. (cherry picked from commit 7f07f4b29ed0cb3ff0e93636bd42d91d38d7a24e) --- SignallingWebServer/cirrus.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SignallingWebServer/cirrus.js b/SignallingWebServer/cirrus.js index fd2c0fa6..615a4a98 100644 --- a/SignallingWebServer/cirrus.js +++ b/SignallingWebServer/cirrus.js @@ -460,7 +460,7 @@ function getSFUForStreamer(streamerId) { } const streamer = streamers.get(streamerId); const sfuPlayerId = streamer.getSFUPlayerId(); - if (!!sfuPlayerId) { + if (!sfuPlayerId) { return null; } return players.get(sfuPlayerId); @@ -661,7 +661,6 @@ streamerServer.on('connection', function (ws, req) { ws.close(1008, 'Cannot parse'); return; } - console.log(msgRaw); let handler = streamerMessageHandlers.get(msg.type); if (!handler || (typeof handler != 'function')) {