From de1768b7bbe67a438fa6dcf6a5a103b1758418b5 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Mon, 10 May 2021 12:10:47 +0200 Subject: [PATCH 01/10] added back automatic deploy of staging 2 --- .github/workflows/build-and-deploy.yml | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 42f4868b..48a7bae9 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: - branch: [master] + branches: [master] release: types: [created] pull_request: @@ -16,7 +16,7 @@ env: jobs: build-front: - if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }} + if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }} runs-on: ubuntu-latest steps: @@ -36,11 +36,11 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: thecodingmachine/workadventure-front - tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }} + tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} add_git_labels: true build-back: - if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }} + if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }} runs-on: ubuntu-latest steps: @@ -59,11 +59,11 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: thecodingmachine/workadventure-back - tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }} + tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} add_git_labels: true build-pusher: - if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }} + if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }} runs-on: ubuntu-latest steps: @@ -82,11 +82,11 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: thecodingmachine/workadventure-pusher - tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }} + tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} add_git_labels: true build-uploader: - if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }} + if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }} runs-on: ubuntu-latest steps: @@ -105,11 +105,11 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: thecodingmachine/workadventure-uploader - tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }} + tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} add_git_labels: true build-maps: - if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }} + if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }} runs-on: ubuntu-latest steps: @@ -129,7 +129,7 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: thecodingmachine/workadventure-maps - tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }} + tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} add_git_labels: true deeploy: @@ -140,7 +140,7 @@ jobs: - build-maps - build-uploader runs-on: ubuntu-latest - if: ${{ github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }} + if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }} steps: - name: Checkout @@ -158,13 +158,13 @@ jobs: 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.pull_request && env.GITHUB_HEAD_REF_SLUG || 'master' }} + DEPLOY_REF: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} with: - namespace: workadventure-${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || 'master' }} + 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.pull_request }} + if: ${{ github.event_name == 'pull_request' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: From d1f1c8f47093bd4cca7d058018c0bd77cf0bfa95 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Mon, 10 May 2021 12:40:12 +0200 Subject: [PATCH 02/10] added back automatic deploy of staging 3 --- deeployer.libsonnet | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 7ed9db8c..f9dd87bd 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -2,7 +2,7 @@ local env = std.extVar("env"), local namespace = env.DEPLOY_REF, local tag = namespace, - local url = if namespace == "master" then "workadventu.re" else namespace+".test.workadventu.re", + local url = namespace+".test.workadventu.re", // develop branch does not use admin because of issue with SSL certificate of admin as of now. local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://"+url else null, "$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json", @@ -25,10 +25,7 @@ "TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET, } + (if adminUrl != null then { "ADMIN_API_URL": adminUrl, - } else {}) + if namespace != "master" then { - // Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids! - "NODE_TLS_REJECT_UNAUTHORIZED": "0" - } + } else {}) }, "back2": { "image": "thecodingmachine/workadventure-back:"+tag, @@ -47,10 +44,7 @@ "TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET, } + (if adminUrl != null then { "ADMIN_API_URL": adminUrl, - } else {}) + if namespace != "master" then { - // Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids! - "NODE_TLS_REJECT_UNAUTHORIZED": "0" - } + } else {}) }, "pusher": { "replicas": 2, @@ -69,10 +63,7 @@ "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY, } + (if adminUrl != null then { "ADMIN_API_URL": adminUrl, - } else {}) + if namespace != "master" then { - // Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids! - "NODE_TLS_REJECT_UNAUTHORIZED": "0" - } + } else {}) }, "front": { "image": "thecodingmachine/workadventure-front:"+tag, From bd4cf5d7f714c9da8b3876369f1c5da316b5110e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?gr=C3=A9goire=20parant?= Date: Mon, 10 May 2021 19:55:43 +0200 Subject: [PATCH 03/10] Fix error context sound meter (#1009) --- front/src/Phaser/Components/SoundMeter.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/front/src/Phaser/Components/SoundMeter.ts b/front/src/Phaser/Components/SoundMeter.ts index af75940e..1d6f7eba 100644 --- a/front/src/Phaser/Components/SoundMeter.ts +++ b/front/src/Phaser/Components/SoundMeter.ts @@ -17,14 +17,12 @@ export class SoundMeter { } private init(context: AudioContext) { - if (this.context === undefined) { - this.context = context; - this.analyser = this.context.createAnalyser(); + this.context = context; + this.analyser = this.context.createAnalyser(); - this.analyser.fftSize = 2048; - const bufferLength = this.analyser.fftSize; - this.dataArray = new Uint8Array(bufferLength); - } + this.analyser.fftSize = 2048; + const bufferLength = this.analyser.fftSize; + this.dataArray = new Uint8Array(bufferLength); } public connectToSource(stream: MediaStream, context: AudioContext): void From 0fd743bcacc66d6c8ce51f76eae1420c9d26fd50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?gr=C3=A9goire=20parant?= Date: Mon, 10 May 2021 20:49:17 +0200 Subject: [PATCH 04/10] HotFix sound meter (#1029) --- front/src/WebRtc/MediaManager.ts | 39 +++++++++++++++++++------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index d9a91940..76cdc671 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -43,7 +43,8 @@ export class MediaManager { microphoneClose: HTMLImageElement; microphone: HTMLImageElement; webrtcInAudio: HTMLAudioElement; - mySoundMeterElement: HTMLDivElement; + //FIX ME SOUNDMETER: check stalability of sound meter calculation + //mySoundMeterElement: HTMLDivElement; private webrtcOutAudio: HTMLAudioElement; constraintsMedia : MediaStreamConstraints = { audio: audioConstraint, @@ -71,9 +72,10 @@ export class MediaManager { private userInputManager?: UserInputManager; - private mySoundMeter?: SoundMeter|null; + //FIX ME SOUNDMETER: check stalability of sound meter calculation + /*private mySoundMeter?: SoundMeter|null; private soundMeters: Map = new Map(); - private soundMeterElements: Map = new Map(); + private soundMeterElements: Map = new Map();*/ constructor() { @@ -134,15 +136,17 @@ export class MediaManager { this.checkActiveUser(); //todo: desactivated in case of bug - this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter')); + //FIX ME SOUNDMETER: check stalability of sound meter calculation + /*this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter')); this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => { this.mySoundMeterElement.children.item(index)?.classList.remove('active'); - }); + });*/ } public updateScene(){ this.lastUpdateScene = new Date(); - this.updateSoudMeter(); + //FIX ME SOUNDMETER: check stalability of sound meter calculation + //this.updateSoudMeter(); } public blurCamera() { @@ -457,12 +461,12 @@ export class MediaManager { this.localStream = stream; this.myCamVideo.srcObject = this.localStream; - //init sound meter - this.mySoundMeter = null; + //FIX ME SOUNDMETER: check stalability of sound meter calculation + /*this.mySoundMeter = null; if(this.constraintsMedia.audio){ this.mySoundMeter = new SoundMeter(); this.mySoundMeter.connectToSource(stream, new AudioContext()); - } + }*/ return stream; }).catch((err: Error) => { throw err; @@ -489,7 +493,7 @@ export class MediaManager { track.stop(); } } - this.mySoundMeter?.stop(); + //this.mySoundMeter?.stop(); } setCamera(id: string): Promise { @@ -632,11 +636,12 @@ export class MediaManager { } remoteVideo.srcObject = stream; + //FIX ME SOUNDMETER: check stalability of sound meter calculation //sound metter - const soundMeter = new SoundMeter(); + /*const soundMeter = new SoundMeter(); soundMeter.connectToSource(stream, new AudioContext()); this.soundMeters.set(userId, soundMeter); - this.soundMeterElements.set(userId, HtmlUtils.getElementByIdOrFail('soundMeter-'+userId)); + this.soundMeterElements.set(userId, HtmlUtils.getElementByIdOrFail('soundMeter-'+userId));*/ } addStreamRemoteScreenSharing(userId: string, stream : MediaStream){ // In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet @@ -652,9 +657,10 @@ export class MediaManager { layoutManager.remove(userId); this.remoteVideo.delete(userId); - this.soundMeters.get(userId)?.stop(); + //FIX ME SOUNDMETER: check stalability of sound meter calculation + /*this.soundMeters.get(userId)?.stop(); this.soundMeters.delete(userId); - this.soundMeterElements.delete(userId); + this.soundMeterElements.delete(userId);*/ //permit to remove user in discussion part this.removeParticipant(userId); @@ -807,7 +813,8 @@ export class MediaManager { } } - updateSoudMeter(){ + //FIX ME SOUNDMETER: check stalability of sound meter calculation + /*updateSoudMeter(){ try{ const volume = parseInt(((this.mySoundMeter ? this.mySoundMeter.getVolume() : 0) / 10).toFixed(0)); this.setVolumeSoundMeter(volume, this.mySoundMeterElement); @@ -824,7 +831,7 @@ export class MediaManager { }catch(err){ //console.error(err); } - } + }*/ private setVolumeSoundMeter(volume: number, element: HTMLDivElement){ if(volume <= 0 && !element.classList.contains('active')){ From 52b1c6733b0c907b8ae2c7ce59f99dcd20126e3c Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 7 May 2021 01:37:05 +0200 Subject: [PATCH 05/10] Notification & Camera - Notification when user is first and not focus on the tab - Camera focus when user is in discussion circle and back on tab with previous config camera settings - Camera stay blur if user is in discussion circle and not back on the tab # Conflicts: # front/src/WebRtc/MediaManager.ts --- front/src/WebRtc/MediaManager.ts | 33 ++++++++++++++++++++++++++++++-- front/src/WebRtc/SimplePeer.ts | 5 +++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 76cdc671..460d5807 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -141,6 +141,9 @@ export class MediaManager { this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => { this.mySoundMeterElement.children.item(index)?.classList.remove('active'); });*/ + + //Check of ask notification navigator permission + this.getNotification(); } public updateScene(){ @@ -790,9 +793,9 @@ export class MediaManager { this.setTimeOutlastUpdateScene = setTimeout(() => { const now = new Date(); //if last update is more of 10 sec - if( (now.getTime() - this.lastUpdateScene.getTime()) > 10000) { + if( (now.getTime() - this.lastUpdateScene.getTime()) > 10000 && this.remoteVideo.size === 0) { this.blurCamera(); - }else{ + }else if((now.getTime() - this.lastUpdateScene.getTime()) <= 10000){ this.focusCamera(); } this.checkActiveUser(); @@ -854,6 +857,32 @@ export class MediaManager { elementChildre.classList.add('active'); }); } + + public getNotification(){ + //Get notification + if (window.Notification && Notification.permission !== "granted") { + Notification.requestPermission().catch((err) => { + console.error(`Notification permission error`, err); + }); + } + } + + public createNotification(userName: string){ + if(this.focused){ + return; + } + if (window.Notification && Notification.permission === "granted") { + const title = 'WorkAdventure'; + const options = { + body: `Hi! ${userName} wants to discuss with you, don't be afraid!`, + icon: '/resources/logos/logo-WA-min.png', + image: '/resources/logos/logo-WA-min.png', + badge: '/resources/logos/logo-WA-min.png', + }; + new Notification(title, options); + //new Notification(`Hi! ${userName} wants to discuss with you, don't be afraid!`); + } + } } export const mediaManager = new MediaManager(); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 7690c27d..3a56d20b 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -158,6 +158,11 @@ export class SimplePeer { this.sendLocalScreenSharingStreamToUser(user.userId); } }); + + //Create a notification for first user in circle discussion + if(this.PeerConnectionArray.size === 0){ + mediaManager.createNotification(user.name??''); + } this.PeerConnectionArray.set(user.userId, peer); for (const peerConnectionListener of this.peerConnectionListeners) { From e50aad0ea05e3f9fc1edb3706f58900cb261fd29 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 7 May 2021 11:39:25 +0200 Subject: [PATCH 06/10] Add WA logo --- front/dist/resources/logos/logo-WA-min.png | Bin 0 -> 2136 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 front/dist/resources/logos/logo-WA-min.png diff --git a/front/dist/resources/logos/logo-WA-min.png b/front/dist/resources/logos/logo-WA-min.png new file mode 100644 index 0000000000000000000000000000000000000000..fe2131519735538aa4a2c7bdfcb46f945f54b69d GIT binary patch literal 2136 zcmV-e2&eanP)y0Kf7jVwzah0w!eWcYeU2~_}zu{)LwUPa<9x_AWVAwm*B@}#c= zz$ySNcyK=~4gg>akD-6JH%$lv=-{#J!M%vk&xpZWd=)?wpZ1Q&PdhJ(SJ=QN!n=6E zHvy!EPmYEM_oI_B0Ng||c!N&@=->}jH^b*iJ%SfagAW2|qv2gg^S0@ur*oP)xa|j7_C1tcFfEM;|sHZzggS#g-i<|G5&2bz7!3Nl{zLb zVp0I>XnaDA+a^U|svsmu@va*>cZ{SDlJ?$16CR*L#y5fyPoP@>9di3d(Jlg=X<(AJ z{Hp6@EdIHFfAZ-kmoLNIxpUIrcXD8;!-*>C))L4x z0nS{1J_pZJm6NM}KYxzD|NP_l^|LR&oP6dWB}^HkZGf+%?v~YFr>&o7!GhYbtSw_KpiG& z#>4_|EWqml=;;rSK92}^1klFfM6Eo2{J8XVjYj$4;>(vW;nk~G!CB?v#fxzI^l4j{ ztE?N~X#$i68wjUV+OA3fP0THcFj=jt8K@F~(VBF^IE|{FK@vdyDHT9j5SZkV`=)-L z2M->YI<-^w_V!9Izr4H*mo8nheT(x&lTFJ!fY6`<;WdS}EE9n7iHU?Y3T;_7HH@Nv z13Y>11kRi}BYffu7cQvWjIaQVKmAod!uj*(r#^fA`t{`fyLa#4{rmSP{@=!02T2E1 z8O`R&tZOtn+4Meo^a!3me=dC8YuB!+-1Y*-bW_D5-U@&fC_R1pbn^bqn>WWtmP7!i zMNlPx`T6-1|IQ1*A#K(OVNndw^5w1xZ7?5d5@bDIZG!l>S*JF@9C7@bKZo$@?o;uE=HuQ-L6>3E-+EA%bMewj_YKn2POK zO@Om?YhxgB^?7$+H9m0f-aTEv%#>|e9)L1{`oh%&5F`L17N`k`999tM?OH!FgDAe? zs34>eY62pM1Q0oO-8?#Ivi<(1VF_U1!zNBuvwm3P{x#SL`vQ@!J4M57=+?<9x z|Bs-u03X}@#Q6E*)G}nNf)&fcQ>LbhvzmY@H$l{fspg$xfVklD08V$aCX6v=J={ex zK;QL(kk6hyb9wm*HYRo)9UYyx&f40ViOVn_?4o`E@&fgP8QYNn;z39NlnK-i=0jZ+ z0}OGs$*5)sB@;BNX6UFEr7SHixhM!3$gB`TJA%}QLR*#zfSQ5&ASHk<=8i7*Xho2e(tQJ}3PQT*LWnRK=vXXT zdQUpSIh9-d@GKW^bj47>`oF`&L*?_>$m93-#eNDy{rWtN(RQV65|0N+I)%2a-8P|% zEuNu$-l9;cT`38taP9EMD**BWVX@7-gQ!SZIC+6^;I@_M*0Ec*`UURXxntup%A7iN z>i9@mSy?f8IUY7e1Ry#wWP`egy=E-ByQ`=GhO~XG*KCLu^~y{Q+o0Y-FB!-@oHEj)2_-7`2xK8UIW(yixJCBzhE3TzPFW8CxaeTdhqVMEXP~mMm&)|;5pF9-S%Evo zs+)VMt`D$FTgy77!`tImgpLIwwy2xf37a|!cU5A5JU!Rb$s%G~*RHNQ1wcWe9R`?& zYpFfcDF8qb41Hd0G;p*U9}FxKHNqRD%s@ZdjSq%a3EL$RXvc{0!Qe7+sR(GyKpkVo z2Lmxc9tsTAFlv0TuuxC?bI>PQs8a(1>Q&*l}h3O_B&nosD zQkuvn?c29DGuyTR04FPSDK40zkH)t;xzNR8(L-$8HXU5sN0O)hd(**Q5iUGHmKK&V z#X5%2*D<1}g6rkt)Bb`Rk#!s;{UhJt-|o!#;DHCo(!|<=^%@6>Y1y_nlWcqvKuR5j zkQ)A`Z)-98D1eky2%UhJt2;oWbIsIsfzJZSvWi0Raw7`{J7{$8m)Z+O04a+ZG5FSx zu!9EghP32 Date: Tue, 11 May 2021 10:56:50 +0200 Subject: [PATCH 07/10] Refactoring code to use the "visibilitychange" event Using the "visiblitychange" event instead of relying on a "trick" related to RAF being disabled when a window is not open allows us to have cleaner code. Bonus: the recursive call to "setTimeout" is gone, so the stacktrace growing indefinitely is gone too. This should make the application a bit more stable. --- front/src/Phaser/Game/GameScene.ts | 17 ++++++++ front/src/Phaser/Login/EnableCameraScene.ts | 1 - front/src/WebRtc/MediaManager.ts | 44 ++++++--------------- 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index c433ed0f..cd1b8892 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -183,6 +183,7 @@ export class GameScene extends ResizableScene implements CenterListener { private messageSubscription: Subscription|null = null; private popUpElements : Map = new Map(); private originalMapUrl: string|undefined; + private onVisibilityChangeCallback: () => void; constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { super({ @@ -202,6 +203,7 @@ export class GameScene extends ResizableScene implements CenterListener { this.connectionAnswerPromise = new Promise((resolve, reject): void => { this.connectionAnswerPromiseResolve = resolve; }) + this.onVisibilityChangeCallback = this.onVisibilityChange.bind(this); } //hook preload scene @@ -499,6 +501,8 @@ export class GameScene extends ResizableScene implements CenterListener { if (!this.room.isDisconnected()) { this.connect(); } + + document.addEventListener('visibilitychange', this.onVisibilityChangeCallback); } /** @@ -620,6 +624,7 @@ export class GameScene extends ResizableScene implements CenterListener { self.chatModeSprite.setVisible(false); self.openChatIcon.setVisible(false); audioManager.restoreVolume(); + self.onVisibilityChange(); } } }) @@ -918,6 +923,8 @@ ${escapedMessage} for(const iframeEvents of this.iframeSubscriptionList){ iframeEvents.unsubscribe(); } + + document.removeEventListener('visibilitychange', this.onVisibilityChangeCallback); } private removeAllRemotePlayers(): void { @@ -1510,4 +1517,14 @@ ${escapedMessage} }); } } + + private onVisibilityChange(): void { + if (document.visibilityState === 'visible') { + mediaManager.focusCamera(); + } else { + if (this.simplePeer.getNbConnections() === 0) { + mediaManager.blurCamera(); + } + } + } } diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts index 917dd44b..dfe48aea 100644 --- a/front/src/Phaser/Login/EnableCameraScene.ts +++ b/front/src/Phaser/Login/EnableCameraScene.ts @@ -247,7 +247,6 @@ export class EnableCameraScene extends Phaser.Scene { update(time: number, delta: number): void { this.soundMeterSprite.setVolume(this.soundMeter.getVolume()); - mediaManager.updateScene(); const middleX = this.getMiddleX(); this.tweens.add({ diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 460d5807..437e5e76 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -55,7 +55,7 @@ export class MediaManager { stopScreenSharingCallBacks : Set = new Set(); showReportModalCallBacks : Set = new Set(); helpCameraSettingsCallBacks : Set = new Set(); - + private microphoneBtn: HTMLDivElement; private cinemaBtn: HTMLDivElement; private monitorBtn: HTMLDivElement; @@ -63,9 +63,6 @@ export class MediaManager { private previousConstraint : MediaStreamConstraints; private focused : boolean = true; - private lastUpdateScene : Date = new Date(); - private setTimeOutlastUpdateScene? : NodeJS.Timeout; - private hasCamera = true; private triggerCloseJistiFrame : Map = new Map(); @@ -134,8 +131,6 @@ export class MediaManager { this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia)); this.pingCameraStatus(); - this.checkActiveUser(); //todo: desactivated in case of bug - //FIX ME SOUNDMETER: check stalability of sound meter calculation /*this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter')); this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => { @@ -147,7 +142,6 @@ export class MediaManager { } public updateScene(){ - this.lastUpdateScene = new Date(); //FIX ME SOUNDMETER: check stalability of sound meter calculation //this.updateSoudMeter(); } @@ -418,7 +412,7 @@ export class MediaManager { } private _startScreenCapture() { - if (navigator.getDisplayMedia) { + if (navigator.getDisplayMedia) { return navigator.getDisplayMedia({video: true}); } else if (navigator.mediaDevices.getDisplayMedia) { return navigator.mediaDevices.getDisplayMedia({video: true}); @@ -553,7 +547,7 @@ export class MediaManager { `; layoutManager.add(DivImportance.Normal, userId, html); - + this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail(userId)); //permit to create participant in discussion part @@ -571,7 +565,7 @@ export class MediaManager { showReportUser(); }); } - + addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){ userId = this.getScreenSharingId(userId); @@ -597,7 +591,7 @@ export class MediaManager { } element.classList.add('active') //todo: why does a method 'disable' add a class 'active'? } - + enabledMicrophoneByUserId(userId: number){ const element = document.getElementById(`microphone-${userId}`); if(!element){ @@ -605,7 +599,7 @@ export class MediaManager { } element.classList.remove('active') //todo: why does a method 'enable' remove a class 'active'? } - + disabledVideoByUserId(userId: number) { let element = document.getElementById(`${userId}`); if (element) { @@ -616,7 +610,7 @@ export class MediaManager { element.style.display = "block"; } } - + enabledVideoByUserId(userId: number){ let element = document.getElementById(`${userId}`); if(element){ @@ -655,7 +649,7 @@ export class MediaManager { this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream); } - + removeActiveVideo(userId: string){ layoutManager.remove(userId); this.remoteVideo.delete(userId); @@ -671,7 +665,7 @@ export class MediaManager { removeActiveScreenSharingVideo(userId: string) { this.removeActiveVideo(this.getScreenSharingId(userId)) } - + playWebrtcOutSound(): void { this.webrtcOutAudio.play(); } @@ -717,7 +711,7 @@ export class MediaManager { const connnectingSpinnerDiv = element.getElementsByClassName('connecting-spinner').item(0) as HTMLDivElement|null; return connnectingSpinnerDiv; } - + private getColorByString(str: String) : String|null { let hash = 0; if (str.length === 0) return null; @@ -785,22 +779,6 @@ export class MediaManager { this.userInputManager = userInputManager; discussionManager.setUserInputManager(userInputManager); } - //check if user is active - private checkActiveUser(){ - if(this.setTimeOutlastUpdateScene){ - clearTimeout(this.setTimeOutlastUpdateScene); - } - this.setTimeOutlastUpdateScene = setTimeout(() => { - const now = new Date(); - //if last update is more of 10 sec - if( (now.getTime() - this.lastUpdateScene.getTime()) > 10000 && this.remoteVideo.size === 0) { - this.blurCamera(); - }else if((now.getTime() - this.lastUpdateScene.getTime()) <= 10000){ - this.focusCamera(); - } - this.checkActiveUser(); - }, this.focused ? 10000 : 1000); - } public setShowReportModalCallBacks(callback: ShowReportCallBack){ this.showReportModalCallBacks.add(callback); @@ -821,7 +799,7 @@ export class MediaManager { try{ const volume = parseInt(((this.mySoundMeter ? this.mySoundMeter.getVolume() : 0) / 10).toFixed(0)); this.setVolumeSoundMeter(volume, this.mySoundMeterElement); - + for(const indexUserId of this.soundMeters.keys()){ const soundMeter = this.soundMeters.get(indexUserId); const soundMeterElement = this.soundMeterElements.get(indexUserId); From ad39b43df35ec2af22d3a7f7f70d95ec08ffcc36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 11 May 2021 14:52:51 +0200 Subject: [PATCH 08/10] Closing game webcame in Jitsi When stepping in Jitsi, the game webcam (from mediaManager) was not shut down. And when enabling/disabling the webcam in Jitsi, the webcam in mediaManager was also enabled/disabled. This PR fixes those issues. It also fixes a race condition when closing a Jitsi where the mic/cam would be enabled at the same time. --- front/src/Phaser/Game/GameScene.ts | 9 ++++++ front/src/WebRtc/JitsiFactory.ts | 27 +++++++----------- front/src/WebRtc/MediaManager.ts | 44 +++++++++++++++++++++--------- 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index cd1b8892..5d54d470 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1475,6 +1475,8 @@ ${escapedMessage} mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => { this.stopJitsi(); }); + + this.onVisibilityChange(); } public stopJitsi(): void { @@ -1483,6 +1485,7 @@ ${escapedMessage} mediaManager.showGameOverlay(); mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi'); + this.onVisibilityChange(); } //todo: put this into an 'orchestrator' scene (EntryScene?) @@ -1519,6 +1522,12 @@ ${escapedMessage} } private onVisibilityChange(): void { + // If the overlay is not displayed, we are in Jitsi. We don't need the webcam. + if (!mediaManager.isGameOverlayVisible()) { + mediaManager.blurCamera(); + return; + } + if (document.visibilityState === 'visible') { mediaManager.focusCamera(); } else { diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index 983b08e2..8ddbba7b 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -10,9 +10,10 @@ interface jitsiConfigInterface { } const getDefaultConfig = () : jitsiConfigInterface => { + const constraints = mediaManager.getConstraintRequestedByUser(); return { - startWithAudioMuted: !mediaManager.constraintsMedia.audio, - startWithVideoMuted: mediaManager.constraintsMedia.video === false, + startWithAudioMuted: !constraints.audio, + startWithVideoMuted: constraints.video === false, prejoinPageEnabled: false } } @@ -71,7 +72,7 @@ class JitsiFactory { private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any private audioCallback = this.onAudioChange.bind(this); private videoCallback = this.onVideoChange.bind(this); - private previousConfigMeet? : jitsiConfigInterface; + private previousConfigMeet! : jitsiConfigInterface; private jitsiScriptLoaded: boolean = false; /** @@ -136,32 +137,24 @@ class JitsiFactory { //restore previous config if(this.previousConfigMeet?.startWithAudioMuted){ - mediaManager.disableMicrophone(); + await mediaManager.disableMicrophone(); }else{ - mediaManager.enableMicrophone(); + await mediaManager.enableMicrophone(); } if(this.previousConfigMeet?.startWithVideoMuted){ - mediaManager.disableCamera(); + await mediaManager.disableCamera(); }else{ - mediaManager.enableCamera(); + await mediaManager.enableCamera(); } } private onAudioChange({muted}: {muted: boolean}): void { - if (muted && mediaManager.constraintsMedia.audio === true) { - mediaManager.disableMicrophone(); - } else if(!muted && mediaManager.constraintsMedia.audio === false) { - mediaManager.enableMicrophone(); - } + this.previousConfigMeet.startWithAudioMuted = muted; } private onVideoChange({muted}: {muted: boolean}): void { - if (muted && mediaManager.constraintsMedia.video !== false) { - mediaManager.disableCamera(); - } else if(!muted && mediaManager.constraintsMedia.video === false) { - mediaManager.enableCamera(); - } + this.previousConfigMeet.startWithVideoMuted = muted; } private async loadJitsiScript(domain: string): Promise { diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 437e5e76..85060a86 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -155,6 +155,13 @@ export class MediaManager { this.disableCamera(); } + /** + * Returns the constraint that the user wants (independently of the visibility / jitsi state...) + */ + public getConstraintRequestedByUser(): MediaStreamConstraints { + return this.previousConstraint ?? this.constraintsMedia; + } + public focusCamera() { if(this.focused){ return; @@ -197,7 +204,7 @@ export class MediaManager { } } - public showGameOverlay(){ + public showGameOverlay(): void { const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); gameOverlay.classList.add('active'); @@ -208,7 +215,7 @@ export class MediaManager { buttonCloseFrame.removeEventListener('click', functionTrigger); } - public hideGameOverlay(){ + public hideGameOverlay(): void { const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); gameOverlay.classList.remove('active'); @@ -219,6 +226,11 @@ export class MediaManager { buttonCloseFrame.addEventListener('click', functionTrigger); } + public isGameOverlayVisible(): boolean { + const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); + return gameOverlay.classList.contains('active'); + } + public updateCameraQuality(value: number) { this.enableCameraStyle(); const newVideoConstraint = JSON.parse(JSON.stringify(videoConstraint)); @@ -230,29 +242,32 @@ export class MediaManager { }); } - public enableCamera() { + public async enableCamera() { this.constraintsMedia.video = videoConstraint; - this.getCamera().then((stream: MediaStream) => { + try { + const stream = await this.getCamera() //TODO show error message tooltip upper of camera button //TODO message : please check camera permission of your navigator if(stream.getVideoTracks().length === 0) { - throw Error('Video track is empty, please check camera permission of your navigator') + throw new Error('Video track is empty, please check camera permission of your navigator') } this.enableCameraStyle(); this.triggerUpdatedLocalStreamCallbacks(stream); - }).catch((err) => { + } catch(err) { console.error(err); this.disableCameraStyle(); + this.stopCamera(); layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => { this.showHelpCameraSettingsCallBack(); }, this.userInputManager); - }); + } } public async disableCamera() { this.disableCameraStyle(); + this.stopCamera(); if (this.constraintsMedia.audio !== false) { const stream = await this.getCamera(); @@ -262,25 +277,27 @@ export class MediaManager { } } - public enableMicrophone() { + public async enableMicrophone() { this.constraintsMedia.audio = audioConstraint; - this.getCamera().then((stream) => { + try { + const stream = await this.getCamera(); + //TODO show error message tooltip upper of camera button //TODO message : please check microphone permission of your navigator - if(stream.getAudioTracks().length === 0) { + if (stream.getAudioTracks().length === 0) { throw Error('Audio track is empty, please check microphone permission of your navigator') } this.enableMicrophoneStyle(); this.triggerUpdatedLocalStreamCallbacks(stream); - }).catch((err) => { + } catch(err) { console.error(err); this.disableMicrophoneStyle(); layoutManager.addInformation('warning', 'Microphone access denied. Click here and check navigators permissions.', () => { this.showHelpCameraSettingsCallBack(); }, this.userInputManager); - }); + } } public async disableMicrophone() { @@ -325,7 +342,6 @@ export class MediaManager { this.cinemaBtn.classList.add("disabled"); this.constraintsMedia.video = false; this.myCamVideo.srcObject = null; - this.stopCamera(); } private enableMicrophoneStyle(){ @@ -436,6 +452,8 @@ export class MediaManager { return this.getLocalStream().catch((err) => { console.info('Error get camera, trying with video option at null =>', err); this.disableCameraStyle(); + this.stopCamera(); + return this.getLocalStream().then((stream : MediaStream) => { this.hasCamera = false; return stream; From 4ec5ad9e3301b484a5ab392d1c4d7e0e2089eccb Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 11 May 2021 16:44:31 +0200 Subject: [PATCH 09/10] FIX: trackDirtyAnims now listen to more generic events --- front/src/Phaser/Game/DirtyScene.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/front/src/Phaser/Game/DirtyScene.ts b/front/src/Phaser/Game/DirtyScene.ts index 03ec9a95..27ebd3cb 100644 --- a/front/src/Phaser/Game/DirtyScene.ts +++ b/front/src/Phaser/Game/DirtyScene.ts @@ -2,6 +2,7 @@ import {ResizableScene} from "../Login/ResizableScene"; import GameObject = Phaser.GameObjects.GameObject; import Events = Phaser.Scenes.Events; import AnimationEvents = Phaser.Animations.Events; +import StructEvents = Phaser.Structs.Events; /** * A scene that can track its dirty/pristine state. @@ -23,12 +24,11 @@ export abstract class DirtyScene extends ResizableScene { } this.isAlreadyTracking = true; const trackAnimationFunction = this.trackAnimation.bind(this); - this.events.on(Events.ADDED_TO_SCENE, (gameObject: GameObject) => { + this.sys.updateList.on(StructEvents.PROCESS_QUEUE_ADD, (gameObject: GameObject) => { this.objectListChanged = true; gameObject.on(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction); }); - - this.events.on(Events.REMOVED_FROM_SCENE, (gameObject: GameObject) => { + this.sys.updateList.on(StructEvents.PROCESS_QUEUE_REMOVE, (gameObject: GameObject) => { this.objectListChanged = true; gameObject.removeListener(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction); }); From 627db30410c0a256a844bf2b56c259b280800c15 Mon Sep 17 00:00:00 2001 From: TabascoEye Date: Tue, 11 May 2021 17:38:28 +0200 Subject: [PATCH 10/10] turning noise suppresion back on turning AGC off was a good idea, disabling noise suppresion with it was not. => should all end up in the "settings" menu in the end --- front/src/WebRtc/MediaManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 85060a86..2bed2c68 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -20,7 +20,7 @@ const audioConstraint: boolean|MediaTrackConstraints = { //TODO: make these values configurable in the game settings menu and store them in localstorage autoGainControl: false, echoCancellation: true, - noiseSuppression: false + noiseSuppression: true }; export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void;