From fd9db5d3f6223b2d10ca99bfb48b20768fc47fb5 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Tue, 22 Mar 2022 02:02:49 -0300 Subject: [PATCH 01/37] push build attempt for mconf repositories --- .github/workflows/build-and-deploy.yml | 98 +++----------------------- 1 file changed, 11 insertions(+), 87 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 9b17136d2c..2966ab3ac6 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -2,7 +2,7 @@ name: Build, push and deploy Docker image on: push: - branches: [master, develop] + branches: [feature-bbb-integration-v3] release: types: [created] pull_request: @@ -50,7 +50,7 @@ jobs: id: meta uses: docker/metadata-action@v3 with: - images: thecodingmachine/workadventure-front + images: mconf/workadventure-front - name: Build and push uses: docker/build-push-action@v2 @@ -59,7 +59,7 @@ jobs: file: front/Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: thecodingmachine/workadventure-front:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: mconf/workadventure-front:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} labels: ${{ steps.meta.outputs.labels }} build-back: @@ -103,7 +103,7 @@ jobs: id: meta uses: docker/metadata-action@v3 with: - images: thecodingmachine/workadventure-back + images: mconf/workadventure-back - name: Build and push uses: docker/build-push-action@v2 @@ -112,7 +112,7 @@ jobs: file: back/Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: thecodingmachine/workadventure-back:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: mconf/workadventure-back:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} labels: ${{ steps.meta.outputs.labels }} build-pusher: @@ -156,7 +156,7 @@ jobs: id: meta uses: docker/metadata-action@v3 with: - images: thecodingmachine/workadventure-pusher + images: mconf/workadventure-pusher - name: Build and push uses: docker/build-push-action@v2 @@ -165,7 +165,7 @@ jobs: file: pusher/Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: thecodingmachine/workadventure-pusher:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: mconf/workadventure-pusher:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} labels: ${{ steps.meta.outputs.labels }} build-uploader: @@ -191,14 +191,14 @@ jobs: id: meta uses: docker/metadata-action@v3 with: - images: thecodingmachine/workadventure-uploader + images: mconf/workadventure-uploader - name: Build and push uses: docker/build-push-action@v2 with: file: uploader/Dockerfile push: true - tags: thecodingmachine/workadventure-uploader:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: mconf/workadventure-uploader:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} labels: ${{ steps.meta.outputs.labels }} build-maps: @@ -227,7 +227,7 @@ jobs: id: meta uses: docker/metadata-action@v3 with: - images: thecodingmachine/workadventure-maps + images: mconf/workadventure-maps - name: Build and push uses: docker/build-push-action@v2 @@ -235,81 +235,5 @@ jobs: context: maps/ file: maps/Dockerfile push: true - tags: thecodingmachine/workadventure-maps:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: mconf/workadventure-maps:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} labels: ${{ steps.meta.outputs.labels }} - - deeploy: - needs: - - build-front - - build-back - - build-pusher - - build-maps - - build-uploader - runs-on: ubuntu-latest - if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }} - - steps: - - name: Checkout - uses: actions/checkout@v2 - - # Create a slugified value of the branch - - uses: rlespinasse/github-slug-action@3.1.0 - - - name: Set ADMIN_URL if "deploy-connect-to-admin" label is set - run: echo "ADMIN_API_URL=https://${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}.test.workadventu.re" >> $GITHUB_ENV - if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy-connect-to-admin') }} - - - name: Write certificate - run: echo "${CERTS_PRIVATE_KEY}" > secret.key && chmod 0600 secret.key - env: - CERTS_PRIVATE_KEY: ${{ secrets.CERTS_PRIVATE_KEY }} - - - name: Download certificate - run: mkdir secrets && scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i secret.key ubuntu@cert.workadventu.re:./config/live/workadventu.re/* secrets/ - - - name: Create namespace - uses: steebchen/kubectl@v1.0.0 - env: - KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }} - with: - args: create namespace workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - continue-on-error: true - - - name: Delete old certificates in namespace - uses: steebchen/kubectl@v1.0.0 - env: - KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }} - with: - args: -n workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} delete secret certificate-tls - continue-on-error: true - - - name: Install certificates in namespace - uses: steebchen/kubectl@v1.0.0 - env: - KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }} - with: - args: -n workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} create secret tls certificate-tls --key="secrets/privkey.pem" --cert="secrets/fullchain.pem" - - - name: Deploy - uses: thecodingmachine/deeployer-action@master - env: - KUBE_CONFIG_FILE: ${{ secrets.KUBE_CONFIG_FILE }} - ADMIN_API_TOKEN: ${{ secrets.ADMIN_API_TOKEN }} - ADMIN_SOCKETS_TOKEN: ${{ secrets.ADMIN_SOCKETS_TOKEN }} - JITSI_ISS: ${{ secrets.JITSI_ISS }} - JITSI_URL: ${{ secrets.JITSI_URL }} - SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }} - TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }} - DEPLOY_REF: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} - POSTHOG_URL: ${{ secrets.POSTHOG_URL }} - with: - namespace: workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - - - name: Add a comment in PR - uses: unsplash/comment-on-pr@v1.2.0 - if: ${{ github.event_name == 'pull_request' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - msg: "Environment deployed at https://play-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re \nTests available at https://maps-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re/tests" From 4e7f04ca52a29900afc92ac20528b24adf44f6c4 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Tue, 22 Mar 2022 02:06:05 -0300 Subject: [PATCH 02/37] attempt to build --- .github/workflows/build-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 2966ab3ac6..baee5cda21 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -2,7 +2,7 @@ name: Build, push and deploy Docker image on: push: - branches: [feature-bbb-integration-v3] + branches: [feature-bbb-integration-v3,feature-bbb-integration-v4] release: types: [created] pull_request: From 1991b06b65c91461cb8fc744518969b665173f58 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Mon, 28 Mar 2022 23:47:49 -0300 Subject: [PATCH 03/37] revert branches to build --- .github/workflows/build-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index baee5cda21..f45c2c809c 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -2,7 +2,7 @@ name: Build, push and deploy Docker image on: push: - branches: [feature-bbb-integration-v3,feature-bbb-integration-v4] + branches: [master, develop] release: types: [created] pull_request: From 2bac8a5bf8cc32e969ecc495c2890030e92ef2e9 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Thu, 31 Mar 2022 16:24:54 -0300 Subject: [PATCH 04/37] add dist so vite-plugin does not break the build --- front/dist/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 front/dist/.gitkeep diff --git a/front/dist/.gitkeep b/front/dist/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 From ddd72b545cb4d01842ba579b1104cb925dca3942 Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Tue, 10 May 2022 18:35:32 -0300 Subject: [PATCH 05/37] Add an option to limit video bandwidth The following environment variables are used to restrict video or screen share bandwidth (in kbps). PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS --- back/package.json | 4 +- docker-compose.yaml | 2 + front/package.json | 1 + front/src/Components/Video/utils.ts | 46 ++++++++ front/src/Enum/EnvironmentVariable.ts | 6 + front/src/WebRtc/ScreenSharingPeer.ts | 4 +- front/src/WebRtc/VideoPeer.ts | 4 +- front/tests/Components/Video/UtilsTest.ts | 129 ++++++++++++++++++++++ front/vite.config.ts | 2 + front/yarn.lock | 12 ++ 10 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 front/tests/Components/Video/UtilsTest.ts diff --git a/back/package.json b/back/package.json index d3a193b68f..c51f374c01 100644 --- a/back/package.json +++ b/back/package.json @@ -43,8 +43,8 @@ "@anatine/zod-openapi": "^1.3.0", "@workadventure/tiled-map-type-guard": "^1.0.3", "axios": "^0.21.2", - "busboy": "^0.3.1", "bigbluebutton-js": "^0.1.1", + "busboy": "^0.3.1", "circular-json": "^0.5.9", "debug": "^4.3.1", "google-protobuf": "^3.13.0", @@ -61,8 +61,8 @@ "zod": "^3.14.3" }, "devDependencies": { - "@types/busboy": "^0.2.3", "@types/bigbluebutton-js": "^0.2.1", + "@types/busboy": "^0.2.3", "@types/circular-json": "^0.4.0", "@types/debug": "^4.1.5", "@types/google-protobuf": "^3.7.3", diff --git a/docker-compose.yaml b/docker-compose.yaml index e68a402372..1e2bd95dff 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -52,6 +52,8 @@ services: MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH" DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS" OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER" + PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS: "$PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS" + PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS: "$PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS" command: yarn run start volumes: - ./front:/usr/src/app diff --git a/front/package.json b/front/package.json index bb1a2ce3a6..fe0447dea3 100644 --- a/front/package.json +++ b/front/package.json @@ -64,6 +64,7 @@ "ts-proto": "^1.96.0", "typesafe-i18n": "^5.4.0", "uuidv4": "^6.2.10", + "webrtc-adapter": "^8.1.1", "zod": "^3.14.3" }, "scripts": { diff --git a/front/src/Components/Video/utils.ts b/front/src/Components/Video/utils.ts index 4be48ff0c3..1b9b6c3fe5 100644 --- a/front/src/Components/Video/utils.ts +++ b/front/src/Components/Video/utils.ts @@ -1,5 +1,6 @@ import type { UserSimplePeerInterface } from "../../WebRtc/SimplePeer"; import { STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER } from "../../Enum/EnvironmentVariable"; +import adapter from "webrtc-adapter"; export function getColorByString(str: string): string | null { let hash = 0; @@ -62,3 +63,48 @@ export function getIceServersConfig(user: UserSimplePeerInterface): RTCIceServer } return config; } + +export function getSdpTransform(videoBandwidth = 0) { + return (sdp: string) => { + sdp = updateBandwidthRestriction(sdp, videoBandwidth, "video"); + + return sdp; + }; +} + +function updateBandwidthRestriction(sdp: string, bandwidth: integer, mediaType: string): string { + if (bandwidth <= 0) { + return sdp; + } + + let modifier = "AS"; + // Firefox doesn't support "AS" + if (adapter.browserDetails.browser === "firefox") { + bandwidth = (bandwidth >>> 0) * 1000; + modifier = "TIAS"; + } + + for ( + let targetMediaPos = sdp.indexOf(`m=${mediaType}`); + targetMediaPos !== -1; + targetMediaPos = sdp.indexOf(`m=${mediaType}`, targetMediaPos + 1) + ) { + const nextMediaPos = sdp.indexOf(`m=`, targetMediaPos + 1); + const nextBWPos = sdp.indexOf(`b=${modifier}:`, targetMediaPos + 1); + + let mediaSlice = sdp.slice(targetMediaPos); + const mustCreateBWField = nextBWPos === -1 || (nextBWPos > nextMediaPos && nextMediaPos !== -1); + if (mustCreateBWField) { + // insert b= after c= line. + mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${bandwidth}$2`); + } else { + // update b= with new 'bandwidth' + mediaSlice = mediaSlice.replace(new RegExp(`b=${modifier}:.*(\r?\n)`), `b=${modifier}:${bandwidth}$1`); + } + + // update the sdp + sdp = sdp.slice(0, targetMediaPos) + mediaSlice; + } + + return sdp; +} diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 9277da60f5..f6733e2518 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -29,6 +29,10 @@ export const POSTHOG_URL = getEnvConfig("POSTHOG_URL") || undefined; export const DISABLE_ANONYMOUS: boolean = getEnvConfig("DISABLE_ANONYMOUS") === "true"; export const OPID_LOGIN_SCREEN_PROVIDER = getEnvConfig("OPID_LOGIN_SCREEN_PROVIDER"); const FALLBACK_LOCALE = getEnvConfig("FALLBACK_LOCALE") || undefined; +const PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS = parseInt(getEnvConfig("PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS") || "0"); +const PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS = parseInt( + getEnvConfig("PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS") || "0" +); export { DEBUG_MODE, @@ -47,4 +51,6 @@ export { JITSI_URL, JITSI_PRIVATE_MODE, FALLBACK_LOCALE, + PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS, + PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS, }; diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index 20384f1a8e..506c20f836 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -2,11 +2,12 @@ import type { RoomConnection } from "../Connexion/RoomConnection"; import { MESSAGE_TYPE_CONSTRAINT, PeerStatus } from "./VideoPeer"; import type { UserSimplePeerInterface } from "./SimplePeer"; import { Readable, readable, writable, Writable } from "svelte/store"; -import { getIceServersConfig } from "../Components/Video/utils"; +import { getIceServersConfig, getSdpTransform } from "../Components/Video/utils"; import { highlightedEmbedScreen } from "../Stores/EmbedScreensStore"; import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils"; import Peer from "simple-peer/simplepeer.min.js"; import { Buffer } from "buffer"; +import { PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS } from "../Enum/EnvironmentVariable"; /** * A peer connection used to transmit video / audio signals between 2 peers. @@ -35,6 +36,7 @@ export class ScreenSharingPeer extends Peer { config: { iceServers: getIceServersConfig(user), }, + sdpTransform: getSdpTransform(PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS), }); this.userId = user.userId; diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index 97f855b733..108341f92a 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -7,11 +7,12 @@ import { readable, Readable, Unsubscriber } from "svelte/store"; import { localStreamStore, obtainedMediaConstraintStore, ObtainedMediaStreamConstraints } from "../Stores/MediaStore"; import { playersStore } from "../Stores/PlayersStore"; import { chatMessagesStore, newChatMessageSubject } from "../Stores/ChatStore"; -import { getIceServersConfig } from "../Components/Video/utils"; +import { getIceServersConfig, getSdpTransform } from "../Components/Video/utils"; import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils"; import { SoundMeter } from "../Phaser/Components/SoundMeter"; import Peer from "simple-peer/simplepeer.min.js"; import { Buffer } from "buffer"; +import { PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS } from "../Enum/EnvironmentVariable"; export type PeerStatus = "connecting" | "connected" | "error" | "closed"; @@ -52,6 +53,7 @@ export class VideoPeer extends Peer { config: { iceServers: getIceServersConfig(user), }, + sdpTransform: getSdpTransform(PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS), }); this.userId = user.userId; diff --git a/front/tests/Components/Video/UtilsTest.ts b/front/tests/Components/Video/UtilsTest.ts new file mode 100644 index 0000000000..219f8e9733 --- /dev/null +++ b/front/tests/Components/Video/UtilsTest.ts @@ -0,0 +1,129 @@ +import "jasmine"; + +import { getSdpTransform } from "../../../src/Components/Video/utils"; + +describe("getSdpTransform()", () => { + it("should not do anything if bandwidth = 0", () => { + const bw = 0; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:22 + m=application + c=IN IP4 0.0.0.0 + b=AS:33`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(originalSdp); + }); + + it("should create the 'b' field of the video media if it doesn't exist", () => { + const bw = 55; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedDefaultSdp(bw)); + }); + + it("should update the 'b' field of the video media if it already exists", () => { + const bw = 66; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + b=AS:22 + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedDefaultSdp(bw)); + }); + + it("should create and update the 'b' field of each video media if one doesn't exist and the other does", () => { + const bw = 77; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedDefaultSdp(bw)); + }); + + it("should not change the 'b' field of other medias", () => { + const bw = 88; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + b=AS:22 + m=video + c=IN IP4 0.0.0.0 + b=AS:33 + m=application + c=IN IP4 0.0.0.0 + b=AS:44`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedFullBWSdp(bw)); + }); + + // Expected sdp where the audio and application medias have no 'b' fields + // and the video medias should have 'b=AS:'. + const expectedDefaultSdp = (bw: integer) => { + return ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + }; + + // Expected sdp where the audio and application medias have a 'b' field + // and the video medias should have 'b=AS:'. + const expectedFullBWSdp = (bw: integer) => { + return ` + m=audio + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=application + c=IN IP4 0.0.0.0 + b=AS:44`.replace(/^[\s]*/gm, ""); + }; +}); diff --git a/front/vite.config.ts b/front/vite.config.ts index 09cc7d7e3c..81f38040fc 100644 --- a/front/vite.config.ts +++ b/front/vite.config.ts @@ -47,6 +47,8 @@ export default defineConfig({ "DISABLE_ANONYMOUS", "OPID_LOGIN_SCREEN_PROVIDER", "FALLBACK_LOCALE", + "PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS", + "PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS", ], }), pluginRewriteAll(), diff --git a/front/yarn.lock b/front/yarn.lock index 7f49a4ed71..31a0520206 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -2508,6 +2508,11 @@ sass@^1.49.7: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" +sdp@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sdp/-/sdp-3.0.3.tgz#669958d54663ea9f4a46cc66518c9d980c52c61e" + integrity sha512-8EkfckS+XZQaPLyChu4ey7PghrdcraCVNpJe2Gfdi2ON1ylQ7OasuKX+b37R9slnRChwIAiQgt+oj8xXGD8x+A== + "semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -3115,6 +3120,13 @@ webfontloader@^1.6.28: resolved "https://registry.yarnpkg.com/webfontloader/-/webfontloader-1.6.28.tgz#db786129253cb6e8eae54c2fb05f870af6675bae" integrity sha1-23hhKSU8tujq5UwvsF+HCvZnW64= +webrtc-adapter@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-8.1.1.tgz#e4a4dfd1b5085d119da40c4efc0147f7d0961cba" + integrity sha512-1yXevP7TeZGmklEXkvQVrZp3fOSJlLeXNGCA7NovQokxgP3/e2T3EVGL0eKU87S9vKppWjvRWqnJeSANEspOBg== + dependencies: + sdp "^3.0.2" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" From ceda008df91a790d5ebd01353963592a016eb5f5 Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Mon, 16 May 2022 14:29:27 -0300 Subject: [PATCH 06/37] Negotiate both AS and TIAS modifiers in sdp --- front/package.json | 3 +-- front/src/Components/Video/utils.ts | 38 ++++++++++++++--------------- front/yarn.lock | 12 --------- 3 files changed, 19 insertions(+), 34 deletions(-) diff --git a/front/package.json b/front/package.json index fe0447dea3..b691a22fc3 100644 --- a/front/package.json +++ b/front/package.json @@ -64,7 +64,6 @@ "ts-proto": "^1.96.0", "typesafe-i18n": "^5.4.0", "uuidv4": "^6.2.10", - "webrtc-adapter": "^8.1.1", "zod": "^3.14.3" }, "scripts": { @@ -96,4 +95,4 @@ "yarn run pretty" ] } -} \ No newline at end of file +} diff --git a/front/src/Components/Video/utils.ts b/front/src/Components/Video/utils.ts index 1b9b6c3fe5..7f650f853e 100644 --- a/front/src/Components/Video/utils.ts +++ b/front/src/Components/Video/utils.ts @@ -1,6 +1,5 @@ import type { UserSimplePeerInterface } from "../../WebRtc/SimplePeer"; import { STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER } from "../../Enum/EnvironmentVariable"; -import adapter from "webrtc-adapter"; export function getColorByString(str: string): string | null { let hash = 0; @@ -77,33 +76,32 @@ function updateBandwidthRestriction(sdp: string, bandwidth: integer, mediaType: return sdp; } - let modifier = "AS"; - // Firefox doesn't support "AS" - if (adapter.browserDetails.browser === "firefox") { - bandwidth = (bandwidth >>> 0) * 1000; - modifier = "TIAS"; - } - for ( let targetMediaPos = sdp.indexOf(`m=${mediaType}`); targetMediaPos !== -1; targetMediaPos = sdp.indexOf(`m=${mediaType}`, targetMediaPos + 1) ) { const nextMediaPos = sdp.indexOf(`m=`, targetMediaPos + 1); - const nextBWPos = sdp.indexOf(`b=${modifier}:`, targetMediaPos + 1); + for (const modifier of ["TIAS", "AS"]) { + const newBandwidth = modifier === "TIAS" ? (bandwidth >>> 0) * 1000 : bandwidth; + const nextBWPos = sdp.indexOf(`b=${modifier}:`, targetMediaPos + 1); - let mediaSlice = sdp.slice(targetMediaPos); - const mustCreateBWField = nextBWPos === -1 || (nextBWPos > nextMediaPos && nextMediaPos !== -1); - if (mustCreateBWField) { - // insert b= after c= line. - mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${bandwidth}$2`); - } else { - // update b= with new 'bandwidth' - mediaSlice = mediaSlice.replace(new RegExp(`b=${modifier}:.*(\r?\n)`), `b=${modifier}:${bandwidth}$1`); - } + let mediaSlice = sdp.slice(targetMediaPos); + const mustCreateBWField = nextBWPos === -1 || (nextBWPos > nextMediaPos && nextMediaPos !== -1); + if (mustCreateBWField) { + // insert b= after c= line. + mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${newBandwidth}$2`); + } else { + // update b= with new 'bandwidth' + mediaSlice = mediaSlice.replace( + new RegExp(`b=${modifier}:.*(\r?\n)`), + `b=${modifier}:${newBandwidth}$1` + ); + } - // update the sdp - sdp = sdp.slice(0, targetMediaPos) + mediaSlice; + // update the sdp + sdp = sdp.slice(0, targetMediaPos) + mediaSlice; + } } return sdp; diff --git a/front/yarn.lock b/front/yarn.lock index 31a0520206..7f49a4ed71 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -2508,11 +2508,6 @@ sass@^1.49.7: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" -sdp@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/sdp/-/sdp-3.0.3.tgz#669958d54663ea9f4a46cc66518c9d980c52c61e" - integrity sha512-8EkfckS+XZQaPLyChu4ey7PghrdcraCVNpJe2Gfdi2ON1ylQ7OasuKX+b37R9slnRChwIAiQgt+oj8xXGD8x+A== - "semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -3120,13 +3115,6 @@ webfontloader@^1.6.28: resolved "https://registry.yarnpkg.com/webfontloader/-/webfontloader-1.6.28.tgz#db786129253cb6e8eae54c2fb05f870af6675bae" integrity sha1-23hhKSU8tujq5UwvsF+HCvZnW64= -webrtc-adapter@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-8.1.1.tgz#e4a4dfd1b5085d119da40c4efc0147f7d0961cba" - integrity sha512-1yXevP7TeZGmklEXkvQVrZp3fOSJlLeXNGCA7NovQokxgP3/e2T3EVGL0eKU87S9vKppWjvRWqnJeSANEspOBg== - dependencies: - sdp "^3.0.2" - which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" From 4643d9cf5caf78fb0a2eee0fc462fa3338d87d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 24 May 2022 14:53:58 +0200 Subject: [PATCH 07/37] Preventing trying to send a message on a closed connection. If the websocket connection is closed, a warning message will be displayed in the logs. However, this will not trigger errors anymore like it used to do. --- front/src/Connexion/RoomConnection.ts | 113 ++++++++++---------------- 1 file changed, 45 insertions(+), 68 deletions(-) diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 4ed9769a87..c3ad51a5ff 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -542,28 +542,24 @@ export class RoomConnection implements RoomConnection { const message = SetPlayerDetailsMessageTsProto.fromPartial({ showVoiceIndicator: show, }); - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "setPlayerDetailsMessage", setPlayerDetailsMessage: message, }, - }).finish(); - - this.socket.send(bytes); + }); } public emitPlayerStatusChange(availabilityStatus: AvailabilityStatus): void { const message = SetPlayerDetailsMessageTsProto.fromPartial({ availabilityStatus, }); - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "setPlayerDetailsMessage", setPlayerDetailsMessage: message, }, - }).finish(); - - this.socket.send(bytes); + }); } public emitPlayerOutlineColor(color: number | null) { @@ -577,14 +573,12 @@ export class RoomConnection implements RoomConnection { outlineColor: color, }); } - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "setPlayerDetailsMessage", setPlayerDetailsMessage: message, }, - }).finish(); - - this.socket.send(bytes); + }); } public closeConnection(): void { @@ -632,7 +626,7 @@ export class RoomConnection implements RoomConnection { const viewportMessage = this.toViewportMessage(viewport); - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "userMovesMessage", userMovesMessage: { @@ -640,20 +634,16 @@ export class RoomConnection implements RoomConnection { viewport: viewportMessage, }, }, - }).finish(); - - this.socket.send(bytes); + }); } public setViewport(viewport: ViewportInterface): void { - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "viewportMessage", viewportMessage: this.toViewportMessage(viewport), }, - }).finish(); - - this.socket.send(bytes); + }); } /* public onUserJoins(callback: (message: MessageUserJoined) => void): void { @@ -726,7 +716,7 @@ export class RoomConnection implements RoomConnection { } public sendWebrtcSignal(signal: unknown, receiverId: number) { - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "webRtcSignalToServerMessage", webRtcSignalToServerMessage: { @@ -734,13 +724,11 @@ export class RoomConnection implements RoomConnection { signal: JSON.stringify(signal), }, }, - }).finish(); - - this.socket.send(bytes); + }); } public sendWebrtcScreenSharingSignal(signal: unknown, receiverId: number) { - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "webRtcScreenSharingSignalToServerMessage", webRtcScreenSharingSignalToServerMessage: { @@ -748,9 +736,7 @@ export class RoomConnection implements RoomConnection { signal: JSON.stringify(signal), }, }, - }).finish(); - - this.socket.send(bytes); + }); } public onServerDisconnected(callback: () => void): void { @@ -773,7 +759,7 @@ export class RoomConnection implements RoomConnection { } emitActionableEvent(itemId: number, event: string, state: unknown, parameters: unknown): void { - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "itemEventMessage", itemEventMessage: { @@ -783,13 +769,11 @@ export class RoomConnection implements RoomConnection { parametersJson: JSON.stringify(parameters), }, }, - }).finish(); - - this.socket.send(bytes); + }); } emitSetVariableEvent(name: string, value: unknown): void { - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "variableMessage", variableMessage: { @@ -797,9 +781,7 @@ export class RoomConnection implements RoomConnection { value: JSON.stringify(value), }, }, - }).finish(); - - this.socket.send(bytes); + }); } public uploadAudio(file: FormData) { @@ -814,7 +796,7 @@ export class RoomConnection implements RoomConnection { } public emitGlobalMessage(message: PlayGlobalMessageInterface): void { - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "playGlobalMessage", playGlobalMessage: { @@ -823,13 +805,11 @@ export class RoomConnection implements RoomConnection { broadcastToWorld: message.broadcastToWorld, }, }, - }).finish(); - - this.socket.send(bytes); + }); } public emitReportPlayerMessage(reportedUserUuid: string, reportComment: string): void { - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "reportPlayerMessage", reportPlayerMessage: { @@ -837,13 +817,11 @@ export class RoomConnection implements RoomConnection { reportComment, }, }, - }).finish(); - - this.socket.send(bytes); + }); } public emitQueryJitsiJwtMessage(jitsiRoom: string, tag: string | undefined): void { - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "queryJitsiJwtMessage", queryJitsiJwtMessage: { @@ -852,9 +830,7 @@ export class RoomConnection implements RoomConnection { // TODO: when we migrated "pusher" to ts-proto, migrate this to a StringValue }, }, - }).finish(); - - this.socket.send(bytes); + }); } public emitJoinBBBMeeting(meetingId: string, props: Map): void { @@ -881,16 +857,14 @@ export class RoomConnection implements RoomConnection { } public emitEmoteEvent(emoteName: string): void { - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "emotePromptMessage", emotePromptMessage: { emote: emoteName, }, }, - }).finish(); - - this.socket.send(bytes); + }); } public emitFollowRequest(): void { @@ -898,16 +872,14 @@ export class RoomConnection implements RoomConnection { return; } - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "followRequestMessage", followRequestMessage: { leader: this.userId, }, }, - }).finish(); - - this.socket.send(bytes); + }); } public emitFollowConfirmation(): void { @@ -915,7 +887,7 @@ export class RoomConnection implements RoomConnection { return; } - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "followConfirmationMessage", followConfirmationMessage: { @@ -923,9 +895,7 @@ export class RoomConnection implements RoomConnection { follower: this.userId, }, }, - }).finish(); - - this.socket.send(bytes); + }); } public emitFollowAbort(): void { @@ -935,7 +905,7 @@ export class RoomConnection implements RoomConnection { return; } - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "followAbortMessage", followAbortMessage: { @@ -943,22 +913,18 @@ export class RoomConnection implements RoomConnection { follower: isLeader ? 0 : this.userId, }, }, - }).finish(); - - this.socket.send(bytes); + }); } public emitLockGroup(lock: boolean = true): void { - const bytes = ClientToServerMessageTsProto.encode({ + this.send({ message: { $case: "lockGroupPromptMessage", lockGroupPromptMessage: { lock, }, }, - }).finish(); - - this.socket.send(bytes); + }); } public getAllTags(): string[] { @@ -975,4 +941,15 @@ export class RoomConnection implements RoomConnection { selectCharacterSceneVisibleStore.set(true); gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene()); } + + private send(message: ClientToServerMessageTsProto): void { + const bytes = ClientToServerMessageTsProto.encode(message).finish(); + + if (this.socket.readyState === WebSocket.CLOSING || this.socket.readyState === WebSocket.CLOSED) { + console.warn("Trying to send a message to the server, but the connection is closed. Message: ", message); + return; + } + + this.socket.send(bytes); + } } From b729394e586942ad0b1feec7382d703c26b339be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 24 May 2022 15:32:46 +0200 Subject: [PATCH 08/37] Properly unregistering some subscriptions in SimplePeer A few "unsubscribes" were missing. Probably not important since SimplePeer life time is bound to the connection, but still. --- front/src/WebRtc/SimplePeer.ts | 51 +++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index fdd9574873..bc1210fb53 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -11,6 +11,7 @@ import { get } from "svelte/store"; import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore"; import { playersStore } from "../Stores/PlayersStore"; import { peerStore, screenSharingPeerStore } from "../Stores/PeerStore"; +import { Subscription } from "rxjs"; export interface UserSimplePeerInterface { userId: number; @@ -32,6 +33,7 @@ export class SimplePeer { private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback; private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback; private readonly unsubscribers: (() => void)[] = []; + private readonly rxJsUnsubscribers: Subscription[] = []; private readonly userId: number; private lastWebrtcUserName: string | undefined; private lastWebrtcPassword: string | undefined; @@ -75,27 +77,37 @@ export class SimplePeer { */ private initialise() { //receive signal by gemer - this.Connection.webRtcSignalToClientMessageStream.subscribe((message: WebRtcSignalReceivedMessageInterface) => { - this.receiveWebrtcSignal(message); - }); + this.rxJsUnsubscribers.push( + this.Connection.webRtcSignalToClientMessageStream.subscribe( + (message: WebRtcSignalReceivedMessageInterface) => { + this.receiveWebrtcSignal(message); + } + ) + ); //receive signal by gemer - this.Connection.webRtcScreenSharingSignalToClientMessageStream.subscribe( - (message: WebRtcSignalReceivedMessageInterface) => { - this.receiveWebrtcScreenSharingSignal(message); - } + this.rxJsUnsubscribers.push( + this.Connection.webRtcScreenSharingSignalToClientMessageStream.subscribe( + (message: WebRtcSignalReceivedMessageInterface) => { + this.receiveWebrtcScreenSharingSignal(message); + } + ) ); mediaManager.showMyCamera(); //receive message start - this.Connection.webRtcStartMessageStream.subscribe((message: UserSimplePeerInterface) => { - this.receiveWebrtcStart(message); - }); + this.rxJsUnsubscribers.push( + this.Connection.webRtcStartMessageStream.subscribe((message: UserSimplePeerInterface) => { + this.receiveWebrtcStart(message); + }) + ); - this.Connection.webRtcDisconnectMessageStream.subscribe((data: WebRtcDisconnectMessageInterface): void => { - this.closeConnection(data.userId); - }); + this.rxJsUnsubscribers.push( + this.Connection.webRtcDisconnectMessageStream.subscribe((data: WebRtcDisconnectMessageInterface): void => { + this.closeConnection(data.userId); + }) + ); } private receiveWebrtcStart(user: UserSimplePeerInterface): void { @@ -219,7 +231,7 @@ export class SimplePeer { ); return; } - //create temp perr to close + //create temp peer to close peer.toClose = true; peer.destroy(); // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" @@ -234,7 +246,9 @@ export class SimplePeer { this.Users.splice(userIndex, 1); } } catch (err) { - console.error("closeConnection", err); + console.error("An error occurred in closeConnection", err); + } finally { + this.PeerConnectionArray.delete(userId); } //if user left discussion, clear array peer connection of sharing @@ -267,7 +281,9 @@ export class SimplePeer { // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. peer.destroy(); } catch (err) { - console.error("closeConnection", err); + console.error("An error occurred in closeScreenSharingConnection", err); + } finally { + this.PeerScreenSharingConnectionArray.delete(userId); } } @@ -288,6 +304,9 @@ export class SimplePeer { for (const unsubscriber of this.unsubscribers) { unsubscriber(); } + for (const subscription of this.rxJsUnsubscribers) { + subscription.unsubscribe(); + } peerStore.cleanupStore(); screenSharingPeerStore.cleanupStore(); } From 2cb18adbd17471e9c78c8a3eb8669a494093bed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 24 May 2022 16:58:00 +0200 Subject: [PATCH 09/37] Actually trying to clean the map of peers in SimplePeer For some reason, this was not done before (???) --- front/src/WebRtc/SimplePeer.ts | 46 +++++++++++++--------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index bc1210fb53..0f37917f6e 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -26,8 +26,6 @@ export type RemotePeer = VideoPeer | ScreenSharingPeer; * This class manages connections to all the peers in the same group as me. */ export class SimplePeer { - private Users: Array = new Array(); //todo: this array should be fusionned with PeerConnectionArray - private PeerScreenSharingConnectionArray: Map = new Map(); private PeerConnectionArray: Map = new Map(); private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback; @@ -111,7 +109,6 @@ export class SimplePeer { } private receiveWebrtcStart(user: UserSimplePeerInterface): void { - this.Users.push(user); // Note: the clients array contain the list of all clients (even the ones we are already connected to in case a user joins a group) // So we can receive a request we already had before. (which will abort at the first line of createPeerConnection) // This would be symmetrical to the way we handle disconnection. @@ -135,7 +132,7 @@ export class SimplePeer { peerConnection.destroy(); const peerConnexionDeleted = this.PeerConnectionArray.delete(user.userId); if (!peerConnexionDeleted) { - throw new Error("Error to delete peer connection"); + throw new Error("Error deleting peer connection"); } //return this.createPeerConnection(user, localStream); } else { @@ -182,20 +179,20 @@ export class SimplePeer { user: UserSimplePeerInterface, stream: MediaStream | null ): ScreenSharingPeer | null { - const peerConnection = this.PeerScreenSharingConnectionArray.get(user.userId); - if (peerConnection) { - if (peerConnection.destroyed) { - peerConnection.toClose = true; - peerConnection.destroy(); + const peerScreenSharingConnection = this.PeerScreenSharingConnectionArray.get(user.userId); + if (peerScreenSharingConnection) { + if (peerScreenSharingConnection.destroyed) { + peerScreenSharingConnection.toClose = true; + peerScreenSharingConnection.destroy(); const peerConnexionDeleted = this.PeerScreenSharingConnectionArray.delete(user.userId); if (!peerConnexionDeleted) { - throw new Error("Error to delete peer connection"); + throw new Error("Error deleting peer connection"); } - this.createPeerConnection(user); + //this.createPeerConnection(user); } else { - peerConnection.toClose = false; + peerScreenSharingConnection.toClose = false; + return null; } - return null; } // Enrich the user with last known credentials (if they are not set in the user object, which happens when a user triggers the screen sharing) @@ -238,25 +235,16 @@ export class SimplePeer { // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. this.closeScreenSharingConnection(userId); - - const userIndex = this.Users.findIndex((user) => user.userId === userId); - if (userIndex < 0) { - throw new Error("Couldn't delete user"); - } else { - this.Users.splice(userIndex, 1); - } } catch (err) { console.error("An error occurred in closeConnection", err); } finally { this.PeerConnectionArray.delete(userId); } - //if user left discussion, clear array peer connection of sharing - if (this.Users.length === 0) { + //if the user left the discussion, clear screen sharing. + if (this.PeerConnectionArray.size === 0) { for (const userId of this.PeerScreenSharingConnectionArray.keys()) { this.closeScreenSharingConnection(userId); - this.PeerScreenSharingConnectionArray.delete(userId); - screenSharingPeerStore.removePeer(userId); } } @@ -285,6 +273,8 @@ export class SimplePeer { } finally { this.PeerScreenSharingConnectionArray.delete(userId); } + + screenSharingPeerStore.removePeer(userId); } public closeAllConnections() { @@ -378,8 +368,8 @@ export class SimplePeer { * Triggered locally when clicking on the screen sharing button */ public sendLocalScreenSharingStream(localScreenCapture: MediaStream) { - for (const user of this.Users) { - this.sendLocalScreenSharingStreamToUser(user.userId, localScreenCapture); + for (const userId of this.PeerConnectionArray.keys()) { + this.sendLocalScreenSharingStreamToUser(userId, localScreenCapture); } } @@ -387,8 +377,8 @@ export class SimplePeer { * Triggered locally when clicking on the screen sharing button */ public stopLocalScreenSharingStream(stream: MediaStream) { - for (const user of this.Users) { - this.stopLocalScreenSharingStreamToUser(user.userId, stream); + for (const userId of this.PeerConnectionArray.keys()) { + this.stopLocalScreenSharingStreamToUser(userId, stream); } } From 52808d44a686d9c047932179444b303157d4f3bc Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Fri, 27 May 2022 15:11:44 -0300 Subject: [PATCH 10/37] Make sure TIAS is offered before AS This is needed to work with Firefox --- front/src/Components/Video/utils.ts | 21 +++++++++------------ front/tests/Components/Video/UtilsTest.ts | 8 ++++++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/front/src/Components/Video/utils.ts b/front/src/Components/Video/utils.ts index 7f650f853e..9d4c6e4d2b 100644 --- a/front/src/Components/Video/utils.ts +++ b/front/src/Components/Video/utils.ts @@ -81,23 +81,20 @@ function updateBandwidthRestriction(sdp: string, bandwidth: integer, mediaType: targetMediaPos !== -1; targetMediaPos = sdp.indexOf(`m=${mediaType}`, targetMediaPos + 1) ) { - const nextMediaPos = sdp.indexOf(`m=`, targetMediaPos + 1); - for (const modifier of ["TIAS", "AS"]) { + // offer TIAS and AS (in this order) + for (const modifier of ["AS", "TIAS"]) { + const nextMediaPos = sdp.indexOf(`m=`, targetMediaPos + 1); const newBandwidth = modifier === "TIAS" ? (bandwidth >>> 0) * 1000 : bandwidth; const nextBWPos = sdp.indexOf(`b=${modifier}:`, targetMediaPos + 1); let mediaSlice = sdp.slice(targetMediaPos); - const mustCreateBWField = nextBWPos === -1 || (nextBWPos > nextMediaPos && nextMediaPos !== -1); - if (mustCreateBWField) { - // insert b= after c= line. - mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${newBandwidth}$2`); - } else { - // update b= with new 'bandwidth' - mediaSlice = mediaSlice.replace( - new RegExp(`b=${modifier}:.*(\r?\n)`), - `b=${modifier}:${newBandwidth}$1` - ); + const bwFieldAlreadyExists = nextBWPos !== -1 && (nextBWPos < nextMediaPos || nextMediaPos === -1); + if (bwFieldAlreadyExists) { + // delete it + mediaSlice = mediaSlice.replace(new RegExp(`b=${modifier}:.*[\r?\n]`), ""); } + // insert b= after c= line. + mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${newBandwidth}$2`); // update the sdp sdp = sdp.slice(0, targetMediaPos) + mediaSlice; diff --git a/front/tests/Components/Video/UtilsTest.ts b/front/tests/Components/Video/UtilsTest.ts index 219f8e9733..a88b8cce69 100644 --- a/front/tests/Components/Video/UtilsTest.ts +++ b/front/tests/Components/Video/UtilsTest.ts @@ -94,23 +94,25 @@ describe("getSdpTransform()", () => { }); // Expected sdp where the audio and application medias have no 'b' fields - // and the video medias should have 'b=AS:'. + // and the video medias should have 'b=TIAS=:' and 'b=AS:'. const expectedDefaultSdp = (bw: integer) => { return ` m=audio c=IN IP4 0.0.0.0 m=video c=IN IP4 0.0.0.0 + b=TIAS:${bw * 1000} b=AS:${bw} m=video c=IN IP4 0.0.0.0 + b=TIAS:${bw * 1000} b=AS:${bw} m=application c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); }; // Expected sdp where the audio and application medias have a 'b' field - // and the video medias should have 'b=AS:'. + // and the video medias should have 'b=TIAS=:' and 'b=AS:'. const expectedFullBWSdp = (bw: integer) => { return ` m=audio @@ -118,9 +120,11 @@ describe("getSdpTransform()", () => { b=AS:11 m=video c=IN IP4 0.0.0.0 + b=TIAS:${bw * 1000} b=AS:${bw} m=video c=IN IP4 0.0.0.0 + b=TIAS:${bw * 1000} b=AS:${bw} m=application c=IN IP4 0.0.0.0 From 8a72f5f7d5d6c774b25fb16ccdf6dd241e3b8f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 27 May 2022 18:16:34 +0200 Subject: [PATCH 11/37] Trying to fix "reconnecting" screen issue In rare circumstances, the "reconnect" screen stays displayed (even if behind the scene, the reconnection has successfully happened). We try here to fix this by forcing the removal of the errorScreen (even if the scene is not sleeping). Also, we make a difference in the user message between a "reconnection failed" and a "connection did not happen yet, even after 1 second" --- front/src/Phaser/Game/GameScene.ts | 42 +++++++++++++++--------------- front/src/i18n/de-DE/warning.ts | 2 ++ front/src/i18n/en-US/warning.ts | 2 ++ front/src/i18n/fr-FR/warning.ts | 2 ++ front/src/i18n/zh-CN/warning.ts | 2 ++ 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 8aacf72bb4..f487517103 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -601,20 +601,22 @@ export class GameScene extends DirtyScene { if (!this.room.isDisconnected()) { if (this.isReconnecting) { setTimeout(() => { - this.scene.sleep(); - if (get(errorScreenStore)) { - // If an error message is already displayed, don't display the "connection lost" message. - return; + if (this.connection === undefined) { + this.scene.sleep(); + if (get(errorScreenStore)) { + // If an error message is already displayed, don't display the "connection lost" message. + return; + } + errorScreenStore.setError( + ErrorScreenMessage.fromPartial({ + type: "reconnecting", + code: "CONNECTION_LOST", + title: get(LL).warning.connectionLostTitle(), + details: get(LL).warning.connectionLostSubtitle(), + }) + ); + //this.scene.launch(ReconnectingSceneName); } - errorScreenStore.setError( - ErrorScreenMessage.fromPartial({ - type: "reconnecting", - code: "CONNECTION_LOST", - title: get(LL).warning.connectionLostTitle(), - details: get(LL).warning.connectionLostSubtitle(), - }) - ); - //this.scene.launch(ReconnectingSceneName); }, 0); } else if (this.connection === undefined) { // Let's wait 1 second before printing the "connecting" screen to avoid blinking @@ -628,9 +630,9 @@ export class GameScene extends DirtyScene { errorScreenStore.setError( ErrorScreenMessage.fromPartial({ type: "reconnecting", - code: "CONNECTION_LOST", - title: get(LL).warning.connectionLostTitle(), - details: get(LL).warning.connectionLostSubtitle(), + code: "CONNECTION_PENDING", + title: get(LL).warning.waitingConnectionTitle(), + details: get(LL).warning.waitingConnectionSubtitle(), }) ); //this.scene.launch(ReconnectingSceneName); @@ -847,11 +849,9 @@ export class GameScene extends DirtyScene { this.connectionAnswerPromiseDeferred.resolve(onConnect.room); // Analyze tags to find if we are admin. If yes, show console. - if (this.scene.isSleeping()) { - const error = get(errorScreenStore); - if (error && error?.type === "reconnecting") errorScreenStore.delete(); - //this.scene.stop(ReconnectingSceneName); - } + const error = get(errorScreenStore); + if (error && error?.type === "reconnecting") errorScreenStore.delete(); + //this.scene.stop(ReconnectingSceneName); //init user position and play trigger to check layers properties this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y); diff --git a/front/src/i18n/de-DE/warning.ts b/front/src/i18n/de-DE/warning.ts index 4973985407..f3d832c23f 100644 --- a/front/src/i18n/de-DE/warning.ts +++ b/front/src/i18n/de-DE/warning.ts @@ -16,6 +16,8 @@ const warning: NonNullable = { connectionLost: "Verbindungen unterbrochen. Wiederverbinden...", connectionLostTitle: "Verbindungen unterbrochen", connectionLostSubtitle: "Wiederverbinden", + waitingConnectionTitle: "Auf Verbindung warten", + waitingConnectionSubtitle: "Verbinden", }; export default warning; diff --git a/front/src/i18n/en-US/warning.ts b/front/src/i18n/en-US/warning.ts index ee235c4fed..b3dcecbcca 100644 --- a/front/src/i18n/en-US/warning.ts +++ b/front/src/i18n/en-US/warning.ts @@ -15,6 +15,8 @@ const warning: BaseTranslation = { connectionLost: "Connection lost. Reconnecting...", connectionLostTitle: "Connection lost", connectionLostSubtitle: "Reconnecting", + waitingConnectionTitle: "Waiting for connection", + waitingConnectionSubtitle: "Connecting", }; export default warning; diff --git a/front/src/i18n/fr-FR/warning.ts b/front/src/i18n/fr-FR/warning.ts index f063b4ed4c..e8895dbd0b 100644 --- a/front/src/i18n/fr-FR/warning.ts +++ b/front/src/i18n/fr-FR/warning.ts @@ -15,6 +15,8 @@ const warning: NonNullable = { connectionLost: "Connexion perdue. Reconnexion...", connectionLostTitle: "Connexion perdue", connectionLostSubtitle: "Reconnexion", + waitingConnectionTitle: "En attente du serveur", + waitingConnectionSubtitle: "Connexion", }; export default warning; diff --git a/front/src/i18n/zh-CN/warning.ts b/front/src/i18n/zh-CN/warning.ts index 8f09291676..090692cfcc 100644 --- a/front/src/i18n/zh-CN/warning.ts +++ b/front/src/i18n/zh-CN/warning.ts @@ -15,6 +15,8 @@ const warning: NonNullable = { connectionLost: "连接丢失。重新连接中...", connectionLostTitle: "连接丢失。", connectionLostSubtitle: "重新连接中", + waitingConnectionTitle: "Waiting for connection", // TODO: translate + waitingConnectionSubtitle: "Connecting", // TODO: translate }; export default warning; From e9eb409b623cafc5df242ddd8fd6c570c7bc3527 Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Tue, 30 Aug 2022 14:53:15 -0300 Subject: [PATCH 12/37] Fix the bbb moderator tag not to being found The ModeratorTagFinder constructs an object with the meetingId that is setted in the map. But the meetingId being passed was not the same as the one defined in the map because it was hashed on the front. So we need to pass the original meetingId (called localMeetingId here) along with the hashed one (called just meetingId) so that we can look for the tags in the ModeratorTagFinder. --- back/src/Services/SocketManager.ts | 8 +++++++- front/src/Connexion/RoomConnection.ts | 2 ++ messages/protos/messages.proto | 5 +++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index d04dc7ce7c..158a519137 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -672,6 +672,7 @@ export class SocketManager { joinBBBMeetingQuery: JoinBBBMeetingQuery ): Promise { const meetingId = joinBBBMeetingQuery.getMeetingid(); + const localMeetingId = joinBBBMeetingQuery.getLocalmeetingid(); const meetingName = joinBBBMeetingQuery.getMeetingname(); const bbbSettings = gameRoom.getBbbSettings(); @@ -687,7 +688,7 @@ export class SocketManager { if (user.tags.includes("admin")) { isAdmin = true; } else { - const moderatorTag = await gameRoom.getModeratorTagForBbbMeeting(meetingId); + const moderatorTag = await gameRoom.getModeratorTagForBbbMeeting(localMeetingId); if (moderatorTag && user.tags.includes(moderatorTag)) { isAdmin = true; } else if (moderatorTag === undefined) { @@ -724,6 +725,11 @@ export class SocketManager { userID: user.id, joinViaHtml5: true, }); + console.log( + `User "${user.name}" (${user.uuid}) joined the BBB meeting "${meetingName}" as ${ + isAdmin ? "Admin" : "Participant" + }.` + ); const bbbMeetingAnswer = new JoinBBBMeetingAnswer(); bbbMeetingAnswer.setMeetingid(meetingId); diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 6fe47d2816..a2e2e5dba5 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -990,11 +990,13 @@ export class RoomConnection implements RoomConnection { props: Map ): Promise { const meetingName = props.get("meetingName") as string; + const localMeetingId = props.get("bbbMeeting") as string; const answer = await this.query({ $case: "joinBBBMeetingQuery", joinBBBMeetingQuery: { meetingId, + localMeetingId, meetingName, }, }); diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 24e437d01b..6ee81545b9 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -175,8 +175,9 @@ message JitsiJwtQuery { } message JoinBBBMeetingQuery { - string meetingId = 1; - string meetingName = 2; + string meetingId = 1; /* a hash of domain, instance and localMeetingId */ + string localMeetingId = 2; /* bbbMeeting field from the map */ + string meetingName = 3; // Fix me Pusher linter fails because eslint-plugin version < 3.0 // map userdata = 3; } From 667750411fea53b8dd03c7cfbb21fc697b59aece Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Tue, 10 May 2022 18:35:32 -0300 Subject: [PATCH 13/37] Add an option to limit video bandwidth The following environment variables are used to restrict video or screen share bandwidth (in kbps). PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS --- docker-compose.yaml | 2 + front/package.json | 1 + front/src/Components/Video/utils.ts | 46 ++++++++ front/src/Enum/EnvironmentVariable.ts | 6 + front/src/WebRtc/ScreenSharingPeer.ts | 4 +- front/src/WebRtc/VideoPeer.ts | 4 +- front/tests/Components/Video/UtilsTest.ts | 129 ++++++++++++++++++++++ front/vite.config.ts | 2 + front/yarn.lock | 12 ++ 9 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 front/tests/Components/Video/UtilsTest.ts diff --git a/docker-compose.yaml b/docker-compose.yaml index f79a7b3d1d..3ff513a757 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -63,6 +63,8 @@ services: ENABLE_OPENID: "$OPID_CLIENT_ID" OPID_PROFILE_SCREEN_PROVIDER: "$OPID_PROFILE_SCREEN_PROVIDER" CHAT_URL: //chat.workadventure.localhost + PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS: "$PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS" + PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS: "$PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS" command: yarn run start volumes: - ./front:/usr/src/app diff --git a/front/package.json b/front/package.json index 969b80b996..987eff2ef5 100644 --- a/front/package.json +++ b/front/package.json @@ -79,6 +79,7 @@ "ts-proto": "^1.117.0", "typesafe-i18n": "^5.4.0", "uuidv4": "^6.2.10", + "webrtc-adapter": "^8.1.1", "zod": "^3.14.3" }, "scripts": { diff --git a/front/src/Components/Video/utils.ts b/front/src/Components/Video/utils.ts index b97f5015e3..76e889f578 100644 --- a/front/src/Components/Video/utils.ts +++ b/front/src/Components/Video/utils.ts @@ -1,5 +1,6 @@ import type { UserSimplePeerInterface } from "../../WebRtc/SimplePeer"; import { STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER } from "../../Enum/EnvironmentVariable"; +import adapter from "webrtc-adapter"; export function getColorByString(str: string): string | null { let hash = 0; @@ -63,3 +64,48 @@ export function getIceServersConfig(user: UserSimplePeerInterface): RTCIceServer } return config; } + +export function getSdpTransform(videoBandwidth = 0) { + return (sdp: string) => { + sdp = updateBandwidthRestriction(sdp, videoBandwidth, "video"); + + return sdp; + }; +} + +function updateBandwidthRestriction(sdp: string, bandwidth: integer, mediaType: string): string { + if (bandwidth <= 0) { + return sdp; + } + + let modifier = "AS"; + // Firefox doesn't support "AS" + if (adapter.browserDetails.browser === "firefox") { + bandwidth = (bandwidth >>> 0) * 1000; + modifier = "TIAS"; + } + + for ( + let targetMediaPos = sdp.indexOf(`m=${mediaType}`); + targetMediaPos !== -1; + targetMediaPos = sdp.indexOf(`m=${mediaType}`, targetMediaPos + 1) + ) { + const nextMediaPos = sdp.indexOf(`m=`, targetMediaPos + 1); + const nextBWPos = sdp.indexOf(`b=${modifier}:`, targetMediaPos + 1); + + let mediaSlice = sdp.slice(targetMediaPos); + const mustCreateBWField = nextBWPos === -1 || (nextBWPos > nextMediaPos && nextMediaPos !== -1); + if (mustCreateBWField) { + // insert b= after c= line. + mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${bandwidth}$2`); + } else { + // update b= with new 'bandwidth' + mediaSlice = mediaSlice.replace(new RegExp(`b=${modifier}:.*(\r?\n)`), `b=${modifier}:${bandwidth}$1`); + } + + // update the sdp + sdp = sdp.slice(0, targetMediaPos) + mediaSlice; + } + + return sdp; +} diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 241cdda320..6c05037afe 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -31,6 +31,10 @@ export const OPID_PROFILE_SCREEN_PROVIDER = getEnvConfig("OPID_PROFILE_SCREEN_PROVIDER") || (ADMIN_URL ? ADMIN_URL + "/profile" : undefined); const FALLBACK_LOCALE = getEnvConfig("FALLBACK_LOCALE") || undefined; export const CHAT_URL = getEnvConfig("CHAT_URL") || "//chat.workadventure.localhost"; +const PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS = parseInt(getEnvConfig("PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS") || "0"); +const PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS = parseInt( + getEnvConfig("PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS") || "0" +); export { DEBUG_MODE, @@ -49,4 +53,6 @@ export { JITSI_PRIVATE_MODE, ENABLE_FEATURE_MAP_EDITOR, FALLBACK_LOCALE, + PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS, + PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS, }; diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index 7cfd8c92e9..a4fde8d3cc 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -2,11 +2,12 @@ import type { RoomConnection } from "../Connexion/RoomConnection"; import { MESSAGE_TYPE_CONSTRAINT, PeerStatus } from "./VideoPeer"; import type { UserSimplePeerInterface } from "./SimplePeer"; import { Readable, readable, writable, Writable } from "svelte/store"; -import { getIceServersConfig } from "../Components/Video/utils"; +import { getIceServersConfig, getSdpTransform } from "../Components/Video/utils"; import { highlightedEmbedScreen } from "../Stores/EmbedScreensStore"; import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils"; import Peer from "simple-peer/simplepeer.min.js"; import { Buffer } from "buffer"; +import { PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS } from "../Enum/EnvironmentVariable"; /** * A peer connection used to transmit video / audio signals between 2 peers. @@ -35,6 +36,7 @@ export class ScreenSharingPeer extends Peer { config: { iceServers: getIceServersConfig(user), }, + sdpTransform: getSdpTransform(PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS), }); this.userId = user.userId; diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index 5fa07ae665..4e271fce21 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -12,12 +12,13 @@ import { newChatMessageWritingStatusSubject, writingStatusMessageStore, } from "../Stores/ChatStore"; -import { getIceServersConfig } from "../Components/Video/utils"; +import { getIceServersConfig, getSdpTransform } from "../Components/Video/utils"; import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils"; import { SoundMeter } from "../Phaser/Components/SoundMeter"; import Peer from "simple-peer/simplepeer.min.js"; import { Buffer } from "buffer"; import { gameManager } from "../Phaser/Game/GameManager"; +import { PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS } from "../Enum/EnvironmentVariable"; export type PeerStatus = "connecting" | "connected" | "error" | "closed"; @@ -60,6 +61,7 @@ export class VideoPeer extends Peer { config: { iceServers: getIceServersConfig(user), }, + sdpTransform: getSdpTransform(PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS), }); this.userId = user.userId; diff --git a/front/tests/Components/Video/UtilsTest.ts b/front/tests/Components/Video/UtilsTest.ts new file mode 100644 index 0000000000..219f8e9733 --- /dev/null +++ b/front/tests/Components/Video/UtilsTest.ts @@ -0,0 +1,129 @@ +import "jasmine"; + +import { getSdpTransform } from "../../../src/Components/Video/utils"; + +describe("getSdpTransform()", () => { + it("should not do anything if bandwidth = 0", () => { + const bw = 0; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:22 + m=application + c=IN IP4 0.0.0.0 + b=AS:33`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(originalSdp); + }); + + it("should create the 'b' field of the video media if it doesn't exist", () => { + const bw = 55; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedDefaultSdp(bw)); + }); + + it("should update the 'b' field of the video media if it already exists", () => { + const bw = 66; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + b=AS:22 + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedDefaultSdp(bw)); + }); + + it("should create and update the 'b' field of each video media if one doesn't exist and the other does", () => { + const bw = 77; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedDefaultSdp(bw)); + }); + + it("should not change the 'b' field of other medias", () => { + const bw = 88; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + b=AS:22 + m=video + c=IN IP4 0.0.0.0 + b=AS:33 + m=application + c=IN IP4 0.0.0.0 + b=AS:44`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedFullBWSdp(bw)); + }); + + // Expected sdp where the audio and application medias have no 'b' fields + // and the video medias should have 'b=AS:'. + const expectedDefaultSdp = (bw: integer) => { + return ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + }; + + // Expected sdp where the audio and application medias have a 'b' field + // and the video medias should have 'b=AS:'. + const expectedFullBWSdp = (bw: integer) => { + return ` + m=audio + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=application + c=IN IP4 0.0.0.0 + b=AS:44`.replace(/^[\s]*/gm, ""); + }; +}); diff --git a/front/vite.config.ts b/front/vite.config.ts index a07eaa3987..7711a28b80 100644 --- a/front/vite.config.ts +++ b/front/vite.config.ts @@ -48,6 +48,8 @@ export default defineConfig({ "OPID_PROFILE_SCREEN_PROVIDER", "FALLBACK_LOCALE", "CHAT_URL", + "PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS", + "PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS", ], }), pluginRewriteAll(), diff --git a/front/yarn.lock b/front/yarn.lock index c92ad9e5fe..3e4a08bb3a 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -3125,6 +3125,11 @@ sass@^1.49.7: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" +sdp@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sdp/-/sdp-3.0.3.tgz#669958d54663ea9f4a46cc66518c9d980c52c61e" + integrity sha512-8EkfckS+XZQaPLyChu4ey7PghrdcraCVNpJe2Gfdi2ON1ylQ7OasuKX+b37R9slnRChwIAiQgt+oj8xXGD8x+A== + "semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -3758,6 +3763,13 @@ webfontloader@^1.6.28: resolved "https://registry.yarnpkg.com/webfontloader/-/webfontloader-1.6.28.tgz#db786129253cb6e8eae54c2fb05f870af6675bae" integrity sha512-Egb0oFEga6f+nSgasH3E0M405Pzn6y3/9tOVanv/DLfa1YBIgcv90L18YyWnvXkRbIM17v5Kv6IT2N6g1x5tvQ== +webrtc-adapter@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-8.1.1.tgz#e4a4dfd1b5085d119da40c4efc0147f7d0961cba" + integrity sha512-1yXevP7TeZGmklEXkvQVrZp3fOSJlLeXNGCA7NovQokxgP3/e2T3EVGL0eKU87S9vKppWjvRWqnJeSANEspOBg== + dependencies: + sdp "^3.0.2" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" From 8810e8a46e002c1a3196ae8164c6d23669de08a5 Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Mon, 16 May 2022 14:29:27 -0300 Subject: [PATCH 14/37] Negotiate both AS and TIAS modifiers in sdp --- front/package.json | 1 - front/src/Components/Video/utils.ts | 38 ++++++++++++++--------------- front/yarn.lock | 12 --------- 3 files changed, 18 insertions(+), 33 deletions(-) diff --git a/front/package.json b/front/package.json index 987eff2ef5..969b80b996 100644 --- a/front/package.json +++ b/front/package.json @@ -79,7 +79,6 @@ "ts-proto": "^1.117.0", "typesafe-i18n": "^5.4.0", "uuidv4": "^6.2.10", - "webrtc-adapter": "^8.1.1", "zod": "^3.14.3" }, "scripts": { diff --git a/front/src/Components/Video/utils.ts b/front/src/Components/Video/utils.ts index 76e889f578..659467138a 100644 --- a/front/src/Components/Video/utils.ts +++ b/front/src/Components/Video/utils.ts @@ -1,6 +1,5 @@ import type { UserSimplePeerInterface } from "../../WebRtc/SimplePeer"; import { STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER } from "../../Enum/EnvironmentVariable"; -import adapter from "webrtc-adapter"; export function getColorByString(str: string): string | null { let hash = 0; @@ -78,33 +77,32 @@ function updateBandwidthRestriction(sdp: string, bandwidth: integer, mediaType: return sdp; } - let modifier = "AS"; - // Firefox doesn't support "AS" - if (adapter.browserDetails.browser === "firefox") { - bandwidth = (bandwidth >>> 0) * 1000; - modifier = "TIAS"; - } - for ( let targetMediaPos = sdp.indexOf(`m=${mediaType}`); targetMediaPos !== -1; targetMediaPos = sdp.indexOf(`m=${mediaType}`, targetMediaPos + 1) ) { const nextMediaPos = sdp.indexOf(`m=`, targetMediaPos + 1); - const nextBWPos = sdp.indexOf(`b=${modifier}:`, targetMediaPos + 1); + for (const modifier of ["TIAS", "AS"]) { + const newBandwidth = modifier === "TIAS" ? (bandwidth >>> 0) * 1000 : bandwidth; + const nextBWPos = sdp.indexOf(`b=${modifier}:`, targetMediaPos + 1); - let mediaSlice = sdp.slice(targetMediaPos); - const mustCreateBWField = nextBWPos === -1 || (nextBWPos > nextMediaPos && nextMediaPos !== -1); - if (mustCreateBWField) { - // insert b= after c= line. - mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${bandwidth}$2`); - } else { - // update b= with new 'bandwidth' - mediaSlice = mediaSlice.replace(new RegExp(`b=${modifier}:.*(\r?\n)`), `b=${modifier}:${bandwidth}$1`); - } + let mediaSlice = sdp.slice(targetMediaPos); + const mustCreateBWField = nextBWPos === -1 || (nextBWPos > nextMediaPos && nextMediaPos !== -1); + if (mustCreateBWField) { + // insert b= after c= line. + mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${newBandwidth}$2`); + } else { + // update b= with new 'bandwidth' + mediaSlice = mediaSlice.replace( + new RegExp(`b=${modifier}:.*(\r?\n)`), + `b=${modifier}:${newBandwidth}$1` + ); + } - // update the sdp - sdp = sdp.slice(0, targetMediaPos) + mediaSlice; + // update the sdp + sdp = sdp.slice(0, targetMediaPos) + mediaSlice; + } } return sdp; diff --git a/front/yarn.lock b/front/yarn.lock index 3e4a08bb3a..c92ad9e5fe 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -3125,11 +3125,6 @@ sass@^1.49.7: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" -sdp@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/sdp/-/sdp-3.0.3.tgz#669958d54663ea9f4a46cc66518c9d980c52c61e" - integrity sha512-8EkfckS+XZQaPLyChu4ey7PghrdcraCVNpJe2Gfdi2ON1ylQ7OasuKX+b37R9slnRChwIAiQgt+oj8xXGD8x+A== - "semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -3763,13 +3758,6 @@ webfontloader@^1.6.28: resolved "https://registry.yarnpkg.com/webfontloader/-/webfontloader-1.6.28.tgz#db786129253cb6e8eae54c2fb05f870af6675bae" integrity sha512-Egb0oFEga6f+nSgasH3E0M405Pzn6y3/9tOVanv/DLfa1YBIgcv90L18YyWnvXkRbIM17v5Kv6IT2N6g1x5tvQ== -webrtc-adapter@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-8.1.1.tgz#e4a4dfd1b5085d119da40c4efc0147f7d0961cba" - integrity sha512-1yXevP7TeZGmklEXkvQVrZp3fOSJlLeXNGCA7NovQokxgP3/e2T3EVGL0eKU87S9vKppWjvRWqnJeSANEspOBg== - dependencies: - sdp "^3.0.2" - which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" From 562cf7d2d60dd11c70008561180930c125ca4c15 Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Fri, 27 May 2022 15:11:44 -0300 Subject: [PATCH 15/37] Make sure TIAS is offered before AS This is needed to work with Firefox --- front/src/Components/Video/utils.ts | 21 +++++++++------------ front/tests/Components/Video/UtilsTest.ts | 8 ++++++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/front/src/Components/Video/utils.ts b/front/src/Components/Video/utils.ts index 659467138a..ce671bad95 100644 --- a/front/src/Components/Video/utils.ts +++ b/front/src/Components/Video/utils.ts @@ -82,23 +82,20 @@ function updateBandwidthRestriction(sdp: string, bandwidth: integer, mediaType: targetMediaPos !== -1; targetMediaPos = sdp.indexOf(`m=${mediaType}`, targetMediaPos + 1) ) { - const nextMediaPos = sdp.indexOf(`m=`, targetMediaPos + 1); - for (const modifier of ["TIAS", "AS"]) { + // offer TIAS and AS (in this order) + for (const modifier of ["AS", "TIAS"]) { + const nextMediaPos = sdp.indexOf(`m=`, targetMediaPos + 1); const newBandwidth = modifier === "TIAS" ? (bandwidth >>> 0) * 1000 : bandwidth; const nextBWPos = sdp.indexOf(`b=${modifier}:`, targetMediaPos + 1); let mediaSlice = sdp.slice(targetMediaPos); - const mustCreateBWField = nextBWPos === -1 || (nextBWPos > nextMediaPos && nextMediaPos !== -1); - if (mustCreateBWField) { - // insert b= after c= line. - mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${newBandwidth}$2`); - } else { - // update b= with new 'bandwidth' - mediaSlice = mediaSlice.replace( - new RegExp(`b=${modifier}:.*(\r?\n)`), - `b=${modifier}:${newBandwidth}$1` - ); + const bwFieldAlreadyExists = nextBWPos !== -1 && (nextBWPos < nextMediaPos || nextMediaPos === -1); + if (bwFieldAlreadyExists) { + // delete it + mediaSlice = mediaSlice.replace(new RegExp(`b=${modifier}:.*[\r?\n]`), ""); } + // insert b= after c= line. + mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${newBandwidth}$2`); // update the sdp sdp = sdp.slice(0, targetMediaPos) + mediaSlice; diff --git a/front/tests/Components/Video/UtilsTest.ts b/front/tests/Components/Video/UtilsTest.ts index 219f8e9733..a88b8cce69 100644 --- a/front/tests/Components/Video/UtilsTest.ts +++ b/front/tests/Components/Video/UtilsTest.ts @@ -94,23 +94,25 @@ describe("getSdpTransform()", () => { }); // Expected sdp where the audio and application medias have no 'b' fields - // and the video medias should have 'b=AS:'. + // and the video medias should have 'b=TIAS=:' and 'b=AS:'. const expectedDefaultSdp = (bw: integer) => { return ` m=audio c=IN IP4 0.0.0.0 m=video c=IN IP4 0.0.0.0 + b=TIAS:${bw * 1000} b=AS:${bw} m=video c=IN IP4 0.0.0.0 + b=TIAS:${bw * 1000} b=AS:${bw} m=application c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); }; // Expected sdp where the audio and application medias have a 'b' field - // and the video medias should have 'b=AS:'. + // and the video medias should have 'b=TIAS=:' and 'b=AS:'. const expectedFullBWSdp = (bw: integer) => { return ` m=audio @@ -118,9 +120,11 @@ describe("getSdpTransform()", () => { b=AS:11 m=video c=IN IP4 0.0.0.0 + b=TIAS:${bw * 1000} b=AS:${bw} m=video c=IN IP4 0.0.0.0 + b=TIAS:${bw * 1000} b=AS:${bw} m=application c=IN IP4 0.0.0.0 From 9554749684e4e7968a63e0f09349ac091b372cff Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Thu, 21 Jul 2022 12:04:41 -0300 Subject: [PATCH 16/37] Set allowfullscreen on the BBB iframe --- front/src/WebRtc/CoWebsite/BBBCoWebsite.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/front/src/WebRtc/CoWebsite/BBBCoWebsite.ts b/front/src/WebRtc/CoWebsite/BBBCoWebsite.ts index a48868f0ad..a0d0b2b7d2 100644 --- a/front/src/WebRtc/CoWebsite/BBBCoWebsite.ts +++ b/front/src/WebRtc/CoWebsite/BBBCoWebsite.ts @@ -17,7 +17,13 @@ export class BBBCoWebsite extends SimpleCoWebsite { load(): CancelablePromise { inExternalServiceStore.set(true); - return super.load(); + const loadIframe = super.load(); + + if (this.iframe) { + this.iframe.allowFullscreen = true; + } + + return loadIframe; } unload(): Promise { From 0be741eb832f1cdfdb250b5c8622ec5d9f090139 Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Thu, 21 Jul 2022 12:43:04 -0300 Subject: [PATCH 17/37] Add screen-wake-lock policy to the BBB iframe --- front/src/WebRtc/BBBFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/WebRtc/BBBFactory.ts b/front/src/WebRtc/BBBFactory.ts index 5cc1f91041..30102b40a4 100644 --- a/front/src/WebRtc/BBBFactory.ts +++ b/front/src/WebRtc/BBBFactory.ts @@ -11,7 +11,7 @@ class BBBFactory { } const allowPolicy = - "microphone *; " + "camera *; " + "display-capture *; " + "clipboard-read *; " + "clipboard-write *;"; + "microphone *; camera *; display-capture *; clipboard-read *; clipboard-write *; screen-wake-lock *;"; const coWebsite = new BBBCoWebsite(new URL(clientURL), false, allowPolicy, undefined, false); coWebsiteManager.addCoWebsiteToStore(coWebsite, 0); coWebsiteManager.loadCoWebsite(coWebsite).catch((e) => console.error(`Error on opening co-website: ${e}`)); From 15441685135e959892bcf5e96d0bdec5f5ff6418 Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Wed, 31 Aug 2022 12:19:56 -0300 Subject: [PATCH 18/37] Simplify allow fullscreen logic --- front/src/WebRtc/BBBFactory.ts | 2 +- front/src/WebRtc/CoWebsite/BBBCoWebsite.ts | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/front/src/WebRtc/BBBFactory.ts b/front/src/WebRtc/BBBFactory.ts index 30102b40a4..1439c4bd6d 100644 --- a/front/src/WebRtc/BBBFactory.ts +++ b/front/src/WebRtc/BBBFactory.ts @@ -11,7 +11,7 @@ class BBBFactory { } const allowPolicy = - "microphone *; camera *; display-capture *; clipboard-read *; clipboard-write *; screen-wake-lock *;"; + "microphone *; camera *; display-capture *; clipboard-read *; clipboard-write *; screen-wake-lock *; fullscreen *"; const coWebsite = new BBBCoWebsite(new URL(clientURL), false, allowPolicy, undefined, false); coWebsiteManager.addCoWebsiteToStore(coWebsite, 0); coWebsiteManager.loadCoWebsite(coWebsite).catch((e) => console.error(`Error on opening co-website: ${e}`)); diff --git a/front/src/WebRtc/CoWebsite/BBBCoWebsite.ts b/front/src/WebRtc/CoWebsite/BBBCoWebsite.ts index a0d0b2b7d2..a48868f0ad 100644 --- a/front/src/WebRtc/CoWebsite/BBBCoWebsite.ts +++ b/front/src/WebRtc/CoWebsite/BBBCoWebsite.ts @@ -17,13 +17,7 @@ export class BBBCoWebsite extends SimpleCoWebsite { load(): CancelablePromise { inExternalServiceStore.set(true); - const loadIframe = super.load(); - - if (this.iframe) { - this.iframe.allowFullscreen = true; - } - - return loadIframe; + return super.load(); } unload(): Promise { From c522431096d9544ab6d39f9555d25f3304430a53 Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Tue, 30 Aug 2022 14:53:15 -0300 Subject: [PATCH 19/37] Fix the bbb moderator tag not to being found The ModeratorTagFinder constructs an object with the meetingId that is setted in the map. But the meetingId being passed was not the same as the one defined in the map because it was hashed on the front. So we need to pass the original meetingId (called localMeetingId here) along with the hashed one (called just meetingId) so that we can look for the tags in the ModeratorTagFinder. --- back/src/Services/SocketManager.ts | 8 +++++++- front/src/Connexion/RoomConnection.ts | 2 ++ messages/protos/messages.proto | 5 +++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index d41e529124..8e503f9702 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -707,6 +707,7 @@ export class SocketManager { joinBBBMeetingQuery: JoinBBBMeetingQuery ): Promise { const meetingId = joinBBBMeetingQuery.getMeetingid(); + const localMeetingId = joinBBBMeetingQuery.getLocalmeetingid(); const meetingName = joinBBBMeetingQuery.getMeetingname(); const bbbSettings = gameRoom.getBbbSettings(); @@ -722,7 +723,7 @@ export class SocketManager { if (user.tags.includes("admin")) { isAdmin = true; } else { - const moderatorTag = await gameRoom.getModeratorTagForBbbMeeting(meetingId); + const moderatorTag = await gameRoom.getModeratorTagForBbbMeeting(localMeetingId); if (moderatorTag && user.tags.includes(moderatorTag)) { isAdmin = true; } else if (moderatorTag === undefined) { @@ -759,6 +760,11 @@ export class SocketManager { userID: user.id, joinViaHtml5: true, }); + console.log( + `User "${user.name}" (${user.uuid}) joined the BBB meeting "${meetingName}" as ${ + isAdmin ? "Admin" : "Participant" + }.` + ); const bbbMeetingAnswer = new JoinBBBMeetingAnswer(); bbbMeetingAnswer.setMeetingid(meetingId); diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 2cc2c0527b..2221335ca8 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -1083,11 +1083,13 @@ export class RoomConnection implements RoomConnection { props: Map ): Promise { const meetingName = props.get("meetingName") as string; + const localMeetingId = props.get("bbbMeeting") as string; const answer = await this.query({ $case: "joinBBBMeetingQuery", joinBBBMeetingQuery: { meetingId, + localMeetingId, meetingName, }, }); diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 665972bdac..75eb4f3913 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -213,8 +213,9 @@ message JitsiJwtQuery { } message JoinBBBMeetingQuery { - string meetingId = 1; - string meetingName = 2; + string meetingId = 1; /* a hash of domain, instance and localMeetingId */ + string localMeetingId = 2; /* bbbMeeting field from the map */ + string meetingName = 3; // Fix me Pusher linter fails because eslint-plugin version < 3.0 // map userdata = 3; } From f602ac47fc24125476b0bd763391d0832df6cd0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Cardinale?= Date: Wed, 21 Sep 2022 11:05:21 +0200 Subject: [PATCH 20/37] Switch to debug instead of console log --- front/chat/src/Xmpp/MucRoom.ts | 2 +- .../src/Controller/IoSocketChatController.ts | 17 +++++----- pusher/src/Services/XmppClient.ts | 31 ++++++++++--------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/front/chat/src/Xmpp/MucRoom.ts b/front/chat/src/Xmpp/MucRoom.ts index c0b1ecd467..e9afb3e225 100644 --- a/front/chat/src/Xmpp/MucRoom.ts +++ b/front/chat/src/Xmpp/MucRoom.ts @@ -104,7 +104,7 @@ enum messageType { react, } -const _VERBOSE = true; +const _VERBOSE = false; export const defaultWoka = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAdCAYAAABBsffGAAAB/ElEQVRIia1WMW7CQBC8EAoqFy74AD1FqNzkAUi09DROwwN4Ag+gMQ09dcQXXNHQIucBPAJFc2Iue+dd40QZycLc7c7N7d7u+cU9wXw+ryyL0+n00eU9tCZIOp1O/f/ZbBbmzuczX6uuRVTlIAYpCSeTScumaZqw0OVyURd47SIGaZ7n6s4wjmc0Grn7/e6yLFtcr9dPaaOGhcTEeDxu2dxut2hXUJ9ioKmW0IidMg6/NPmD1EmqtojTBWAvE26SW8r+YhfIu87zbyB5BiRerVYtikXxXuLRuK058HABMyz/AX8UHwXgV0NRaEXzDKzaw+EQCioo1yrsLfvyjwZrTvK0yp/xh/o+JwbFhFYgFRNqzGEIB1ZhH2INkXJZoShn2WNSgJRNS/qoYSHxer1+qkhChnC320ULRI1LEsNhv99HISBkLmhP/7L8OfqhiKC6SzEJtSTLHMkGFhK6XC79L89rmtC6rv0YfjXV9COPDwtVQxEc2ZflIu7R+WADQrkA7eCH5BdFwQRXQ8bKxXejeWFoYZGCQM7Yh7BAkcw0DEnEEPHhbjBPQfCDvwzlEINlWZq3OAiOx2O0KwAKU8gehXfzu2Wz2VQMTXqCeLZZSNvtVv20MFsu48gQpDvjuHYxE+ZHESBPSJ/x3sqBvhe0hc5vRXkfypBY4xGcc9+lcFxartG6LgAAAABJRU5ErkJggg=="; export const defaultColor = "#626262"; diff --git a/pusher/src/Controller/IoSocketChatController.ts b/pusher/src/Controller/IoSocketChatController.ts index 6dbc43f3f2..cfa6e8c9d8 100644 --- a/pusher/src/Controller/IoSocketChatController.ts +++ b/pusher/src/Controller/IoSocketChatController.ts @@ -24,6 +24,10 @@ import { WebSocket } from "uWebSockets.js"; import { adminService } from "../Services/AdminService"; import { ErrorApiData, isErrorApiData } from "../Messages/JsonMessages/ErrorApiData"; import { apiVersionHash } from "../Messages/JsonMessages/ApiVersion"; +import Jwt from "jsonwebtoken"; +import { MucRoomDefinitionInterface } from "../Messages/JsonMessages/MucRoomDefinitionInterface"; +import { XmppClient } from "../Services/XmppClient"; +import Debug from "debug"; /** * The object passed between the "open" and the "upgrade" methods when opening a websocket @@ -47,9 +51,6 @@ interface UpgradeFailedInvalidData { playUri: string; } -import Jwt from "jsonwebtoken"; -import { MucRoomDefinitionInterface } from "../Messages/JsonMessages/MucRoomDefinitionInterface"; -import { XmppClient } from "../Services/XmppClient"; // eslint-disable-next-line @typescript-eslint/no-var-requires const { jid } = require("@xmpp/client"); @@ -61,6 +62,8 @@ interface UpgradeFailedErrorData { type UpgradeFailedData = UpgradeFailedErrorData | UpgradeFailedInvalidData; +const debug = Debug("ioSocketChatController"); + export class IoSocketChatController { private nextUserId = 1; @@ -230,7 +233,7 @@ export class IoSocketChatController { SocketManager.mergeCharacterLayersAndCustomTextures(characterLayers, memberTextures);*/ if (upgradeAborted.aborted) { - console.log("Ouch! Client disconnected before we could upgrade it!"); + debug("Ouch! Client disconnected before we could upgrade it!"); /* You must not upgrade now */ return; } @@ -318,10 +321,10 @@ export class IoSocketChatController { //let ok = ws.send(message, isBinary); }, drain: (ws) => { - console.log("WebSocket backpressure: " + ws.getBufferedAmount()); + debug("WebSocket backpressure: " + ws.getBufferedAmount()); }, close: (ws) => { - console.log("IoSocketChatController closing ..."); + debug("IoSocketChatController closing ..."); const client = ws as ExSocketInterface; try { client.disconnecting = true; @@ -359,7 +362,7 @@ export class IoSocketChatController { client.jabberPassword = ws.jabberPassword; client.mucRooms = ws.mucRooms; - console.info("IoSocketChatController => initClient => XmppClient"); + debug("IoSocketChatController => initClient => XmppClient"); client.xmppClient = new XmppClient(client, client.mucRooms); return client; diff --git a/pusher/src/Services/XmppClient.ts b/pusher/src/Services/XmppClient.ts index 1f3c72ab83..9242c537e0 100644 --- a/pusher/src/Services/XmppClient.ts +++ b/pusher/src/Services/XmppClient.ts @@ -17,6 +17,9 @@ import { Client, client, xml } from "@xmpp/client"; import { Element } from "@xmpp/xml"; import SASLError from "@xmpp/sasl/lib/SASLError"; import StreamError from "@xmpp/connection/lib/StreamError"; +import Debug from "debug"; + +const debug = Debug("xmppClient"); class ElementExt extends Element {} @@ -60,9 +63,9 @@ export class XmppClient { xmpp.on("error", (err: unknown) => { if (err instanceof SASLError) - console.info("XmppClient => createClient => receive => error", err.name, err.condition); + debug("XmppClient => createClient => receive => error", err.name, err.condition); else { - console.info("XmppClient => createClient => receive => error", err); + debug("XmppClient => createClient => receive => error", err); } this.sendErrorToIframe(err as string); //console.error("XmppClient => receive => error =>", err); @@ -70,15 +73,15 @@ export class XmppClient { }); xmpp.reconnect.on("reconnecting", () => { - console.info("XmppClient => createClient => reconnecting"); + debug("XmppClient => createClient => reconnecting"); }); xmpp.reconnect.on("reconnected", () => { - console.info("XmppClient => createClient => reconnected"); + debug("XmppClient => createClient => reconnected"); }); xmpp.on("offline", () => { - console.info("XmppClient => createClient => offline => status", status); + debug("XmppClient => createClient => offline => status", status); status = "disconnected"; //close en restart connexion @@ -98,7 +101,7 @@ export class XmppClient { }); xmpp.on("disconnect", () => { - console.info( + debug( "XmppClient => createClient => disconnect => status", status, this.clientSocket.disconnecting @@ -118,7 +121,7 @@ export class XmppClient { } }); xmpp.on("online", (address: JID) => { - console.info("XmppClient => createClient => online"); + debug("XmppClient => createClient => online"); xmpp.reconnect.stop(); status = "connected"; //TODO @@ -151,13 +154,13 @@ export class XmppClient { } }); xmpp.on("status", (status: string) => { - console.error("XmppClient => createClient => status => status", status); + debug("XmppClient => createClient => status => status", status); // FIXME: the client keeps trying to reconnect.... even if the pusher is disconnected! }); xmpp.start() .then(() => { - console.log("XmppClient => createClient => start"); + debug("XmppClient => createClient => start"); res(xmpp); }) .catch((err: Error) => { @@ -228,17 +231,17 @@ export class XmppClient { close(): void { //cancel promise - console.info("xmppClient => close"); + debug("xmppClient => close"); this.clientPromise.cancel(); } start(): CancelablePromise { - console.info("xmppClient => start"); + debug("xmppClient => start"); return (this.clientPromise = new CancelablePromise((res, rej, onCancel) => { this.createClient(res, rej); onCancel(() => { (async (): Promise => { - console.info("clientPromise => onCancel => from xmppClient"); + debug("clientPromise => onCancel => from xmppClient"); if (this.timeout) { clearTimeout(this.timeout); this.timeout = undefined; @@ -268,9 +271,9 @@ export class XmppClient { }); }).catch((err) => { if (err instanceof SASLError) { - console.info("clientPromise => receive => error", err.name, err.condition); + debug("clientPromise => receive => error", err.name, err.condition); } else { - console.info("clientPromise => receive => error", err); + debug("clientPromise => receive => error", err); } this.sendErrorToIframe(err.toString()); this.clientPromise.cancel(); From f5d8a86bf2a335d05d95a9fede6e281848f63929 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Fri, 14 Oct 2022 22:01:17 -0300 Subject: [PATCH 21/37] remove restriction to docker login --- .github/workflows/build-test-and-deploy.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/build-test-and-deploy.yml b/.github/workflows/build-test-and-deploy.yml index 57803800c1..060ce14d69 100644 --- a/.github/workflows/build-test-and-deploy.yml +++ b/.github/workflows/build-test-and-deploy.yml @@ -29,7 +29,6 @@ jobs: - name: Login to DockerHub uses: docker/login-action@v1 - if: ${{ github.repository_owner == 'thecodingmachine' }} with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -90,7 +89,6 @@ jobs: - name: Login to DockerHub uses: docker/login-action@v1 - if: ${{ github.repository_owner == 'thecodingmachine' }} with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -151,7 +149,6 @@ jobs: - name: Login to DockerHub uses: docker/login-action@v1 - if: ${{ github.repository_owner == 'thecodingmachine' }} with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -212,7 +209,6 @@ jobs: - name: Login to DockerHub uses: docker/login-action@v1 - if: ${{ github.repository_owner == 'thecodingmachine' }} with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -269,7 +265,6 @@ jobs: - name: Login to DockerHub uses: docker/login-action@v1 - if: ${{ github.repository_owner == 'thecodingmachine' }} with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -325,7 +320,6 @@ jobs: - name: Login to DockerHub uses: docker/login-action@v1 - if: ${{ github.repository_owner == 'thecodingmachine' }} with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -384,7 +378,6 @@ jobs: - name: Login to DockerHub uses: docker/login-action@v1 - if: ${{ github.repository_owner == 'thecodingmachine' }} with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -445,7 +438,6 @@ jobs: - name: Login to DockerHub uses: docker/login-action@v1 - if: ${{ github.repository_owner == 'thecodingmachine' }} with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} From 2a1e0194d0feb90b34905ec6661093cfc041b077 Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Tue, 10 May 2022 18:35:32 -0300 Subject: [PATCH 22/37] Add an option to limit video bandwidth The following environment variables are used to restrict video or screen share bandwidth (in kbps). PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS --- docker-compose.yaml | 2 + front/tests/Components/Video/UtilsTest.ts | 129 ++++++++++++++++++ play/package.json | 1 + play/src/front/Components/Video/utils.ts | 46 +++++++ play/src/front/Enum/EnvironmentVariable.ts | 2 + play/src/front/WebRtc/ScreenSharingPeer.ts | 4 +- play/src/front/WebRtc/VideoPeer.ts | 4 +- .../tests/front/Components/Video/UtilsTest.ts | 129 ++++++++++++++++++ play/yarn.lock | 12 ++ 9 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 front/tests/Components/Video/UtilsTest.ts create mode 100644 play/tests/front/Components/Video/UtilsTest.ts diff --git a/docker-compose.yaml b/docker-compose.yaml index 13addb13cf..c82c496b3d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -95,6 +95,8 @@ services: EJABBERD_WS_URI: ws://ejabberd:5443/ws ENABLE_CHAT: "$ENABLE_CHAT" ENABLE_CHAT_UPLOAD: "$ENABLE_CHAT_UPLOAD" + PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS: "$PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS" + PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS: "$PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS" labels: - "traefik.enable=true" - "traefik.http.routers.front.rule=Host(`front.workadventure.localhost`)" diff --git a/front/tests/Components/Video/UtilsTest.ts b/front/tests/Components/Video/UtilsTest.ts new file mode 100644 index 0000000000..219f8e9733 --- /dev/null +++ b/front/tests/Components/Video/UtilsTest.ts @@ -0,0 +1,129 @@ +import "jasmine"; + +import { getSdpTransform } from "../../../src/Components/Video/utils"; + +describe("getSdpTransform()", () => { + it("should not do anything if bandwidth = 0", () => { + const bw = 0; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:22 + m=application + c=IN IP4 0.0.0.0 + b=AS:33`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(originalSdp); + }); + + it("should create the 'b' field of the video media if it doesn't exist", () => { + const bw = 55; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedDefaultSdp(bw)); + }); + + it("should update the 'b' field of the video media if it already exists", () => { + const bw = 66; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + b=AS:22 + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedDefaultSdp(bw)); + }); + + it("should create and update the 'b' field of each video media if one doesn't exist and the other does", () => { + const bw = 77; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedDefaultSdp(bw)); + }); + + it("should not change the 'b' field of other medias", () => { + const bw = 88; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + b=AS:22 + m=video + c=IN IP4 0.0.0.0 + b=AS:33 + m=application + c=IN IP4 0.0.0.0 + b=AS:44`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedFullBWSdp(bw)); + }); + + // Expected sdp where the audio and application medias have no 'b' fields + // and the video medias should have 'b=AS:'. + const expectedDefaultSdp = (bw: integer) => { + return ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + }; + + // Expected sdp where the audio and application medias have a 'b' field + // and the video medias should have 'b=AS:'. + const expectedFullBWSdp = (bw: integer) => { + return ` + m=audio + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=application + c=IN IP4 0.0.0.0 + b=AS:44`.replace(/^[\s]*/gm, ""); + }; +}); diff --git a/play/package.json b/play/package.json index 3f46be9780..6c0d4674ee 100644 --- a/play/package.json +++ b/play/package.json @@ -138,6 +138,7 @@ "ts-proto-descriptors": "^1.7.1", "typesafe-i18n": "^5.14.0", "uuid": "^9.0.0", + "webrtc-adapter": "^8.1.1", "zod": "^3.19.1" } } diff --git a/play/src/front/Components/Video/utils.ts b/play/src/front/Components/Video/utils.ts index a0ff75aa45..24fa1af042 100644 --- a/play/src/front/Components/Video/utils.ts +++ b/play/src/front/Components/Video/utils.ts @@ -1,5 +1,6 @@ import type { UserSimplePeerInterface } from "../../WebRtc/SimplePeer"; import { STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER } from "../../Enum/EnvironmentVariable"; +import adapter from "webrtc-adapter"; export function getColorByString(str: string): string | null { let hash = 0; @@ -64,3 +65,48 @@ export function getIceServersConfig(user: UserSimplePeerInterface): RTCIceServer } return config; } + +export function getSdpTransform(videoBandwidth = 0) { + return (sdp: string) => { + sdp = updateBandwidthRestriction(sdp, videoBandwidth, "video"); + + return sdp; + }; +} + +function updateBandwidthRestriction(sdp: string, bandwidth: integer, mediaType: string): string { + if (bandwidth <= 0) { + return sdp; + } + + let modifier = "AS"; + // Firefox doesn't support "AS" + if (adapter.browserDetails.browser === "firefox") { + bandwidth = (bandwidth >>> 0) * 1000; + modifier = "TIAS"; + } + + for ( + let targetMediaPos = sdp.indexOf(`m=${mediaType}`); + targetMediaPos !== -1; + targetMediaPos = sdp.indexOf(`m=${mediaType}`, targetMediaPos + 1) + ) { + const nextMediaPos = sdp.indexOf(`m=`, targetMediaPos + 1); + const nextBWPos = sdp.indexOf(`b=${modifier}:`, targetMediaPos + 1); + + let mediaSlice = sdp.slice(targetMediaPos); + const mustCreateBWField = nextBWPos === -1 || (nextBWPos > nextMediaPos && nextMediaPos !== -1); + if (mustCreateBWField) { + // insert b= after c= line. + mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${bandwidth}$2`); + } else { + // update b= with new 'bandwidth' + mediaSlice = mediaSlice.replace(new RegExp(`b=${modifier}:.*(\r?\n)`), `b=${modifier}:${bandwidth}$1`); + } + + // update the sdp + sdp = sdp.slice(0, targetMediaPos) + mediaSlice; + } + + return sdp; +} diff --git a/play/src/front/Enum/EnvironmentVariable.ts b/play/src/front/Enum/EnvironmentVariable.ts index 363c4ee477..54ce8bb962 100644 --- a/play/src/front/Enum/EnvironmentVariable.ts +++ b/play/src/front/Enum/EnvironmentVariable.ts @@ -35,6 +35,8 @@ export const OPID_LOGOUT_REDIRECT_URL = env.OPID_LOGOUT_REDIRECT_URL; export const CHAT_URL = env.CHAT_URL; export const ENABLE_CHAT_UPLOAD = env.ENABLE_CHAT_UPLOAD; export const FALLBACK_LOCALE = env.FALLBACK_LOCALE; +export const PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS = env.PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS; +export const PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS = env.PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS; export const POSITION_DELAY = 200; // Wait 200ms between sending position events export const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player diff --git a/play/src/front/WebRtc/ScreenSharingPeer.ts b/play/src/front/WebRtc/ScreenSharingPeer.ts index 5c84da894d..00a608d063 100644 --- a/play/src/front/WebRtc/ScreenSharingPeer.ts +++ b/play/src/front/WebRtc/ScreenSharingPeer.ts @@ -4,11 +4,12 @@ import { MESSAGE_TYPE_CONSTRAINT } from "./VideoPeer"; import type { UserSimplePeerInterface } from "./SimplePeer"; import type { Readable, Writable } from "svelte/store"; import { readable, writable } from "svelte/store"; -import { getIceServersConfig } from "../Components/Video/utils"; +import { getIceServersConfig, getSdpTransform } from "../Components/Video/utils"; import { highlightedEmbedScreen } from "../Stores/EmbedScreensStore"; import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils"; import Peer from "simple-peer/simplepeer.min.js"; import { Buffer } from "buffer"; +import { PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS } from "../Enum/EnvironmentVariable"; /** * A peer connection used to transmit video / audio signals between 2 peers. @@ -37,6 +38,7 @@ export class ScreenSharingPeer extends Peer { config: { iceServers: getIceServersConfig(user), }, + sdpTransform: getSdpTransform(PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS), }); this.userId = user.userId; diff --git a/play/src/front/WebRtc/VideoPeer.ts b/play/src/front/WebRtc/VideoPeer.ts index 38770072bb..4ea8421da5 100644 --- a/play/src/front/WebRtc/VideoPeer.ts +++ b/play/src/front/WebRtc/VideoPeer.ts @@ -14,12 +14,13 @@ import { newChatMessageWritingStatusSubject, writingStatusMessageStore, } from "../Stores/ChatStore"; -import { getIceServersConfig } from "../Components/Video/utils"; +import { getIceServersConfig, getSdpTransform } from "../Components/Video/utils"; import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils"; import { SoundMeter } from "../Phaser/Components/SoundMeter"; import Peer from "simple-peer/simplepeer.min.js"; import { Buffer } from "buffer"; import { gameManager } from "../Phaser/Game/GameManager"; +import { PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS } from "../Enum/EnvironmentVariable"; export type PeerStatus = "connecting" | "connected" | "error" | "closed"; @@ -63,6 +64,7 @@ export class VideoPeer extends Peer { config: { iceServers: getIceServersConfig(user), }, + sdpTransform: getSdpTransform(PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS), }); this.userId = user.userId; diff --git a/play/tests/front/Components/Video/UtilsTest.ts b/play/tests/front/Components/Video/UtilsTest.ts new file mode 100644 index 0000000000..843e3b338c --- /dev/null +++ b/play/tests/front/Components/Video/UtilsTest.ts @@ -0,0 +1,129 @@ +import "jasmine"; + +import { getSdpTransform } from "../../../src/front/Components/Video/utils"; + +describe("getSdpTransform()", () => { + it("should not do anything if bandwidth = 0", () => { + const bw = 0; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:22 + m=application + c=IN IP4 0.0.0.0 + b=AS:33`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(originalSdp); + }); + + it("should create the 'b' field of the video media if it doesn't exist", () => { + const bw = 55; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedDefaultSdp(bw)); + }); + + it("should update the 'b' field of the video media if it already exists", () => { + const bw = 66; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + b=AS:22 + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedDefaultSdp(bw)); + }); + + it("should create and update the 'b' field of each video media if one doesn't exist and the other does", () => { + const bw = 77; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedDefaultSdp(bw)); + }); + + it("should not change the 'b' field of other medias", () => { + const bw = 88; + const originalSdp = ` + m=audio + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + b=AS:22 + m=video + c=IN IP4 0.0.0.0 + b=AS:33 + m=application + c=IN IP4 0.0.0.0 + b=AS:44`.replace(/^[\s]*/gm, ""); + + const modifiedSdp = getSdpTransform(bw)(originalSdp); + expect(modifiedSdp).toEqual(expectedFullBWSdp(bw)); + }); + + // Expected sdp where the audio and application medias have no 'b' fields + // and the video medias should have 'b=AS:'. + const expectedDefaultSdp = (bw: integer) => { + return ` + m=audio + c=IN IP4 0.0.0.0 + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=application + c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); + }; + + // Expected sdp where the audio and application medias have a 'b' field + // and the video medias should have 'b=AS:'. + const expectedFullBWSdp = (bw: integer) => { + return ` + m=audio + c=IN IP4 0.0.0.0 + b=AS:11 + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=video + c=IN IP4 0.0.0.0 + b=AS:${bw} + m=application + c=IN IP4 0.0.0.0 + b=AS:44`.replace(/^[\s]*/gm, ""); + }; +}); diff --git a/play/yarn.lock b/play/yarn.lock index ac1bf25a88..6ad7cf9b95 100644 --- a/play/yarn.lock +++ b/play/yarn.lock @@ -4489,6 +4489,11 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" +sdp@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sdp/-/sdp-3.0.3.tgz#669958d54663ea9f4a46cc66518c9d980c52c61e" + integrity sha512-8EkfckS+XZQaPLyChu4ey7PghrdcraCVNpJe2Gfdi2ON1ylQ7OasuKX+b37R9slnRChwIAiQgt+oj8xXGD8x+A== + semver@^5.6.0, semver@^5.7.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -5308,6 +5313,13 @@ webidl-conversions@^7.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== +webrtc-adapter@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-8.1.1.tgz#e4a4dfd1b5085d119da40c4efc0147f7d0961cba" + integrity sha512-1yXevP7TeZGmklEXkvQVrZp3fOSJlLeXNGCA7NovQokxgP3/e2T3EVGL0eKU87S9vKppWjvRWqnJeSANEspOBg== + dependencies: + sdp "^3.0.2" + whatwg-encoding@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" From 3cf25889e2a8c38e5c9381074639e52b5b27d35b Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Mon, 16 May 2022 14:29:27 -0300 Subject: [PATCH 23/37] Negotiate both AS and TIAS modifiers in sdp --- play/src/front/Components/Video/utils.ts | 38 +++++++++++------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/play/src/front/Components/Video/utils.ts b/play/src/front/Components/Video/utils.ts index 24fa1af042..4cad674ecb 100644 --- a/play/src/front/Components/Video/utils.ts +++ b/play/src/front/Components/Video/utils.ts @@ -1,6 +1,5 @@ import type { UserSimplePeerInterface } from "../../WebRtc/SimplePeer"; import { STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER } from "../../Enum/EnvironmentVariable"; -import adapter from "webrtc-adapter"; export function getColorByString(str: string): string | null { let hash = 0; @@ -79,33 +78,32 @@ function updateBandwidthRestriction(sdp: string, bandwidth: integer, mediaType: return sdp; } - let modifier = "AS"; - // Firefox doesn't support "AS" - if (adapter.browserDetails.browser === "firefox") { - bandwidth = (bandwidth >>> 0) * 1000; - modifier = "TIAS"; - } - for ( let targetMediaPos = sdp.indexOf(`m=${mediaType}`); targetMediaPos !== -1; targetMediaPos = sdp.indexOf(`m=${mediaType}`, targetMediaPos + 1) ) { const nextMediaPos = sdp.indexOf(`m=`, targetMediaPos + 1); - const nextBWPos = sdp.indexOf(`b=${modifier}:`, targetMediaPos + 1); + for (const modifier of ["TIAS", "AS"]) { + const newBandwidth = modifier === "TIAS" ? (bandwidth >>> 0) * 1000 : bandwidth; + const nextBWPos = sdp.indexOf(`b=${modifier}:`, targetMediaPos + 1); - let mediaSlice = sdp.slice(targetMediaPos); - const mustCreateBWField = nextBWPos === -1 || (nextBWPos > nextMediaPos && nextMediaPos !== -1); - if (mustCreateBWField) { - // insert b= after c= line. - mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${bandwidth}$2`); - } else { - // update b= with new 'bandwidth' - mediaSlice = mediaSlice.replace(new RegExp(`b=${modifier}:.*(\r?\n)`), `b=${modifier}:${bandwidth}$1`); - } + let mediaSlice = sdp.slice(targetMediaPos); + const mustCreateBWField = nextBWPos === -1 || (nextBWPos > nextMediaPos && nextMediaPos !== -1); + if (mustCreateBWField) { + // insert b= after c= line. + mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${newBandwidth}$2`); + } else { + // update b= with new 'bandwidth' + mediaSlice = mediaSlice.replace( + new RegExp(`b=${modifier}:.*(\r?\n)`), + `b=${modifier}:${newBandwidth}$1` + ); + } - // update the sdp - sdp = sdp.slice(0, targetMediaPos) + mediaSlice; + // update the sdp + sdp = sdp.slice(0, targetMediaPos) + mediaSlice; + } } return sdp; From 202fcef447158d90ee05ff108919e7502df28d59 Mon Sep 17 00:00:00 2001 From: Marcos Kintschner Date: Fri, 27 May 2022 15:11:44 -0300 Subject: [PATCH 24/37] Make sure TIAS is offered before AS This is needed to work with Firefox --- front/tests/Components/Video/UtilsTest.ts | 8 ++++++-- play/src/front/Components/Video/utils.ts | 21 +++++++++------------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/front/tests/Components/Video/UtilsTest.ts b/front/tests/Components/Video/UtilsTest.ts index 219f8e9733..a88b8cce69 100644 --- a/front/tests/Components/Video/UtilsTest.ts +++ b/front/tests/Components/Video/UtilsTest.ts @@ -94,23 +94,25 @@ describe("getSdpTransform()", () => { }); // Expected sdp where the audio and application medias have no 'b' fields - // and the video medias should have 'b=AS:'. + // and the video medias should have 'b=TIAS=:' and 'b=AS:'. const expectedDefaultSdp = (bw: integer) => { return ` m=audio c=IN IP4 0.0.0.0 m=video c=IN IP4 0.0.0.0 + b=TIAS:${bw * 1000} b=AS:${bw} m=video c=IN IP4 0.0.0.0 + b=TIAS:${bw * 1000} b=AS:${bw} m=application c=IN IP4 0.0.0.0`.replace(/^[\s]*/gm, ""); }; // Expected sdp where the audio and application medias have a 'b' field - // and the video medias should have 'b=AS:'. + // and the video medias should have 'b=TIAS=:' and 'b=AS:'. const expectedFullBWSdp = (bw: integer) => { return ` m=audio @@ -118,9 +120,11 @@ describe("getSdpTransform()", () => { b=AS:11 m=video c=IN IP4 0.0.0.0 + b=TIAS:${bw * 1000} b=AS:${bw} m=video c=IN IP4 0.0.0.0 + b=TIAS:${bw * 1000} b=AS:${bw} m=application c=IN IP4 0.0.0.0 diff --git a/play/src/front/Components/Video/utils.ts b/play/src/front/Components/Video/utils.ts index 4cad674ecb..226267b2d2 100644 --- a/play/src/front/Components/Video/utils.ts +++ b/play/src/front/Components/Video/utils.ts @@ -83,23 +83,20 @@ function updateBandwidthRestriction(sdp: string, bandwidth: integer, mediaType: targetMediaPos !== -1; targetMediaPos = sdp.indexOf(`m=${mediaType}`, targetMediaPos + 1) ) { - const nextMediaPos = sdp.indexOf(`m=`, targetMediaPos + 1); - for (const modifier of ["TIAS", "AS"]) { + // offer TIAS and AS (in this order) + for (const modifier of ["AS", "TIAS"]) { + const nextMediaPos = sdp.indexOf(`m=`, targetMediaPos + 1); const newBandwidth = modifier === "TIAS" ? (bandwidth >>> 0) * 1000 : bandwidth; const nextBWPos = sdp.indexOf(`b=${modifier}:`, targetMediaPos + 1); let mediaSlice = sdp.slice(targetMediaPos); - const mustCreateBWField = nextBWPos === -1 || (nextBWPos > nextMediaPos && nextMediaPos !== -1); - if (mustCreateBWField) { - // insert b= after c= line. - mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${newBandwidth}$2`); - } else { - // update b= with new 'bandwidth' - mediaSlice = mediaSlice.replace( - new RegExp(`b=${modifier}:.*(\r?\n)`), - `b=${modifier}:${newBandwidth}$1` - ); + const bwFieldAlreadyExists = nextBWPos !== -1 && (nextBWPos < nextMediaPos || nextMediaPos === -1); + if (bwFieldAlreadyExists) { + // delete it + mediaSlice = mediaSlice.replace(new RegExp(`b=${modifier}:.*[\r?\n]`), ""); } + // insert b= after c= line. + mediaSlice = mediaSlice.replace(/c=IN (.*)(\r?\n)/, `c=IN $1$2b=${modifier}:${newBandwidth}$2`); // update the sdp sdp = sdp.slice(0, targetMediaPos) + mediaSlice; From 77229fad663ea245911c72f0cabe067bb4562f5f Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Tue, 8 Nov 2022 00:13:44 -0300 Subject: [PATCH 25/37] Add limit bandwidth vars to FrontConfigInterface --- play/src/common/FrontConfigurationInterface.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/play/src/common/FrontConfigurationInterface.ts b/play/src/common/FrontConfigurationInterface.ts index 9e66e0a944..6fbb1210f1 100644 --- a/play/src/common/FrontConfigurationInterface.ts +++ b/play/src/common/FrontConfigurationInterface.ts @@ -26,4 +26,6 @@ export interface FrontConfigurationInterface { CHAT_URL: string | undefined; ENABLE_CHAT_UPLOAD: boolean; FALLBACK_LOCALE: string | undefined; + PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS: number; + PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS: number; } From e6b3dd1f1ba53ab65bbe07646e6512dd671ae700 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Tue, 8 Nov 2022 00:25:05 -0300 Subject: [PATCH 26/37] Set limit bandwidth properties on pusher --- play/src/pusher/enums/EnvironmentVariable.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/play/src/pusher/enums/EnvironmentVariable.ts b/play/src/pusher/enums/EnvironmentVariable.ts index 1dafe203a6..9b4fd42b8a 100644 --- a/play/src/pusher/enums/EnvironmentVariable.ts +++ b/play/src/pusher/enums/EnvironmentVariable.ts @@ -67,6 +67,8 @@ const EnvironmentVariables = z.object({ POSTHOG_URL: z.string().url().optional().or(z.literal("")), FALLBACK_LOCALE: z.string().optional(), CHAT_URL: z.string().url(), + PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS: PositiveIntAsString.optional(), + PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS: PositiveIntAsString.optional(), }); type EnvironmentVariables = z.infer; @@ -172,4 +174,6 @@ export const FRONT_ENVIRONMENT_VARIABLES: FrontConfigurationInterface = { CHAT_URL: env.CHAT_URL, ENABLE_CHAT_UPLOAD: toBool(env.ENABLE_CHAT_UPLOAD, true), FALLBACK_LOCALE: env.FALLBACK_LOCALE, + PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS: toNumber(env.PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS, 0), + PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS: toNumber(env.PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS, 0), }; From 01a4b6688978488a1f242730a26b431c2b9ff5e4 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Tue, 8 Nov 2022 00:32:35 -0300 Subject: [PATCH 27/37] Fix tests --- play/tests/front/Components/Video/UtilsTest.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/play/tests/front/Components/Video/UtilsTest.ts b/play/tests/front/Components/Video/UtilsTest.ts index 843e3b338c..ee113509c1 100644 --- a/play/tests/front/Components/Video/UtilsTest.ts +++ b/play/tests/front/Components/Video/UtilsTest.ts @@ -1,6 +1,6 @@ -import "jasmine"; - -import { getSdpTransform } from "../../../src/front/Components/Video/utils"; +// @vitest-environment jsdom +import { describe, expect, it } from "vitest"; +import { getSdpTransform } from "../../../../src/front/Components/Video/utils"; describe("getSdpTransform()", () => { it("should not do anything if bandwidth = 0", () => { From 626a8e8a2d2d0e9572d371145d80368feb021fd0 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Thu, 1 Dec 2022 15:33:02 -0300 Subject: [PATCH 28/37] remove e2e build and test --- .github/workflows/build-test-and-deploy.yml | 260 +------------------- 1 file changed, 2 insertions(+), 258 deletions(-) diff --git a/.github/workflows/build-test-and-deploy.yml b/.github/workflows/build-test-and-deploy.yml index 99052c0c5c..1632b47d4d 100644 --- a/.github/workflows/build-test-and-deploy.yml +++ b/.github/workflows/build-test-and-deploy.yml @@ -54,25 +54,6 @@ jobs: cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} - - name: Build test image - uses: docker/build-push-action@v3 - with: - context: . - file: play/Dockerfile - platforms: linux/amd64 - push: false - tags: mconf/workadventure-play:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-from: type=registry,ref=mconf/workadventure-play:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-to: type=inline - labels: ${{ steps.meta.outputs.labels }} - outputs: type=docker,dest=/tmp/play.tar - - - name: Upload image as artifact - uses: actions/upload-artifact@v3 - with: - name: play - path: /tmp/play.tar - build-chat: runs-on: ubuntu-latest @@ -114,25 +95,6 @@ jobs: cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} - - name: Build test image - uses: docker/build-push-action@v3 - with: - context: . - file: chat/Dockerfile - platforms: linux/amd64 - push: false - tags: mconf/workadventure-chat:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-from: type=registry,ref=mconf/workadventure-chat:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-to: type=inline - labels: ${{ steps.meta.outputs.labels }} - outputs: type=docker,dest=/tmp/chat.tar - - - name: Upload image as artifact - uses: actions/upload-artifact@v3 - with: - name: chat - path: /tmp/chat.tar - build-back: runs-on: ubuntu-latest @@ -174,25 +136,6 @@ jobs: cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} - - name: Build test image - uses: docker/build-push-action@v3 - with: - context: . - file: back/Dockerfile - platforms: linux/amd64 - push: false - tags: mconf/workadventure-back:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-from: type=registry,ref=mconf/workadventure-back:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-to: type=inline - labels: ${{ steps.meta.outputs.labels }} - outputs: type=docker,dest=/tmp/back.tar - - - name: Upload image as artifact - uses: actions/upload-artifact@v3 - with: - name: back - path: /tmp/back.tar - build-uploader: runs-on: ubuntu-latest @@ -228,23 +171,6 @@ jobs: cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} - - name: Build test image - uses: docker/build-push-action@v3 - with: - file: uploader/Dockerfile - push: false - tags: mconf/workadventure-uploader:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-from: type=registry,ref=mconf/workadventure-uploader:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-to: type=inline - labels: ${{ steps.meta.outputs.labels }} - outputs: type=docker,dest=/tmp/uploader.tar - - - name: Upload image as artifact - uses: actions/upload-artifact@v3 - with: - name: uploader - path: /tmp/uploader.tar - build-maps: runs-on: ubuntu-latest @@ -284,24 +210,6 @@ jobs: cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} - - name: Build test image - uses: docker/build-push-action@v3 - with: - context: maps/ - file: maps/Dockerfile - push: false - tags: mconf/workadventure-maps:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-from: type=registry,ref=mconf/workadventure-maps:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-to: type=inline - labels: ${{ steps.meta.outputs.labels }} - outputs: type=docker,dest=/tmp/maps.tar - - - name: Upload image as artifact - uses: actions/upload-artifact@v3 - with: - name: maps - path: /tmp/maps.tar - build-map-storage: runs-on: ubuntu-latest @@ -343,25 +251,6 @@ jobs: cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} - - name: Build test image - uses: docker/build-push-action@v3 - with: - context: . - file: map-storage/Dockerfile - platforms: linux/amd64 - push: false - tags: mconf/workadventure-map-storage:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-from: type=registry,ref=mconf/workadventure-map-storage:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-to: type=inline - labels: ${{ steps.meta.outputs.labels }} - outputs: type=docker,dest=/tmp/map-storage.tar - - - name: Upload image as artifact - uses: actions/upload-artifact@v3 - with: - name: map-storage - path: /tmp/map-storage.tar - build-ejabberd: runs-on: ubuntu-latest @@ -397,152 +286,7 @@ jobs: context: xmpp/ platforms: linux/amd64,linux/arm64 push: true - tags: mconf/workadventure-simple-ecs:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-from: type=registry,ref=mconf/workadventure-simple-ecs:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: mconf/workadventure-ejabberd:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + cache-from: type=registry,ref=mconf/workadventure-ejabberd:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} - - - name: Build test image - uses: docker/build-push-action@v3 - with: - context: xmpp/ - platforms: linux/amd64 - push: false - tags: mconf/workadventure-simple-ecs:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-from: type=registry,ref=mconf/workadventure-simple-ecs:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - cache-to: type=inline - labels: ${{ steps.meta.outputs.labels }} - outputs: type=docker,dest=/tmp/ejabberd.tar - - - name: Upload image as artifact - uses: actions/upload-artifact@v3 - with: - name: ejabberd - path: /tmp/ejabberd.tar - - end-to-end-tests: - name: "End to end tests with ${{ matrix.browser }}" - strategy: - fail-fast: false - matrix: - browser: [ 'chromium', 'firefox', 'webkit' ] - needs: - - build-play - - build-chat - - build-back - - build-maps - - build-uploader - - build-map-storage - - build-ejabberd - timeout-minutes: 60 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 - with: - node-version: 16 - - name: Install dependencies - run: npm ci - working-directory: tests - - name: Install Playwright - run: npx playwright install --with-deps ${{ matrix.browser }} - working-directory: tests - - name: 'Setup .env file' - run: cp .env.template .env - - uses: rlespinasse/github-slug-action@3.1.0 - - name: Display pulled version - run: echo "Pulling images with tag ${DOCKER_TAG}" - env: - DOCKER_TAG: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - - - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: play - path: /tmp - - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: chat - path: /tmp - - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: back - path: /tmp - - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: maps - path: /tmp - - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: map-storage - path: /tmp - - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: uploader - path: /tmp - - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: ejabberd - path: /tmp - - - name: Load image - run: | - docker load --input /tmp/play.tar - docker load --input /tmp/chat.tar - docker load --input /tmp/back.tar - docker load --input /tmp/maps.tar - docker load --input /tmp/map-storage.tar - docker load --input /tmp/uploader.tar - docker load --input /tmp/ejabberd.tar - docker image ls -a - -# - name: Download images -# run: docker-compose -f docker-compose.yaml -f docker-compose-oidc.yaml -f docker-compose.e2e.yml pull -# env: -#s DOCKER_TAG: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - - name: Start WorkAdventure - run: docker-compose -f docker-compose.yaml -f docker-compose-oidc.yaml -f docker-compose.e2e.yml up -d - env: - DOCKER_TAG: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - - name: Wait for environment to Start - run: sleep 30 - - name: Run Playwright tests - run: npm run test-prod-like -- --project=${{ matrix.browser }} - working-directory: tests - env: - DOCKER_TAG: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - - name: Display docker-compose logs on failure - run: docker-compose -f docker-compose.yaml -f docker-compose-oidc.yaml -f docker-compose.e2e.yml logs - if: failure() - env: - DOCKER_TAG: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - - name: Display containers state - run: docker-compose -f docker-compose.yaml -f docker-compose-oidc.yaml -f docker-compose.e2e.yml ps - if: failure() - env: - DOCKER_TAG: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - - name: Side-load docker-compose logs in the playwright report - run: docker-compose -f docker-compose.yaml -f docker-compose-oidc.yaml -f docker-compose.e2e.yml logs > tests/playwright-report/docker-compose.log - if: failure() - env: - DOCKER_TAG: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - - uses: actions/upload-artifact@v2 - if: always() - with: - name: playwright-report-${{ matrix.browser }} - path: tests/playwright-report/ - retention-days: 30 - From 852c55fa987b0e97c941c2603bc1c1ba03380213 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Fri, 21 Apr 2023 22:38:21 -0300 Subject: [PATCH 29/37] fix: package-lock.json required webrtc-adapter and sdp 3.2.0 --- package-lock.json | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index c68e8663c4..71c7053522 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18165,9 +18165,9 @@ } }, "node_modules/sdp": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.0.3.tgz", - "integrity": "sha512-8EkfckS+XZQaPLyChu4ey7PghrdcraCVNpJe2Gfdi2ON1ylQ7OasuKX+b37R9slnRChwIAiQgt+oj8xXGD8x+A==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz", + "integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==" }, "node_modules/semver": { "version": "6.3.0", @@ -21119,6 +21119,18 @@ "node": ">=10.13.0" } }, + "node_modules/webrtc-adapter": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.2.tgz", + "integrity": "sha512-jQWwqiAEAFZamWliJo0Q+dIC6ZMJ8BgCFvW/oXWVFby1Nw14dOUfPwZ3lVe4nafDXdTyCUT7xfLt5xXiioXUCQ==", + "dependencies": { + "sdp": "^3.2.0" + }, + "engines": { + "node": ">=6.0.0", + "npm": ">=3.10.0" + } + }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -21651,6 +21663,7 @@ "typesafe-i18n": "^5.14.0", "typescript": "^4.9.4", "uuid": "^9.0.0", + "webrtc-adapter": "^8.1.1", "zod": "^3.19.1" }, "devDependencies": { @@ -36053,9 +36066,9 @@ } }, "sdp": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.0.3.tgz", - "integrity": "sha512-8EkfckS+XZQaPLyChu4ey7PghrdcraCVNpJe2Gfdi2ON1ylQ7OasuKX+b37R9slnRChwIAiQgt+oj8xXGD8x+A==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz", + "integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==" }, "semver": { "version": "6.3.0", @@ -38212,6 +38225,14 @@ "dev": true, "peer": true }, + "webrtc-adapter": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.2.tgz", + "integrity": "sha512-jQWwqiAEAFZamWliJo0Q+dIC6ZMJ8BgCFvW/oXWVFby1Nw14dOUfPwZ3lVe4nafDXdTyCUT7xfLt5xXiioXUCQ==", + "requires": { + "sdp": "^3.2.0" + } + }, "whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -38489,6 +38510,7 @@ "vite": "^3.1.4", "vite-tsconfig-paths": "^3.5.1", "vitest": "^0.25.5", + "webrtc-adapter": "^8.1.1", "zod": "^3.19.1" }, "dependencies": { From 5af132a60973afe3b081fed0ff746c4e2338cdda Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Sun, 2 Jul 2023 18:43:15 -0300 Subject: [PATCH 30/37] fix: parse of max bandwidth for video and screenshare --- play/src/pusher/enums/EnvironmentVariable.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/play/src/pusher/enums/EnvironmentVariable.ts b/play/src/pusher/enums/EnvironmentVariable.ts index 9196c951b2..37dee3344d 100644 --- a/play/src/pusher/enums/EnvironmentVariable.ts +++ b/play/src/pusher/enums/EnvironmentVariable.ts @@ -133,6 +133,6 @@ export const FRONT_ENVIRONMENT_VARIABLES: FrontConfigurationInterface = { JITSI_MUC_DOMAIN: env.JITSI_MUC_DOMAIN, MAP_STORAGE_PATH_PREFIX: env.MAP_STORAGE_PATH_PREFIX, FEATURE_FLAG_BROADCAST_AREAS: env.FEATURE_FLAG_BROADCAST_AREAS, - PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS: toNumber(env.PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS, 0), - PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS: toNumber(env.PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS, 0), + PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS: parseInt(env.PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS || "0"), + PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS: parseInt(env.PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS || "0"), }; From c8015bad0d2339b99a6d5cdbf2bb440d90346140 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Sun, 2 Jul 2023 23:30:06 -0300 Subject: [PATCH 31/37] chore: push images to OCIR --- .github/workflows/build-test-and-deploy.yml | 105 +++++++++++--------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/.github/workflows/build-test-and-deploy.yml b/.github/workflows/build-test-and-deploy.yml index 6f2a22234c..160145c2c1 100644 --- a/.github/workflows/build-test-and-deploy.yml +++ b/.github/workflows/build-test-and-deploy.yml @@ -27,11 +27,12 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - - name: Login to DockerHub + - name: Login to OCIR uses: docker/login-action@v2 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + registry: ${{ CONTAINER_REGISTRY }} + username: ${{ secrets.OCI_USERNAME }} + password: ${{ secrets.OCI_TOKEN }} - uses: rlespinasse/github-slug-action@3.1.0 @@ -39,7 +40,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: mconf/workadventure-play + images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-play - name: Build and push uses: docker/build-push-action@v3 @@ -49,10 +50,10 @@ jobs: file: play/Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: mconf/workadventure-play:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-play:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=mconf/workadventure-play:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=mconf/workadventure-play:develop + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-play:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-play:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} @@ -70,11 +71,12 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - - name: Login to DockerHub + - name: Login to OCIR uses: docker/login-action@v2 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + registry: ${{ CONTAINER_REGISTRY }} + username: ${{ secrets.OCI_USERNAME }} + password: ${{ secrets.OCI_TOKEN }} - uses: rlespinasse/github-slug-action@3.1.0 @@ -82,7 +84,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: mconf/workadventure-chat + images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-chat - name: Build and push uses: docker/build-push-action@v3 @@ -92,10 +94,10 @@ jobs: file: chat/Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: mconf/workadventure-chat:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-chat:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=mconf/workadventure-chat:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=mconf/workadventure-chat:develop + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-chat:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-chat:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} @@ -113,11 +115,12 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - - name: Login to DockerHub + - name: Login to OCIR uses: docker/login-action@v2 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + registry: ${{ CONTAINER_REGISTRY }} + username: ${{ secrets.OCI_USERNAME }} + password: ${{ secrets.OCI_TOKEN }} - uses: rlespinasse/github-slug-action@3.1.0 @@ -125,7 +128,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: mconf/workadventure-back + images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-back - name: Replace version string run: | @@ -141,10 +144,10 @@ jobs: file: back/Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: mconf/workadventure-back:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-back:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=mconf/workadventure-back:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=mconf/workadventure-back:develop + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-back:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-back:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} @@ -158,11 +161,12 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - - name: Login to DockerHub + - name: Login to OCIR uses: docker/login-action@v2 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + registry: ${{ CONTAINER_REGISTRY }} + username: ${{ secrets.OCI_USERNAME }} + password: ${{ secrets.OCI_TOKEN }} - uses: rlespinasse/github-slug-action@3.1.0 @@ -170,7 +174,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: mconf/workadventure-uploader + images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-uploader - name: Build and push uses: docker/build-push-action@v3 @@ -178,10 +182,10 @@ jobs: with: file: uploader/Dockerfile push: true - tags: mconf/workadventure-uploader:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-uploader:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=mconf/workadventure-uploader:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=mconf/workadventure-uploader:develop + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-uploader:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-uploader:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} @@ -198,11 +202,12 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - - name: Login to DockerHub + - name: Login to OCIR uses: docker/login-action@v2 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + registry: ${{ CONTAINER_REGISTRY }} + username: ${{ secrets.OCI_USERNAME }} + password: ${{ secrets.OCI_TOKEN }} - uses: rlespinasse/github-slug-action@3.1.0 @@ -210,7 +215,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: mconf/workadventure-maps + images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-maps - name: Build and push uses: docker/build-push-action@v3 @@ -219,10 +224,10 @@ jobs: context: maps/ file: maps/Dockerfile push: true - tags: mconf/workadventure-maps:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-maps:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=mconf/workadventure-maps:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=mconf/workadventure-maps:develop + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-maps:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-maps:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} @@ -240,11 +245,12 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - - name: Login to DockerHub + - name: Login to OCIR uses: docker/login-action@v2 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + registry: ${{ CONTAINER_REGISTRY }} + username: ${{ secrets.OCI_USERNAME }} + password: ${{ secrets.OCI_TOKEN }} - uses: rlespinasse/github-slug-action@3.1.0 @@ -252,7 +258,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: mconf/workadventure-map-storage + images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-map-storage - name: Build and push uses: docker/build-push-action@v3 @@ -262,10 +268,10 @@ jobs: file: map-storage/Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: mconf/workadventure-map-storage:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-map-storage:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=mconf/workadventure-map-storage:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=mconf/workadventure-map-storage:develop + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-map-storage:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-map-storage:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} @@ -283,11 +289,12 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - - name: Login to DockerHub + - name: Login to OCIR uses: docker/login-action@v2 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + registry: ${{ CONTAINER_REGISTRY }} + username: ${{ secrets.OCI_USERNAME }} + password: ${{ secrets.OCI_TOKEN }} - uses: rlespinasse/github-slug-action@3.1.0 @@ -295,7 +302,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: mconf/workadventure-ejabberd + images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-ejabberd - name: Build and push uses: docker/build-push-action@v3 @@ -304,9 +311,9 @@ jobs: context: xmpp/ platforms: linux/amd64,linux/arm64 push: true - tags: mconf/workadventure-ejabberd:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-ejabberd:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=mconf/workadventure-ejabberd:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=mconf/workadventure-ejabberd:develop + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-ejabberd:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-ejabberd:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} From a3efb03881c5d21e64d45ab06150fc778af57a8b Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Sun, 2 Jul 2023 23:36:58 -0300 Subject: [PATCH 32/37] fix: use vars context for new variables --- .github/workflows/build-test-and-deploy.yml | 70 ++++++++++----------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/.github/workflows/build-test-and-deploy.yml b/.github/workflows/build-test-and-deploy.yml index 160145c2c1..0a28d1fb54 100644 --- a/.github/workflows/build-test-and-deploy.yml +++ b/.github/workflows/build-test-and-deploy.yml @@ -30,7 +30,7 @@ jobs: - name: Login to OCIR uses: docker/login-action@v2 with: - registry: ${{ CONTAINER_REGISTRY }} + registry: ${{ vars.CONTAINER_REGISTRY }} username: ${{ secrets.OCI_USERNAME }} password: ${{ secrets.OCI_TOKEN }} @@ -40,7 +40,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-play + images: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-play - name: Build and push uses: docker/build-push-action@v3 @@ -50,10 +50,10 @@ jobs: file: play/Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-play:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-play:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-play:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-play:develop + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-play:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-play:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} @@ -74,7 +74,7 @@ jobs: - name: Login to OCIR uses: docker/login-action@v2 with: - registry: ${{ CONTAINER_REGISTRY }} + registry: ${{ vars.CONTAINER_REGISTRY }} username: ${{ secrets.OCI_USERNAME }} password: ${{ secrets.OCI_TOKEN }} @@ -84,7 +84,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-chat + images: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-chat - name: Build and push uses: docker/build-push-action@v3 @@ -94,10 +94,10 @@ jobs: file: chat/Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-chat:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-chat:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-chat:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-chat:develop + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-chat:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-chat:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} @@ -118,7 +118,7 @@ jobs: - name: Login to OCIR uses: docker/login-action@v2 with: - registry: ${{ CONTAINER_REGISTRY }} + registry: ${{ vars.CONTAINER_REGISTRY }} username: ${{ secrets.OCI_USERNAME }} password: ${{ secrets.OCI_TOKEN }} @@ -128,7 +128,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-back + images: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-back - name: Replace version string run: | @@ -144,10 +144,10 @@ jobs: file: back/Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-back:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-back:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-back:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-back:develop + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-back:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-back:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} @@ -164,7 +164,7 @@ jobs: - name: Login to OCIR uses: docker/login-action@v2 with: - registry: ${{ CONTAINER_REGISTRY }} + registry: ${{ vars.CONTAINER_REGISTRY }} username: ${{ secrets.OCI_USERNAME }} password: ${{ secrets.OCI_TOKEN }} @@ -174,7 +174,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-uploader + images: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-uploader - name: Build and push uses: docker/build-push-action@v3 @@ -182,10 +182,10 @@ jobs: with: file: uploader/Dockerfile push: true - tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-uploader:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-uploader:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-uploader:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-uploader:develop + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-uploader:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-uploader:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} @@ -205,7 +205,7 @@ jobs: - name: Login to OCIR uses: docker/login-action@v2 with: - registry: ${{ CONTAINER_REGISTRY }} + registry: ${{ vars.CONTAINER_REGISTRY }} username: ${{ secrets.OCI_USERNAME }} password: ${{ secrets.OCI_TOKEN }} @@ -215,7 +215,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-maps + images: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-maps - name: Build and push uses: docker/build-push-action@v3 @@ -224,10 +224,10 @@ jobs: context: maps/ file: maps/Dockerfile push: true - tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-maps:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-maps:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-maps:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-maps:develop + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-maps:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-maps:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} @@ -248,7 +248,7 @@ jobs: - name: Login to OCIR uses: docker/login-action@v2 with: - registry: ${{ CONTAINER_REGISTRY }} + registry: ${{ vars.CONTAINER_REGISTRY }} username: ${{ secrets.OCI_USERNAME }} password: ${{ secrets.OCI_TOKEN }} @@ -258,7 +258,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-map-storage + images: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-map-storage - name: Build and push uses: docker/build-push-action@v3 @@ -268,10 +268,10 @@ jobs: file: map-storage/Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-map-storage:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-map-storage:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-map-storage:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-map-storage:develop + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-map-storage:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-map-storage:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} @@ -292,7 +292,7 @@ jobs: - name: Login to OCIR uses: docker/login-action@v2 with: - registry: ${{ CONTAINER_REGISTRY }} + registry: ${{ vars.CONTAINER_REGISTRY }} username: ${{ secrets.OCI_USERNAME }} password: ${{ secrets.OCI_TOKEN }} @@ -302,7 +302,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-ejabberd + images: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-ejabberd - name: Build and push uses: docker/build-push-action@v3 @@ -311,9 +311,9 @@ jobs: context: xmpp/ platforms: linux/amd64,linux/arm64 push: true - tags: ${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-ejabberd:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + tags: ${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-ejabberd:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} cache-from: | - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-ejabberd:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} - type=registry,ref=${{ CONTAINER_REGISTRY_ACCOUNT }}/workadventure-ejabberd:develop + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-ejabberd:${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} + type=registry,ref=${{ vars.CONTAINER_REGISTRY_ACCOUNT }}/workadventure-ejabberd:develop cache-to: type=inline labels: ${{ steps.meta.outputs.labels }} From bcccaedbd4487479a5cd7fafd892496fab963649 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Sat, 22 Jul 2023 00:30:15 -0300 Subject: [PATCH 33/37] chore: testing mobile access using direct link --- .../Phaser/Game/GameMapPropertiesListener.ts | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/play/src/front/Phaser/Game/GameMapPropertiesListener.ts b/play/src/front/Phaser/Game/GameMapPropertiesListener.ts index 098e7e343b..a9d07e69fe 100644 --- a/play/src/front/Phaser/Game/GameMapPropertiesListener.ts +++ b/play/src/front/Phaser/Game/GameMapPropertiesListener.ts @@ -36,6 +36,7 @@ import type { GameMapFrontWrapper } from "./GameMap/GameMapFrontWrapper"; import type { GameScene } from "./GameScene"; import { AreasPropertiesListener } from "./MapEditor/AreasPropertiesListener"; import { gameManager } from "./GameManager"; +import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils"; export interface OpenCoWebsite { actionId: string; @@ -210,20 +211,39 @@ export class GameMapPropertiesListener { return; } } - inBbbStore.set(true); - bbbFactory.setStopped(false); - bbbFactory - .parametrizeMeetingId(newValue as string) - .then((hashedMeetingId) => { - if (this.scene.connection === undefined) { - throw new Error("No more connection to open BBB"); - } - return this.scene.connection.queryBBBMeetingUrl(hashedMeetingId, allProps); - }) - .then((bbbAnswer) => { - bbbFactory.start(bbbAnswer.clientURL); - }) - .catch((e) => console.error(e)); + + // let isMobile = isMediaBreakpointUp("md"); + // setting it to mobile always for testing purposes + let isMobile = true; + if (isMobile) { + bbbFactory + .parametrizeMeetingId(newValue as string) + .then((hashedMeetingId) => { + if (this.scene.connection === undefined) { + throw new Error("No more connection to open BBB"); + } + return this.scene.connection.queryBBBMeetingUrl(hashedMeetingId, allProps); + }) + .then((bbbAnswer) => { + scriptUtils.openTab("br.rnp.conferenciawebmobile://direct-join/" + bbbAnswer.clientURL); + }) + .catch((e) => console.error(e)); + } else { + inBbbStore.set(true); + bbbFactory.setStopped(false); + bbbFactory + .parametrizeMeetingId(newValue as string) + .then((hashedMeetingId) => { + if (this.scene.connection === undefined) { + throw new Error("No more connection to open BBB"); + } + return this.scene.connection.queryBBBMeetingUrl(hashedMeetingId, allProps); + }) + .then((bbbAnswer) => { + bbbFactory.start(bbbAnswer.clientURL); + }) + .catch((e) => console.error(e)); + } }); this.gameMapFrontWrapper.onPropertyChange(GameMapProperties.EXIT_SCENE_URL, (newValue) => { From a62963ff3baf070922921af52e9d5522fb7faadf Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Sat, 22 Jul 2023 00:59:59 -0300 Subject: [PATCH 34/37] fix: join url without https and properly detects mobile --- back/src/Services/SocketManager.ts | 1 - play/src/front/Components/ActionBar/ActionBar.svelte | 2 +- .../Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte | 2 +- play/src/front/Phaser/Game/GameMapPropertiesListener.ts | 6 ++---- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index eba7627028..cb197be121 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -917,7 +917,6 @@ export class SocketManager { const clientURL = api.administration.join(user.name, meetingId, isAdmin ? moderatorPW : attendeePW, { ...joinParams, userID: user.id, - joinViaHtml5: true, }); console.log( `User "${user.name}" (${user.uuid}) joined the BBB meeting "${meetingName}" as ${ diff --git a/play/src/front/Components/ActionBar/ActionBar.svelte b/play/src/front/Components/ActionBar/ActionBar.svelte index 2fd84bf360..b4f7bdfcf7 100644 --- a/play/src/front/Components/ActionBar/ActionBar.svelte +++ b/play/src/front/Components/ActionBar/ActionBar.svelte @@ -307,7 +307,7 @@ } function openBo() { - window.open(`https://workadventu.re/admin`, "_blanck"); + window.open(`https://workadventu.re/admin`, "_blank"); } /*function register() { diff --git a/play/src/front/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte b/play/src/front/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte index ce99ba02a4..683b8fc0a5 100644 --- a/play/src/front/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte +++ b/play/src/front/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte @@ -34,7 +34,7 @@ cowebsiteName = "Jitsi meeting"; } else if (isBBB) { icon.src = meetingIcon; - cowebsiteName = "BigBlueButton meeting"; + cowebsiteName = "Elos meeting"; } else { icon.src = `${ICON_URL}/icon?url=${encodeURIComponent( coWebsite.getUrl().toString() diff --git a/play/src/front/Phaser/Game/GameMapPropertiesListener.ts b/play/src/front/Phaser/Game/GameMapPropertiesListener.ts index a9d07e69fe..a2fab68d9a 100644 --- a/play/src/front/Phaser/Game/GameMapPropertiesListener.ts +++ b/play/src/front/Phaser/Game/GameMapPropertiesListener.ts @@ -212,9 +212,7 @@ export class GameMapPropertiesListener { } } - // let isMobile = isMediaBreakpointUp("md"); - // setting it to mobile always for testing purposes - let isMobile = true; + let isMobile = isMediaBreakpointUp("md"); if (isMobile) { bbbFactory .parametrizeMeetingId(newValue as string) @@ -225,7 +223,7 @@ export class GameMapPropertiesListener { return this.scene.connection.queryBBBMeetingUrl(hashedMeetingId, allProps); }) .then((bbbAnswer) => { - scriptUtils.openTab("br.rnp.conferenciawebmobile://direct-join/" + bbbAnswer.clientURL); + scriptUtils.openTab("br.rnp.conferenciawebmobile://direct-join/" + bbbAnswer.clientURL.replace(/^https?:\/\//, '')); }) .catch((e) => console.error(e)); } else { From e8f69351d76348dff0513f3a57176598e0c884a2 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Sat, 22 Jul 2023 01:26:45 -0300 Subject: [PATCH 35/37] chore: trying to improve mobile access to bbb rooms --- .../Phaser/Game/GameMapPropertiesListener.ts | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/play/src/front/Phaser/Game/GameMapPropertiesListener.ts b/play/src/front/Phaser/Game/GameMapPropertiesListener.ts index a2fab68d9a..431ba096d7 100644 --- a/play/src/front/Phaser/Game/GameMapPropertiesListener.ts +++ b/play/src/front/Phaser/Game/GameMapPropertiesListener.ts @@ -214,18 +214,28 @@ export class GameMapPropertiesListener { let isMobile = isMediaBreakpointUp("md"); if (isMobile) { - bbbFactory - .parametrizeMeetingId(newValue as string) - .then((hashedMeetingId) => { - if (this.scene.connection === undefined) { - throw new Error("No more connection to open BBB"); - } - return this.scene.connection.queryBBBMeetingUrl(hashedMeetingId, allProps); - }) - .then((bbbAnswer) => { - scriptUtils.openTab("br.rnp.conferenciawebmobile://direct-join/" + bbbAnswer.clientURL.replace(/^https?:\/\//, '')); - }) - .catch((e) => console.error(e)); + let message = allProps.get(GameMapProperties.OPEN_WEBSITE_TRIGGER_MESSAGE); + if (message === undefined) { + message = get(LL).trigger.newTab(); + } + layoutManagerActionStore.addAction({ + uuid: "openTab", + type: "message", + message: message, + callback: () => bbbFactory + .parametrizeMeetingId(newValue as string) + .then((hashedMeetingId) => { + if (this.scene.connection === undefined) { + throw new Error("No more connection to open BBB"); + } + return this.scene.connection.queryBBBMeetingUrl(hashedMeetingId, allProps); + }) + .then((bbbAnswer) => { + scriptUtils.openTab("br.rnp.conferenciawebmobile://direct-join/" + bbbAnswer.clientURL.replace(/^https?:\/\//, '')); + }) + .catch((e) => console.error(e)), + userInputManager: this.scene.userInputManager, + }); } else { inBbbStore.set(true); bbbFactory.setStopped(false); From 81c99c708b393831d255cef43b4301577022ef8c Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Sat, 22 Jul 2023 23:34:48 -0300 Subject: [PATCH 36/37] fix: set properly bbbFactory state for mobile --- play/src/front/Phaser/Game/GameMapPropertiesListener.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/play/src/front/Phaser/Game/GameMapPropertiesListener.ts b/play/src/front/Phaser/Game/GameMapPropertiesListener.ts index 431ba096d7..ce62a7b48f 100644 --- a/play/src/front/Phaser/Game/GameMapPropertiesListener.ts +++ b/play/src/front/Phaser/Game/GameMapPropertiesListener.ts @@ -212,6 +212,9 @@ export class GameMapPropertiesListener { } } + inBbbStore.set(true); + bbbFactory.setStopped(false); + let isMobile = isMediaBreakpointUp("md"); if (isMobile) { let message = allProps.get(GameMapProperties.OPEN_WEBSITE_TRIGGER_MESSAGE); @@ -237,8 +240,6 @@ export class GameMapPropertiesListener { userInputManager: this.scene.userInputManager, }); } else { - inBbbStore.set(true); - bbbFactory.setStopped(false); bbbFactory .parametrizeMeetingId(newValue as string) .then((hashedMeetingId) => { From 29d61f93da63ab6c5282168c598c45c4ef7de0b9 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno Date: Sun, 23 Jul 2023 00:08:04 -0300 Subject: [PATCH 37/37] feature: configurable bbb mobile using direct link --- docker-compose.yaml | 2 ++ play/src/common/FrontConfigurationInterface.ts | 2 ++ play/src/front/Enum/EnvironmentVariable.ts | 2 ++ .../front/Phaser/Game/GameMapPropertiesListener.ts | 11 ++++++++--- play/src/pusher/enums/EnvironmentVariable.ts | 2 ++ play/src/pusher/enums/EnvironmentVariableValidator.ts | 3 +++ 6 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index acfcc62628..e7c0f0118c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -123,6 +123,8 @@ services: FEATURE_FLAG_BROADCAST_AREAS: "true" PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS: "$PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS" PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS: "$PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS" + BBB_MOBILE_ENABLED: "$BBB_MOBILE_ENABLED" + BBB_MOBILE_DIRECT_JOIN_PREFIX: "$BBB_MOBILE_DIRECT_JOIN_PREFIX" labels: - "traefik.enable=true" - "traefik.http.routers.front.rule=Host(`front.workadventure.localhost`)" diff --git a/play/src/common/FrontConfigurationInterface.ts b/play/src/common/FrontConfigurationInterface.ts index 3345532310..28e6ba6779 100644 --- a/play/src/common/FrontConfigurationInterface.ts +++ b/play/src/common/FrontConfigurationInterface.ts @@ -44,4 +44,6 @@ export interface FrontConfigurationInterface { FEATURE_FLAG_BROADCAST_AREAS: boolean; PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS: number; PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS: number; + BBB_MOBILE_ENABLED: boolean; + BBB_MOBILE_DIRECT_JOIN_PREFIX: string | undefined; } diff --git a/play/src/front/Enum/EnvironmentVariable.ts b/play/src/front/Enum/EnvironmentVariable.ts index b39e9f47f5..fdea4c5287 100644 --- a/play/src/front/Enum/EnvironmentVariable.ts +++ b/play/src/front/Enum/EnvironmentVariable.ts @@ -40,6 +40,8 @@ export const ENABLE_REPORT_ISSUES_MENU = env.ENABLE_REPORT_ISSUES_MENU; export const REPORT_ISSUES_URL = env.REPORT_ISSUES_URL; export const PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS = env.PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS; export const PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS = env.PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS; +export const BBB_MOBILE_ENABLED = env.BBB_MOBILE_ENABLED; +export const BBB_MOBILE_DIRECT_JOIN_PREFIX = env.BBB_MOBILE_DIRECT_JOIN_PREFIX; export const POSITION_DELAY = 200; // Wait 200ms between sending position events export const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player diff --git a/play/src/front/Phaser/Game/GameMapPropertiesListener.ts b/play/src/front/Phaser/Game/GameMapPropertiesListener.ts index ce62a7b48f..dd34e4d7d0 100644 --- a/play/src/front/Phaser/Game/GameMapPropertiesListener.ts +++ b/play/src/front/Phaser/Game/GameMapPropertiesListener.ts @@ -12,7 +12,12 @@ import { ON_ACTION_TRIGGER_BUTTON, ON_ICON_TRIGGER_BUTTON } from "../../WebRtc/L import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWebsite"; import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite"; import { bbbFactory } from "../../WebRtc/BBBFactory"; -import { JITSI_PRIVATE_MODE, JITSI_URL } from "../../Enum/EnvironmentVariable"; +import { + JITSI_PRIVATE_MODE, + JITSI_URL, + BBB_MOBILE_ENABLED, + BBB_MOBILE_DIRECT_JOIN_PREFIX +} from "../../Enum/EnvironmentVariable"; import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite"; import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore"; import { iframeListener } from "../../Api/IframeListener"; @@ -216,7 +221,7 @@ export class GameMapPropertiesListener { bbbFactory.setStopped(false); let isMobile = isMediaBreakpointUp("md"); - if (isMobile) { + if (BBB_MOBILE_ENABLED && isMobile) { let message = allProps.get(GameMapProperties.OPEN_WEBSITE_TRIGGER_MESSAGE); if (message === undefined) { message = get(LL).trigger.newTab(); @@ -234,7 +239,7 @@ export class GameMapPropertiesListener { return this.scene.connection.queryBBBMeetingUrl(hashedMeetingId, allProps); }) .then((bbbAnswer) => { - scriptUtils.openTab("br.rnp.conferenciawebmobile://direct-join/" + bbbAnswer.clientURL.replace(/^https?:\/\//, '')); + scriptUtils.openTab(BBB_MOBILE_DIRECT_JOIN_PREFIX + bbbAnswer.clientURL.replace(/^https?:\/\//, '')); }) .catch((e) => console.error(e)), userInputManager: this.scene.userInputManager, diff --git a/play/src/pusher/enums/EnvironmentVariable.ts b/play/src/pusher/enums/EnvironmentVariable.ts index 37dee3344d..2f81fb4913 100644 --- a/play/src/pusher/enums/EnvironmentVariable.ts +++ b/play/src/pusher/enums/EnvironmentVariable.ts @@ -135,4 +135,6 @@ export const FRONT_ENVIRONMENT_VARIABLES: FrontConfigurationInterface = { FEATURE_FLAG_BROADCAST_AREAS: env.FEATURE_FLAG_BROADCAST_AREAS, PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS: parseInt(env.PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS || "0"), PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS: parseInt(env.PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS || "0"), + BBB_MOBILE_ENABLED: env.BBB_MOBILE_ENABLED, + BBB_MOBILE_DIRECT_JOIN_PREFIX: env.BBB_MOBILE_DIRECT_JOIN_PREFIX, }; diff --git a/play/src/pusher/enums/EnvironmentVariableValidator.ts b/play/src/pusher/enums/EnvironmentVariableValidator.ts index 089c8473ec..e17024f6ba 100644 --- a/play/src/pusher/enums/EnvironmentVariableValidator.ts +++ b/play/src/pusher/enums/EnvironmentVariableValidator.ts @@ -108,6 +108,9 @@ export const EnvironmentVariables = z.object({ // Limit bandwidth environment variables PEER_VIDEO_MAX_BANDWIDTH_KBITS_PS: PositiveIntAsString.optional(), PEER_SCREENSHARE_MAX_BANDWIDTH_KBITS_PS: PositiveIntAsString.optional(), + + BBB_MOBILE_ENABLED: BoolAsString.optional().transform((val) => toBool(val, false)), + BBB_MOBILE_DIRECT_JOIN_PREFIX: z.string().optional(), }); export type EnvironmentVariables = z.infer;