diff --git a/front/dist/index.html b/front/dist/index.html index 0e696622..5806bef7 100644 --- a/front/dist/index.html +++ b/front/dist/index.html @@ -72,7 +72,11 @@ - +
diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 5fb43e07..10e16cfa 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -284,6 +284,23 @@ body { #cowebsite.hidden { transform: translateX(100%); } + + #cowebsite .close-btn{ + position: absolute; + top: 10px; + right: -100px; + background: none; + border: none; + cursor: pointer; + animation: right .2s ease; + } + #cowebsite .close-btn img{ + height: 15px; + right: 15px; + } + #cowebsite:hover .close-btn{ + right: 10px; + } } @media (max-aspect-ratio: 1/1) { .game-overlay { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 6e85e6cf..a07b4c3a 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -317,7 +317,7 @@ export class GameScene extends ResizableScene implements CenterListener { // Let's alter browser history let path = this.room.id; if (this.room.hash) { - path += '#'+this.room.hash; + path += '#' + this.room.hash; } window.history.pushState({}, 'WorkAdventure', path); @@ -932,6 +932,7 @@ export class GameScene extends ResizableScene implements CenterListener { * @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate. */ update(time: number, delta: number) : void { + mediaManager.setLastUpdateScene(); this.currentTick = time; this.CurrentPlayer.moveUser(delta); @@ -1227,12 +1228,19 @@ export class GameScene extends ResizableScene implements CenterListener { jitsiFactory.start(roomName, gameManager.getPlayerName(), jwt); this.connection.setSilent(true); mediaManager.hideGameOverlay(); + + //permit to stop jitsi when user close iframe + mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => { + this.stopJitsi(); + }); } public stopJitsi(): void { this.connection.setSilent(false); jitsiFactory.stop(); mediaManager.showGameOverlay(); + + mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi'); } private loadSpritesheet(name: string, url: string): Promise { diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index b625bf6e..e6d5b748 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -44,7 +44,9 @@ class CoWebsiteManager { public loadCoWebsite(url: string): void { this.load(); - this.cowebsiteDiv.innerHTML = ''; + this.cowebsiteDiv.innerHTML = ``; const iframe = document.createElement('iframe'); iframe.id = 'cowebsite-iframe'; @@ -83,7 +85,9 @@ class CoWebsiteManager { this.close(); this.fire(); setTimeout(() => { - this.cowebsiteDiv.innerHTML = ''; + this.cowebsiteDiv.innerHTML = ``; resolve(); }, animationTime) })); diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index ce1878fb..943e13c2 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -40,12 +40,20 @@ export class MediaManager { private cinemaBtn: HTMLDivElement; private monitorBtn: HTMLDivElement; + private previousConstraint : MediaStreamConstraints; + private focused : boolean = true; + + private lastUpdateScene : Date = new Date(); + private setTimeOutlastUpdateScene? : NodeJS.Timeout; + private discussionManager: DiscussionManager; private userInputManager?: UserInputManager; private hasCamera = true; + private triggerCloseJistiFrame : Map = new Map(); + constructor() { this.myCamVideo = HtmlUtils.getElementByIdOrFail('myCamVideo'); @@ -98,9 +106,35 @@ export class MediaManager { //update tracking }); + this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia)); + this.pingCameraStatus(); + + this.checkActiveUser(); + this.discussionManager = new DiscussionManager(this,''); } + public setLastUpdateScene(){ + this.lastUpdateScene = new Date(); + } + + public blurCamera() { + if(!this.focused){ + return; + } + this.focused = false; + this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia)); + this.disableCamera(); + } + + public focusCamera() { + if(this.focused){ + return; + } + this.focused = true; + this.applyPreviousConfig(); + } + public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void { this.updatedLocalStreamCallBacks.add(callback); } @@ -138,20 +172,27 @@ export class MediaManager { public showGameOverlay(){ const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); gameOverlay.classList.add('active'); + + const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close'); + const functionTrigger = () => { + this.triggerCloseJitsiFrameButton(); + } + buttonCloseFrame.removeEventListener('click', functionTrigger); } public hideGameOverlay(){ const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); gameOverlay.classList.remove('active'); + + const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close'); + const functionTrigger = () => { + this.triggerCloseJitsiFrameButton(); + } + buttonCloseFrame.addEventListener('click', functionTrigger); } public enableCamera() { - if(!this.hasCamera){ - return; - } - this.cinemaClose.style.display = "none"; - this.cinemaBtn.classList.remove("disabled"); - this.cinema.style.display = "block"; + this.enableCameraStyle(); this.constraintsMedia.video = videoConstraint; this.getCamera().then((stream: MediaStream) => { this.triggerUpdatedLocalStreamCallbacks(stream); @@ -159,7 +200,8 @@ export class MediaManager { } public async disableCamera() { - this.disabledCameraView(); + this.disableCameraStyle(); + if (this.constraintsMedia.audio !== false) { const stream = await this.getCamera(); this.triggerUpdatedLocalStreamCallbacks(stream); @@ -168,19 +210,8 @@ export class MediaManager { } } - private disabledCameraView(){ - this.cinemaClose.style.display = "block"; - this.cinema.style.display = "none"; - this.cinemaBtn.classList.add("disabled"); - this.constraintsMedia.video = false; - this.myCamVideo.srcObject = null; - this.stopCamera(); - } - public enableMicrophone() { - this.microphoneClose.style.display = "none"; - this.microphone.style.display = "block"; - this.microphoneBtn.classList.remove("disabled"); + this.enableMicrophoneStyle(); this.constraintsMedia.audio = true; this.getCamera().then((stream) => { @@ -189,10 +220,7 @@ export class MediaManager { } public async disableMicrophone() { - this.microphoneClose.style.display = "block"; - this.microphone.style.display = "none"; - this.microphoneBtn.classList.add("disabled"); - this.constraintsMedia.audio = false; + this.disableMicrophoneStyle(); this.stopMicrophone(); if (this.constraintsMedia.video !== false) { @@ -203,6 +231,52 @@ export class MediaManager { } } + private applyPreviousConfig() { + this.constraintsMedia = this.previousConstraint; + if(!this.constraintsMedia.video){ + this.disableCameraStyle(); + }else{ + this.enableCameraStyle(); + } + if(!this.constraintsMedia.audio){ + this.disableMicrophoneStyle() + }else{ + this.enableMicrophoneStyle() + } + + this.getCamera().then((stream: MediaStream) => { + this.triggerUpdatedLocalStreamCallbacks(stream); + }); + } + + private enableCameraStyle(){ + this.cinemaClose.style.display = "none"; + this.cinemaBtn.classList.remove("disabled"); + this.cinema.style.display = "block"; + } + + private disableCameraStyle(){ + this.cinemaClose.style.display = "block"; + this.cinema.style.display = "none"; + this.cinemaBtn.classList.add("disabled"); + this.constraintsMedia.video = false; + this.myCamVideo.srcObject = null; + this.stopCamera(); + } + + private enableMicrophoneStyle(){ + this.microphoneClose.style.display = "none"; + this.microphone.style.display = "block"; + this.microphoneBtn.classList.remove("disabled"); + } + + private disableMicrophoneStyle(){ + this.microphoneClose.style.display = "block"; + this.microphone.style.display = "none"; + this.microphoneBtn.classList.add("disabled"); + this.constraintsMedia.audio = false; + } + private enableScreenSharing() { this.monitorClose.style.display = "none"; this.monitor.style.display = "block"; @@ -285,7 +359,7 @@ export class MediaManager { return this.getLocalStream().catch(() => { console.info('Error get camera, trying with video option at null'); - this.disabledCameraView(); + this.disableCameraStyle(); return this.getLocalStream().then((stream : MediaStream) => { this.hasCamera = false; return stream; @@ -594,6 +668,30 @@ export class MediaManager { public removeParticipant(userId: number|string){ this.discussionManager.removeParticipant(userId); } + public addTriggerCloseJitsiFrameButton(id: String, Function: Function){ + this.triggerCloseJistiFrame.set(id, Function); + } + + public removeTriggerCloseJitsiFrameButton(id: String){ + this.triggerCloseJistiFrame.delete(id); + } + + private triggerCloseJitsiFrameButton(): void { + for (const callback of this.triggerCloseJistiFrame.values()) { + callback(); + } + } + /** + * For some reasons, the microphone muted icon or the stream is not always up to date. + * Here, every 30 seconds, we are "reseting" the streams and sending again the constraints to the other peers via the data channel again (see SimplePeer::pushVideoToRemoteUser) + **/ + private pingCameraStatus(){ + setTimeout(() => { + console.log('ping camera status'); + this.triggerUpdatedLocalStreamCallbacks(this.localStream); + this.pingCameraStatus(); + }, 30000); + } public addNewMessage(name: string, message: string, isMe: boolean = false){ this.discussionManager.addMessage(name, message, isMe); @@ -615,6 +713,22 @@ export class MediaManager { public setUserInputManager(userInputManager : UserInputManager){ this.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.blurCamera(); + }else{ + this.focusCamera(); + } + this.checkActiveUser(); + }, this.focused ? 10000 : 1000); + } } export const mediaManager = new MediaManager(); diff --git a/website/package-lock.json b/website/package-lock.json index 286ca3f0..947eb4ff 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -2251,9 +2251,9 @@ } }, "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz", + "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==", "dev": true, "requires": { "is-obj": "^1.0.0"