From 24fb605f50c823ee8d9347d6c4dc8255d6bc6b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 7 Aug 2020 23:39:06 +0200 Subject: [PATCH 01/51] Switching to definitely assigned parameters This allows us to go in "full strict mode" (yeah!) See https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#strict-class-initialization --- front/src/Phaser/Entity/RemotePlayer.ts | 2 -- front/src/Phaser/Game/GameManager.ts | 4 +-- front/src/Phaser/Game/GameScene.ts | 28 +++++++++---------- front/src/Phaser/Login/CustomizeScene.ts | 18 ++++++------ front/src/Phaser/Login/EnableCameraScene.ts | 22 +++++++-------- .../src/Phaser/Login/SelectCharacterScene.ts | 14 +++++----- front/src/Phaser/Player/Player.ts | 4 +-- .../src/Phaser/Reconnecting/FourOFourScene.ts | 12 ++++---- .../Phaser/Reconnecting/ReconnectingScene.ts | 5 ++-- front/tsconfig.json | 4 +-- 10 files changed, 55 insertions(+), 58 deletions(-) diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index 18785331..6764ff59 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -7,8 +7,6 @@ import {Character} from "../Entity/Character"; */ export class RemotePlayer extends Character { userId: string; - previousDirection: string; - wasMoving: boolean; constructor( userId: string, diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 3dcf3474..db119a13 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -13,8 +13,8 @@ export interface HasMovedEvent { } export class GameManager { - private playerName: string; - private characterLayers: string[]; + private playerName!: string; + private characterLayers!: string[]; public setPlayerName(name: string): void { this.playerName = name; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 1a3cd9b7..487f3fb1 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -73,31 +73,31 @@ interface DeleteGroupEventInterface { export class GameScene extends Phaser.Scene { GameManager : GameManager; Terrains : Array; - CurrentPlayer: CurrentGamerInterface; - MapPlayers : Phaser.Physics.Arcade.Group; + CurrentPlayer!: CurrentGamerInterface; + MapPlayers!: Phaser.Physics.Arcade.Group; MapPlayersByKey : Map = new Map(); - Map: Phaser.Tilemaps.Tilemap; - Layers : Array; - Objects : Array; - mapFile: ITiledMap; + Map!: Phaser.Tilemaps.Tilemap; + Layers!: Array; + Objects!: Array; + mapFile!: ITiledMap; groups: Map; - startX: number; - startY: number; - circleTexture: CanvasTexture; + startX!: number; + startY!: number; + circleTexture!: CanvasTexture; pendingEvents: Queue = new Queue(); private initPosition: PositionInterface|null = null; private playersPositionInterpolator = new PlayersPositionInterpolator(); - private connection: Connection; - private simplePeer : SimplePeer; - private connectionPromise: Promise + private connection!: Connection; + private simplePeer!: SimplePeer; + private connectionPromise!: Promise MapKey: string; MapUrlFile: string; RoomId: string; instance: string; - currentTick: number; - lastSentTick: number; // The last tick at which a position was sent. + currentTick!: number; + lastSentTick!: number; // The last tick at which a position was sent. lastMoveEventSent: HasMovedEvent = { direction: '', moving: false, diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index a4c4282f..50834bd1 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -18,24 +18,24 @@ enum CustomizeTextures{ export class CustomizeScene extends Phaser.Scene { - private textField: TextField; - private enterField: TextField; + private textField!: TextField; + private enterField!: TextField; - private arrowRight: Image; - private arrowLeft: Image; + private arrowRight!: Image; + private arrowLeft!: Image; - private arrowDown: Image; - private arrowUp: Image; + private arrowDown!: Image; + private arrowUp!: Image; - private Rectangle: Rectangle; + private Rectangle!: Rectangle; - private logo: Image; + private logo!: Image; private selectedLayers: Array = [0]; private containersRow: Array> = new Array>(); private activeRow = 0; - private repositionCallback: (this: Window, ev: UIEvent) => void; + private repositionCallback!: (this: Window, ev: UIEvent) => void; constructor() { super({ diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts index 6d96459e..7e631b6b 100644 --- a/front/src/Phaser/Login/EnableCameraScene.ts +++ b/front/src/Phaser/Login/EnableCameraScene.ts @@ -21,22 +21,22 @@ enum LoginTextures { } export class EnableCameraScene extends Phaser.Scene { - private textField: TextField; - private pressReturnField: TextField; - private cameraNameField: TextField; - private logo: Image; - private arrowLeft: Image; - private arrowRight: Image; - private arrowDown: Image; - private arrowUp: Image; + private textField!: TextField; + private pressReturnField!: TextField; + private cameraNameField!: TextField; + private logo!: Image; + private arrowLeft!: Image; + private arrowRight!: Image; + private arrowDown!: Image; + private arrowUp!: Image; private microphonesList: MediaDeviceInfo[] = new Array(); private camerasList: MediaDeviceInfo[] = new Array(); private cameraSelected: number = 0; private microphoneSelected: number = 0; private soundMeter: SoundMeter; - private soundMeterSprite: SoundMeterSprite; - private microphoneNameField: TextField; - private repositionCallback: (this: Window, ev: UIEvent) => void; + private soundMeterSprite!: SoundMeterSprite; + private microphoneNameField!: TextField; + private repositionCallback!: (this: Window, ev: UIEvent) => void; constructor() { super({ diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 535529ee..64285766 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -21,16 +21,16 @@ enum LoginTextures { export class SelectCharacterScene extends Phaser.Scene { private readonly nbCharactersPerRow = 4; - private textField: TextField; - private pressReturnField: TextField; - private logo: Image; - private customizeButton: Image; - private customizeButtonSelected: Image; + private textField!: TextField; + private pressReturnField!: TextField; + private logo!: Image; + private customizeButton!: Image; + private customizeButtonSelected!: Image; - private selectedRectangle: Rectangle; + private selectedRectangle!: Rectangle; private selectedRectangleXPos = 0; // Number of the character selected in the rows private selectedRectangleYPos = 0; // Number of the character selected in the columns - private selectedPlayer: Phaser.Physics.Arcade.Sprite|null; // null if we are selecting the "customize" option + private selectedPlayer!: Phaser.Physics.Arcade.Sprite|null; // null if we are selecting the "customize" option private players: Array = new Array(); constructor() { diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index b9490c8d..cfd6cc6e 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -13,8 +13,8 @@ export interface CurrentGamerInterface extends Character{ export class Player extends Character implements CurrentGamerInterface { userInputManager: UserInputManager; - previousDirection: string; - wasMoving: boolean; + previousDirection: string = PlayerAnimationNames.WalkDown; + wasMoving: boolean = false; constructor( Scene: GameScene, diff --git a/front/src/Phaser/Reconnecting/FourOFourScene.ts b/front/src/Phaser/Reconnecting/FourOFourScene.ts index 8c71ae65..0c91a5bc 100644 --- a/front/src/Phaser/Reconnecting/FourOFourScene.ts +++ b/front/src/Phaser/Reconnecting/FourOFourScene.ts @@ -10,12 +10,12 @@ enum Textures { } export class FourOFourScene extends Phaser.Scene { - private mapNotFoundField: TextField; - private couldNotFindField: TextField; - private fileNameField: Text; - private logo: Image; - private cat: Sprite; - private file: string; + private mapNotFoundField!: TextField; + private couldNotFindField!: TextField; + private fileNameField!: Text; + private logo!: Image; + private cat!: Sprite; + private file!: string; constructor() { super({ diff --git a/front/src/Phaser/Reconnecting/ReconnectingScene.ts b/front/src/Phaser/Reconnecting/ReconnectingScene.ts index 7a377b66..07d2b858 100644 --- a/front/src/Phaser/Reconnecting/ReconnectingScene.ts +++ b/front/src/Phaser/Reconnecting/ReconnectingScene.ts @@ -9,9 +9,8 @@ enum ReconnectingTextures { } export class ReconnectingScene extends Phaser.Scene { - private reconnectingField: TextField; - private logo: Image; - private cat: Sprite; + private reconnectingField!: TextField; + private logo!: Image; constructor() { super({ diff --git a/front/tsconfig.json b/front/tsconfig.json index 1661efa2..e56a6ee7 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -10,12 +10,12 @@ "jsx": "react", "allowJs": true, - "strict": false, /* Enable all strict type-checking options. */ + "strict": true, /* Enable all strict type-checking options. */ "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ "strictNullChecks": true, /* Enable strict null checks. */ "strictFunctionTypes": true, /* Enable strict checking of function types. */ "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - "strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */ + "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ From 7232bbaef9adedd5244ff5c88777dd73898dc017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 11 Aug 2020 22:32:55 +0200 Subject: [PATCH 02/51] Adding LayoutManager to position videos as cleverly as possible --- front/src/WebRtc/HtmlUtils.ts | 10 ++++ front/src/WebRtc/LayoutManager.ts | 94 +++++++++++++++++++++++++++++++ front/src/index.ts | 2 +- 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 front/src/WebRtc/HtmlUtils.ts create mode 100644 front/src/WebRtc/LayoutManager.ts diff --git a/front/src/WebRtc/HtmlUtils.ts b/front/src/WebRtc/HtmlUtils.ts new file mode 100644 index 00000000..c2e6ff6d --- /dev/null +++ b/front/src/WebRtc/HtmlUtils.ts @@ -0,0 +1,10 @@ +export class HtmlUtils { + public static getElementByIdOrFail(id: string): T { + const elem = document.getElementById(id); + if (elem === null) { + throw new Error("Cannot find HTML element with id '"+id+"'"); + } + // FIXME: does not check the type of the returned type + return elem as T; + } +} diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts new file mode 100644 index 00000000..cf986d0c --- /dev/null +++ b/front/src/WebRtc/LayoutManager.ts @@ -0,0 +1,94 @@ +import {HtmlUtils} from "./HtmlUtils"; + +export enum LayoutMode { + // All videos are displayed on the right side of the screen. If there is a screen sharing, it is displayed in the middle. + Presentation = "Presentation", + // Videos take the whole page. + VideoChat = "VideoChat", +} + +export enum DivImportance { + // For screen sharing + Important = "Important", + // For normal video + Normal = "Normal", +} + +/** + * This class is in charge of the video-conference layout. + * It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode. + */ +export class LayoutManager { + private mode: LayoutMode = LayoutMode.Presentation; + + private importantDivs: Map = new Map(); + private normalDivs: Map = new Map(); + + public add(importance: DivImportance, userId: string, html: string): void { + const div = document.createElement('div'); + div.append(html); + div.id = "user-"+userId; + + if (importance === DivImportance.Important) { + this.importantDivs.set(userId, div); + + // If this is the first video with high importance, let's switch mode automatically. + if (this.importantDivs.size === 1 && this.mode === LayoutMode.VideoChat) { + this.switchLayoutMode(LayoutMode.Presentation); + } + } else if (importance === DivImportance.Normal) { + this.normalDivs.set(userId, div); + } else { + throw new Error('Unexpected importance'); + } + + this.positionDiv(div, importance); + } + + private positionDiv(elem: HTMLDivElement, importance: DivImportance): void { + if (this.mode === LayoutMode.VideoChat) { + const chatModeDiv = HtmlUtils.getElementByIdOrFail('chat-mode'); + chatModeDiv.appendChild(elem); + } else { + if (importance === DivImportance.Important) { + const mainSectionDiv = HtmlUtils.getElementByIdOrFail('main-section'); + mainSectionDiv.appendChild(elem); + } else if (importance === DivImportance.Normal) { + const sideBarDiv = HtmlUtils.getElementByIdOrFail('sidebar'); + sideBarDiv.appendChild(elem); + } + } + } + + /** + * Removes the DIV matching userId. + */ + public remove(userId: string): void { + let div = this.importantDivs.get(userId); + if (div !== undefined) { + div.remove(); + this.importantDivs.delete(userId); + return; + } + + div = this.normalDivs.get(userId); + if (div !== undefined) { + div.remove(); + this.normalDivs.delete(userId); + return; + } + + throw new Error('Could not find user ID "'+userId+'"'); + } + + private switchLayoutMode(layoutMode: LayoutMode) { + this.mode = layoutMode; + + for (let div of this.importantDivs.values()) { + this.positionDiv(div, DivImportance.Important); + } + for (let div of this.normalDivs.values()) { + this.positionDiv(div, DivImportance.Normal); + } + } +} diff --git a/front/src/index.ts b/front/src/index.ts index 7634351f..d64a8f2e 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -11,7 +11,7 @@ import {FourOFourScene} from "./Phaser/Reconnecting/FourOFourScene"; import {CustomizeScene} from "./Phaser/Login/CustomizeScene"; const config: GameConfig = { - title: "Office game", + title: "WorkAdventure", width: window.innerWidth / RESOLUTION, height: window.innerHeight / RESOLUTION, parent: "game", From 83fe024c452f207b3c8154cb55fec07242066aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 11 Aug 2020 22:40:54 +0200 Subject: [PATCH 03/51] Adjusting class in chat mode based on number of divs displayed. --- front/src/WebRtc/LayoutManager.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index cf986d0c..63a02356 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -43,6 +43,7 @@ export class LayoutManager { } this.positionDiv(div, importance); + this.adjustVideoChatClass(); } private positionDiv(elem: HTMLDivElement, importance: DivImportance): void { @@ -68,6 +69,7 @@ export class LayoutManager { if (div !== undefined) { div.remove(); this.importantDivs.delete(userId); + this.adjustVideoChatClass(); return; } @@ -75,12 +77,30 @@ export class LayoutManager { if (div !== undefined) { div.remove(); this.normalDivs.delete(userId); + this.adjustVideoChatClass(); return; } throw new Error('Could not find user ID "'+userId+'"'); } + private adjustVideoChatClass(): void { + const chatModeDiv = HtmlUtils.getElementByIdOrFail('chat-mode'); + chatModeDiv.classList.remove('one-col', 'two-col', 'three-col', 'four-col'); + + const nbUsers = this.importantDivs.size + this.normalDivs.size; + + if (nbUsers <= 1) { + chatModeDiv.classList.add('one-col'); + } else if (nbUsers <= 4) { + chatModeDiv.classList.add('two-col'); + } else if (nbUsers <= 9) { + chatModeDiv.classList.add('three-col'); + } else { + chatModeDiv.classList.add('four-col'); + } + } + private switchLayoutMode(layoutMode: LayoutMode) { this.mode = layoutMode; From 9f6c6e0ce18172b5c57c4a391bc4c275a55978ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 13 Aug 2020 18:21:48 +0200 Subject: [PATCH 04/51] Adding CoWebsiteManager + first working version of flex video --- front/dist/index.html | 52 ++++++- front/dist/resources/style/style.css | 196 ++++++++++++++++++++++----- front/src/WebRtc/CoWebsiteManager.ts | 56 ++++++++ front/src/WebRtc/LayoutManager.ts | 22 ++- front/src/WebRtc/MediaManager.ts | 31 ++++- front/src/index.ts | 20 ++- 6 files changed, 326 insertions(+), 51 deletions(-) create mode 100644 front/src/WebRtc/CoWebsiteManager.ts diff --git a/front/dist/index.html b/front/dist/index.html index a680c59a..92a7bf3c 100644 --- a/front/dist/index.html +++ b/front/dist/index.html @@ -39,7 +39,53 @@ WorkAdventure -
+
+
+
+
+ +
+ + + + +
+
+ +
+
+
+ + +
+
+ + +
+
+
+ +
+
+
+
+
+ -->
diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 5f0e1cab..45b61679 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -27,7 +27,7 @@ video{ -webkit-transform: scaleX(-1); transform: scaleX(-1); } -.webrtc{ +/*.webrtc{ display: none; position: absolute; right: 0px; @@ -36,21 +36,22 @@ video{ } .webrtc.active{ display: block; -} +}*/ -.webrtc, .activeCam{} -.activeCam .video-container{ - position: absolute; - height: 25%; +/*.webrtc, .activeCam{}*/ +/*.activeCam*/ .video-container{ + position: relative; + /*height: 25%; top: 10px; margin: 5px; right: -100px; - transition: all 0.2s ease; - border-color: black; + transition: all 0.2s ease;*/ + /*border-color: black; border-style: solid; - border-width: 0.2px; + border-width: 0.2px;*/ + background-color: #00000099; } -.activeCam .video-container i{ +/*.activeCam*/ .video-container i{ position: absolute; width: 100px; height: 65px; @@ -63,10 +64,10 @@ video{ font-size: 28px; color: white; } -.activeCam .video-container img.active{ +/*.activeCam*/ .video-container img.active{ display: block; } -.activeCam .video-container img{ +/*.activeCam*/ .video-container img{ position: absolute; display: none; width: 15px; @@ -78,34 +79,28 @@ video{ padding: 10px; z-index: 2; } -.activeCam .video-container video{ +/*.activeCam*/ .video-container video{ height: 100%; } -.webrtc:hover .activeCam .video-container{ +/*.webrtc:hover .activeCam .video-container{ right: 10px; -} -.activeCam .video-container#div-myCamVideo{ +}*/ +/*.activeCam*/ .video-container#div-myCamVideo{ border: none; } -.activeCam .video-container video#myCamVideo{ - width: 200px; - height: 113px; +/*.activeCam*/ + +#div-myCamVideo { + position: fixed; + right: 0; + bottom: 0; } -/*CSS size for 2 - 3 elements*/ -.activeCam .video-container:nth-child(1){ - /*this is for camera of user*/ - top: 75%; -} -.activeCam .video-container:nth-child(2){ - top: 0%; -} -.activeCam .video-container:nth-child(3){ - top: 25%; -} -.activeCam .video-container:nth-child(4) { - top: 50%; +video#myCamVideo{ + width: 15vw; + /*width: 200px;*/ + /*height: 113px;*/ } /*btn animation*/ @@ -122,7 +117,7 @@ video{ transition-timing-function: ease-in-out; bottom: 20px; } -.webrtc:hover .btn-cam-action div{ +#activeCam:hover .btn-cam-action div{ transform: translateY(0); } .btn-cam-action div:hover{ @@ -237,3 +232,138 @@ video{ .webrtcsetup.active{ display: block; } + + +/* New layout */ +body { + margin: 0; + height: 100vh; + width: 100vw; +} +.main-container { + height: 100vh; + width: 100vw; + display: flex; + align-items: stretch; +} + +@media (min-aspect-ratio: 1/1) { + .main-container { + flex-direction: row + } + + .game-overlay { + flex-direction: row + } + + .sidebar { + flex-direction: column + } +} +@media (max-aspect-ratio: 1/1) { + .main-container { + flex-direction: column + } + + .game-overlay { + flex-direction: column + } + + .sidebar { + flex-direction: row + } +} + +.game { + flex-basis: 100%; + position: relative; /* Position relative is needed for the game-overlay. */ +} + +/* A potentially shared website could appear in an iframe in the cowebsite space. */ +.cowebsite { + flex-basis: 100%; + transition: flex-basis 0.5s; +} + +/*.cowebsite:hover { + flex-basis: 100%; +}*/ + +.cowebsite iframe { + width: 100%; + height: 100%; +} + + +.game-overlay { + display: none; + position: absolute; + width: 100%; + height: 100%; + /* TODO: DO WE NEED FLEX HERE???? WE WANT A SIDEBAR OF EXACTLY 25% (note: flex useful for direction!!!) */ +} + +.game-overlay.active { + display: flex; +} + +.game-overlay video { + width: 100% +} + +.main-section { + flex: 0 0 75%; + display: flex; + justify-content: start; + /*align-items: flex-start;*/ + flex-wrap: wrap; +} + +.main-section div { + margin: 5%; + flex-basis: 90%; + /*flex-shrink: 2;*/ +} + +.sidebar { + flex: 0 0 25%; + display: flex; +} + +.sidebar > div { + height: 15%; + margin: 5%; +} + +.chat-mode { + display: flex; + width: 100%; + + flex-wrap: wrap; + + padding: 1%; +} + +.chat-mode div { + margin: 1%; +} + +.chat-mode.one-col div { + flex-basis: 98%; +} + +.chat-mode.two-col div { + flex-basis: 48%; +} + +.chat-mode.three-col div { + flex-basis: 31.333333%; +} + +.chat-mode.four-col div { + flex-basis: 23%; +} + +.chat-mode div:last-child { + flex-grow: 5; +} diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts new file mode 100644 index 00000000..0150760c --- /dev/null +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -0,0 +1,56 @@ +import {HtmlUtils} from "./HtmlUtils"; + +export type CoWebsiteStateChangedCallback = () => void; + +export class CoWebsiteManager { + + private static observers = new Array(); + + public static loadCoWebsite(url: string): void { + const cowebsiteDiv = HtmlUtils.getElementByIdOrFail("cowebsite"); + cowebsiteDiv.innerHTML = ''; + + const iframe = document.createElement('iframe'); + iframe.id = 'cowebsite-iframe'; + iframe.src = url; + cowebsiteDiv.appendChild(iframe); + CoWebsiteManager.fire(); + } + + public static closeCoWebsite(): void { + const cowebsiteDiv = HtmlUtils.getElementByIdOrFail("cowebsite"); + cowebsiteDiv.innerHTML = ''; + CoWebsiteManager.fire(); + } + + public static getGameSize(): {width: number, height: number} { + const iframe = document.getElementById('cowebsite-iframe'); + if (iframe === null) { + return { + width: window.innerWidth, + height: window.innerHeight + } + } + if (window.innerWidth >= window.innerHeight) { + return { + width: window.innerWidth / 2, + height: window.innerHeight + } + } else { + return { + width: window.innerWidth, + height: window.innerHeight / 2 + } + } + } + + public static onStateChange(observer: CoWebsiteStateChangedCallback) { + CoWebsiteManager.observers.push(observer); + } + + private static fire(): void { + for (const callback of CoWebsiteManager.observers) { + callback(); + } + } +} diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index 63a02356..670e05bd 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -18,7 +18,7 @@ export enum DivImportance { * This class is in charge of the video-conference layout. * It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode. */ -export class LayoutManager { +class LayoutManager { private mode: LayoutMode = LayoutMode.Presentation; private importantDivs: Map = new Map(); @@ -26,7 +26,7 @@ export class LayoutManager { public add(importance: DivImportance, userId: string, html: string): void { const div = document.createElement('div'); - div.append(html); + div.innerHTML = html; div.id = "user-"+userId; if (importance === DivImportance.Important) { @@ -65,6 +65,7 @@ export class LayoutManager { * Removes the DIV matching userId. */ public remove(userId: string): void { + console.log('Removing video for userID '+userId+'.'); let div = this.importantDivs.get(userId); if (div !== undefined) { div.remove(); @@ -81,7 +82,8 @@ export class LayoutManager { return; } - throw new Error('Could not find user ID "'+userId+'"'); + console.log('Cannot remove userID '+userId+'. Already removed?'); + //throw new Error('Could not find user ID "'+userId+'"'); } private adjustVideoChatClass(): void { @@ -104,6 +106,16 @@ export class LayoutManager { private switchLayoutMode(layoutMode: LayoutMode) { this.mode = layoutMode; + if (layoutMode === LayoutMode.Presentation) { + HtmlUtils.getElementByIdOrFail('sidebar').style.display = 'block'; + HtmlUtils.getElementByIdOrFail('main-section').style.display = 'block'; + HtmlUtils.getElementByIdOrFail('chat-mode').style.display = 'none'; + } else { + HtmlUtils.getElementByIdOrFail('sidebar').style.display = 'none'; + HtmlUtils.getElementByIdOrFail('main-section').style.display = 'none'; + HtmlUtils.getElementByIdOrFail('chat-mode').style.display = 'block'; + } + for (let div of this.importantDivs.values()) { this.positionDiv(div, DivImportance.Important); } @@ -112,3 +124,7 @@ export class LayoutManager { } } } + +const layoutManager = new LayoutManager(); + +export { layoutManager }; diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index e69850a2..fb7c34f1 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -1,3 +1,5 @@ +import {DivImportance, layoutManager} from "./LayoutManager"; + const videoConstraint: boolean|MediaTrackConstraints = { width: { ideal: 1280 }, height: { ideal: 720 }, @@ -73,8 +75,8 @@ export class MediaManager { } activeVisio(){ - const webRtc = this.getElementByIdOrFail('webRtc'); - webRtc.classList.add('active'); + const gameOverlay = this.getElementByIdOrFail('game-overlay'); + gameOverlay.classList.add('active'); } enabledCamera() { @@ -184,10 +186,11 @@ export class MediaManager { */ addActiveVideo(userId : string, userName: string = ""){ this.webrtcInAudio.play(); - const elementRemoteVideo = this.getElementByIdOrFail("activeCam"); + + //const elementRemoteVideo = this.getElementByIdOrFail("activeCam"); userName = userName.toUpperCase(); const color = this.getColorByString(userName); - elementRemoteVideo.insertAdjacentHTML('beforeend', ` + /*elementRemoteVideo.insertAdjacentHTML('beforeend', `
@@ -195,7 +198,20 @@ export class MediaManager {
- `); + `);*/ + + const html = ` +
+
+ + ${userName} + + +
+ `; + + layoutManager.add(DivImportance.Normal, userId, html); + this.remoteVideo.set(userId, this.getElementByIdOrFail(userId)); } @@ -274,11 +290,12 @@ export class MediaManager { * @param userId */ removeActiveVideo(userId : string){ - const element = document.getElementById(`div-${userId}`); + /*const element = document.getElementById(`div-${userId}`); if(!element){ return; } - element.remove(); + element.remove();*/ + layoutManager.remove(userId); this.remoteVideo.delete(userId); } diff --git a/front/src/index.ts b/front/src/index.ts index d64a8f2e..75ad0fe6 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -4,16 +4,21 @@ import {DEBUG_MODE, RESOLUTION} from "./Enum/EnvironmentVariable"; import {cypressAsserter} from "./Cypress/CypressAsserter"; import {LoginScene} from "./Phaser/Login/LoginScene"; import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene"; -import {gameManager} from "./Phaser/Game/GameManager"; import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene"; import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene"; import {FourOFourScene} from "./Phaser/Reconnecting/FourOFourScene"; import {CustomizeScene} from "./Phaser/Login/CustomizeScene"; +import {HtmlUtils} from "./WebRtc/HtmlUtils"; +import {CoWebsiteManager} from "./WebRtc/CoWebsiteManager"; + +//CoWebsiteManager.loadCoWebsite('https://thecodingmachine.com'); + +const {width, height} = CoWebsiteManager.getGameSize(); const config: GameConfig = { title: "WorkAdventure", - width: window.innerWidth / RESOLUTION, - height: window.innerHeight / RESOLUTION, + width: width / RESOLUTION, + height: height / RESOLUTION, parent: "game", scene: [LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, FourOFourScene, CustomizeScene], zoom: RESOLUTION, @@ -30,5 +35,12 @@ cypressAsserter.gameStarted(); const game = new Phaser.Game(config); window.addEventListener('resize', function (event) { - game.scale.resize(window.innerWidth / RESOLUTION, window.innerHeight / RESOLUTION); + const {width, height} = CoWebsiteManager.getGameSize(); + + game.scale.resize(width / RESOLUTION, height / RESOLUTION); +}); +CoWebsiteManager.onStateChange(() => { + const {width, height} = CoWebsiteManager.getGameSize(); + + game.scale.resize(width / RESOLUTION, height / RESOLUTION); }); From 0041e088a4289b2517326d8284bfcd5f83e8f21c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 13 Aug 2020 18:28:22 +0200 Subject: [PATCH 05/51] Fixing position of self webcam in CoWebsite mode --- front/dist/resources/style/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 45b61679..9fe4c9ea 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -92,7 +92,7 @@ video{ /*.activeCam*/ #div-myCamVideo { - position: fixed; + position: absolute; right: 0; bottom: 0; } From 88c099fc1390ab9d2c9354caf9accb13f3c641fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sun, 16 Aug 2020 23:19:04 +0200 Subject: [PATCH 06/51] Improving layout Fixing left-right switch on all cameras (except current player camera) --- front/dist/resources/style/style.css | 45 ++++++++++++++++------------ front/src/Phaser/Game/GameScene.ts | 41 +++++++++++++++---------- front/src/WebRtc/LayoutManager.ts | 6 +++- front/src/WebRtc/MediaManager.ts | 16 +++++----- 4 files changed, 63 insertions(+), 45 deletions(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 9fe4c9ea..8f188cca 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -23,10 +23,6 @@ body .message-info.info{ body .message-info.warning{ background: #ffa500d6; } -video{ - -webkit-transform: scaleX(-1); - transform: scaleX(-1); -} /*.webrtc{ display: none; position: absolute; @@ -99,10 +95,13 @@ video{ video#myCamVideo{ width: 15vw; + -webkit-transform: scaleX(-1); + transform: scaleX(-1); /*width: 200px;*/ /*height: 113px;*/ } + /*btn animation*/ .btn-cam-action div{ cursor: pointer; @@ -113,7 +112,7 @@ video#myCamVideo{ background: #666; box-shadow: 2px 2px 24px #444; border-radius: 48px; - transform: translateY(12vw); + transform: translateY(12vh); transition-timing-function: ease-in-out; bottom: 20px; } @@ -249,28 +248,37 @@ body { @media (min-aspect-ratio: 1/1) { .main-container { - flex-direction: row + flex-direction: row; } .game-overlay { - flex-direction: row + flex-direction: row; } .sidebar { - flex-direction: column + flex-direction: column; + } + + .sidebar > div { + height: 15%; } } @media (max-aspect-ratio: 1/1) { .main-container { - flex-direction: column + flex-direction: column; } .game-overlay { - flex-direction: column + flex-direction: column; } .sidebar { - flex-direction: row + flex-direction: row; + align-items: flex-end; + } + + .sidebar > div { + width: 15%; } } @@ -319,7 +327,7 @@ body { flex-wrap: wrap; } -.main-section div { +.main-section > div { margin: 5%; flex-basis: 90%; /*flex-shrink: 2;*/ @@ -331,8 +339,7 @@ body { } .sidebar > div { - height: 15%; - margin: 5%; + margin: 2%; } .chat-mode { @@ -348,22 +355,22 @@ body { margin: 1%; } -.chat-mode.one-col div { +.chat-mode.one-col > div { flex-basis: 98%; } -.chat-mode.two-col div { +.chat-mode.two-col > div { flex-basis: 48%; } -.chat-mode.three-col div { +.chat-mode.three-col > div { flex-basis: 31.333333%; } -.chat-mode.four-col div { +.chat-mode.four-col > div { flex-basis: 23%; } -.chat-mode div:last-child { +.chat-mode > div:last-child { flex-grow: 5; } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 487f3fb1..6505ad11 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,34 +1,33 @@ import {GameManager, gameManager, HasMovedEvent} from "./GameManager"; import { Connection, - GroupCreatedUpdatedMessageInterface, MessageUserJoined, + GroupCreatedUpdatedMessageInterface, + MessageUserJoined, MessageUserMovedInterface, - MessageUserPositionInterface, PointInterface, PositionInterface + MessageUserPositionInterface, + PointInterface, + PositionInterface } from "../../Connection"; import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player"; -import { DEBUG_MODE, ZOOM_LEVEL, POSITION_DELAY } from "../../Enum/EnvironmentVariable"; -import { - ITiledMap, - ITiledMapLayer, - ITiledMapLayerProperty, - ITiledTileSet -} from "../Map/ITiledMap"; +import {DEBUG_MODE, POSITION_DELAY, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; +import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledTileSet} from "../Map/ITiledMap"; import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character"; -import Texture = Phaser.Textures.Texture; -import Sprite = Phaser.GameObjects.Sprite; -import CanvasTexture = Phaser.Textures.CanvasTexture; import {AddPlayerInterface} from "./AddPlayerInterface"; import {PlayerAnimationNames} from "../Player/Animation"; import {PlayerMovement} from "./PlayerMovement"; import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator"; import {RemotePlayer} from "../Entity/RemotePlayer"; -import GameObject = Phaser.GameObjects.GameObject; -import { Queue } from 'queue-typescript'; +import {Queue} from 'queue-typescript'; import {SimplePeer} from "../../WebRtc/SimplePeer"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; -import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import {FourOFourSceneName} from "../Reconnecting/FourOFourScene"; -import {LAYERS, loadAllLayers} from "../Entity/body_character"; +import {loadAllLayers} from "../Entity/body_character"; +import {layoutManager, LayoutMode} from "../../WebRtc/LayoutManager"; +import Texture = Phaser.Textures.Texture; +import Sprite = Phaser.GameObjects.Sprite; +import CanvasTexture = Phaser.Textures.CanvasTexture; +import GameObject = Phaser.GameObjects.GameObject; +import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; export enum Textures { @@ -364,6 +363,16 @@ export class GameScene extends Phaser.Scene { } }, 500); } + + // FIXME: change this to use the class for input + this.input.keyboard.on('keyup-' + 'M', function () { + const mode = layoutManager.getLayoutMode(); + if (mode === LayoutMode.Presentation) { + layoutManager.switchLayoutMode(LayoutMode.VideoChat); + } else { + layoutManager.switchLayoutMode(LayoutMode.Presentation); + } + }); } private getExitSceneUrl(layer: ITiledMapLayer): string|undefined { diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index 670e05bd..7e99d496 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -103,7 +103,7 @@ class LayoutManager { } } - private switchLayoutMode(layoutMode: LayoutMode) { + public switchLayoutMode(layoutMode: LayoutMode) { this.mode = layoutMode; if (layoutMode === LayoutMode.Presentation) { @@ -123,6 +123,10 @@ class LayoutManager { this.positionDiv(div, DivImportance.Normal); } } + + public getLayoutMode(): LayoutMode { + return this.mode; + } } const layoutManager = new LayoutManager(); diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index fb7c34f1..9804317b 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -204,7 +204,7 @@ export class MediaManager {
- ${userName} + ${userName}
@@ -248,11 +248,10 @@ export class MediaManager { if (element) { element.style.opacity = "0"; } - element = document.getElementById(`div-${userId}`); - if (!element) { - return; + element = document.getElementById(`name-${userId}`); + if (element) { + element.style.display = "block"; } - element.style.borderStyle = "solid"; } /** @@ -264,11 +263,10 @@ export class MediaManager { if(element){ element.style.opacity = "1"; } - element = document.getElementById(`div-${userId}`); - if(!element){ - return; + element = document.getElementById(`name-${userId}`); + if(element){ + element.style.display = "none"; } - element.style.borderStyle = "none"; } /** From 15097779454ae03493a706faa7b81feb79e93b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sun, 16 Aug 2020 23:45:03 +0200 Subject: [PATCH 07/51] Improving video CSS (work on overlay) --- front/dist/resources/style/style.css | 12 ++++++++++-- front/src/WebRtc/LayoutManager.ts | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 8f188cca..d7a9a560 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -260,7 +260,7 @@ body { } .sidebar > div { - height: 15%; + max-height: 21%; } } @media (max-aspect-ratio: 1/1) { @@ -278,7 +278,7 @@ body { } .sidebar > div { - width: 15%; + max-width: 21%; } } @@ -342,6 +342,14 @@ body { margin: 2%; } +/* Let's make sure videos are vertically centered if they need to be cropped */ +.media-container { + display: flex; + justify-content: center; + flex-direction: column; + overflow: hidden; +} + .chat-mode { display: flex; width: 100%; diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index 7e99d496..98c20b4c 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -28,6 +28,7 @@ class LayoutManager { const div = document.createElement('div'); div.innerHTML = html; div.id = "user-"+userId; + div.className = "media-container" if (importance === DivImportance.Important) { this.importantDivs.set(userId, div); From 05ca8c813e727b86e172682f5656989695fe0c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sun, 16 Aug 2020 23:49:31 +0200 Subject: [PATCH 08/51] Fixing chat mode canceling flex display --- front/src/WebRtc/LayoutManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index 98c20b4c..a0001693 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -108,13 +108,13 @@ class LayoutManager { this.mode = layoutMode; if (layoutMode === LayoutMode.Presentation) { - HtmlUtils.getElementByIdOrFail('sidebar').style.display = 'block'; - HtmlUtils.getElementByIdOrFail('main-section').style.display = 'block'; + HtmlUtils.getElementByIdOrFail('sidebar').style.display = 'flex'; + HtmlUtils.getElementByIdOrFail('main-section').style.display = 'flex'; HtmlUtils.getElementByIdOrFail('chat-mode').style.display = 'none'; } else { HtmlUtils.getElementByIdOrFail('sidebar').style.display = 'none'; HtmlUtils.getElementByIdOrFail('main-section').style.display = 'none'; - HtmlUtils.getElementByIdOrFail('chat-mode').style.display = 'block'; + HtmlUtils.getElementByIdOrFail('chat-mode').style.display = 'flex'; } for (let div of this.importantDivs.values()) { From 7fe2cc19c35209852b65a4d647ed473800b0cb34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 17 Aug 2020 15:20:03 +0200 Subject: [PATCH 09/51] Adding buttons to switch mode --- front/dist/resources/objects/layout_modes.png | Bin 0 -> 297 bytes front/dist/resources/style/style.css | 2 + front/src/Phaser/Game/GameScene.ts | 54 +++++++++++++++--- 3 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 front/dist/resources/objects/layout_modes.png diff --git a/front/dist/resources/objects/layout_modes.png b/front/dist/resources/objects/layout_modes.png new file mode 100644 index 0000000000000000000000000000000000000000..abd9adaf5336965b6a87b6af603dae1d24d9c43b GIT binary patch literal 297 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=A3R+gLn`LHy>XDM*+7K#Vok-r zyX&M(QYJ3b&(APEv_@o)46C=d=2I)18U|UrhIKJlKFS4VyH+uzoc_*m^SeBgi`~tc zpa0G{p>ACq$H2%UkRW~FRJelBf%Oc3**Fv$7}A-4h|aM1e?7l}VLyk!O)iHNrV9+l z4%Uom4J-l<42)6?g?Ek%m^K_f%zoh^^NSCRB|jLfz;>Ox&)9Dm`4wnO{4aNnU$g4Y zZ2$kIrmB~L31sfy8Ef1aX6Dv1hcPaiw+ div { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 6505ad11..2ef7d630 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -106,6 +106,9 @@ export class GameScene extends Phaser.Scene { private PositionNextScene: Array> = new Array>(); private startLayerName: string|undefined; + private presentationModeSprite!: Sprite; + private chatModeSprite!: Sprite; + private repositionCallback!: (this: Window, ev: UIEvent) => void; static createFromUrl(mapUrlFile: string, instance: string, key: string|null = null): GameScene { const mapKey = GameScene.getMapKeyByUrl(mapUrlFile); @@ -158,6 +161,12 @@ export class GameScene extends Phaser.Scene { ); }); + this.load.spritesheet( + 'layout_modes', + 'resources/objects/layout_modes.png', + {frameWidth: 32, frameHeight: 32} + ); + loadAllLayers(this.load); this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); @@ -213,6 +222,7 @@ export class GameScene extends Phaser.Scene { this.scene.stop(this.scene.key); this.scene.remove(this.scene.key); + window.removeEventListener('resize', this.repositionCallback); }) // When connection is performed, let's connect SimplePeer @@ -364,15 +374,39 @@ export class GameScene extends Phaser.Scene { }, 500); } + // FIXME: handle display / hide based on number of cameras connected + this.presentationModeSprite = this.add.sprite(2, this.game.renderer.height - 2, 'layout_modes', 0); + this.presentationModeSprite.setScrollFactor(0, 0); + this.presentationModeSprite.setOrigin(0, 1); + this.presentationModeSprite.setInteractive(); + this.presentationModeSprite.on('pointerup', this.switchLayoutMode.bind(this)); + this.chatModeSprite = this.add.sprite(36, this.game.renderer.height - 2, 'layout_modes', 3); + this.chatModeSprite.setScrollFactor(0, 0); + this.chatModeSprite.setOrigin(0, 1); + this.chatModeSprite.setInteractive(); + this.chatModeSprite.on('pointerup', this.switchLayoutMode.bind(this)); + // FIXME: change this to use the class for input - this.input.keyboard.on('keyup-' + 'M', function () { - const mode = layoutManager.getLayoutMode(); - if (mode === LayoutMode.Presentation) { - layoutManager.switchLayoutMode(LayoutMode.VideoChat); - } else { - layoutManager.switchLayoutMode(LayoutMode.Presentation); - } + this.input.keyboard.on('keyup-' + 'M', () => { + this.switchLayoutMode(); }); + + this.repositionCallback = this.reposition.bind(this); + window.addEventListener('resize', this.repositionCallback); + this.reposition(); + } + + private switchLayoutMode(): void { + const mode = layoutManager.getLayoutMode(); + if (mode === LayoutMode.Presentation) { + layoutManager.switchLayoutMode(LayoutMode.VideoChat); + this.presentationModeSprite.setFrame(1); + this.chatModeSprite.setFrame(2); + } else { + layoutManager.switchLayoutMode(LayoutMode.Presentation); + this.presentationModeSprite.setFrame(0); + this.chatModeSprite.setFrame(3); + } } private getExitSceneUrl(layer: ITiledMapLayer): string|undefined { @@ -634,6 +668,7 @@ export class GameScene extends Phaser.Scene { this.simplePeer.unregister(); this.scene.stop(); this.scene.remove(this.scene.key); + window.removeEventListener('resize', this.repositionCallback); this.scene.start(nextSceneKey.key, { startLayerName: nextSceneKey.hash }); @@ -821,4 +856,9 @@ export class GameScene extends Phaser.Scene { const endPos = mapUrlStart.indexOf(".json"); return mapUrlStart.substring(startPos, endPos); } + + private reposition(): void { + this.presentationModeSprite.setY(this.game.renderer.height - 2); + this.chatModeSprite.setY(this.game.renderer.height - 2); + } } From 6516e621b04b08dce54788774fd296d6712bc32d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 17 Aug 2020 16:12:53 +0200 Subject: [PATCH 10/51] Adding display / hide of layout buttons when a meet start / ends --- front/src/Phaser/Game/GameScene.ts | 22 ++++++++++++++++++---- front/src/WebRtc/SimplePeer.ts | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 2ef7d630..c4517545 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -18,7 +18,7 @@ import {PlayerMovement} from "./PlayerMovement"; import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator"; import {RemotePlayer} from "../Entity/RemotePlayer"; import {Queue} from 'queue-typescript'; -import {SimplePeer} from "../../WebRtc/SimplePeer"; +import {SimplePeer, UserSimplePeer} from "../../WebRtc/SimplePeer"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; import {FourOFourSceneName} from "../Reconnecting/FourOFourScene"; import {loadAllLayers} from "../Entity/body_character"; @@ -227,6 +227,19 @@ export class GameScene extends Phaser.Scene { // When connection is performed, let's connect SimplePeer this.simplePeer = new SimplePeer(this.connection); + const self = this; + this.simplePeer.registerPeerConnectionListener({ + onConnect(user: UserSimplePeer) { + self.presentationModeSprite.setVisible(true); + self.chatModeSprite.setVisible(true); + }, + onDisconnect(userId: string) { + if (self.simplePeer.getNbConnections() === 0) { + self.presentationModeSprite.setVisible(false); + self.chatModeSprite.setVisible(false); + } + } + }) this.scene.wake(); this.scene.sleep(ReconnectingSceneName); @@ -374,19 +387,20 @@ export class GameScene extends Phaser.Scene { }, 500); } - // FIXME: handle display / hide based on number of cameras connected this.presentationModeSprite = this.add.sprite(2, this.game.renderer.height - 2, 'layout_modes', 0); this.presentationModeSprite.setScrollFactor(0, 0); this.presentationModeSprite.setOrigin(0, 1); this.presentationModeSprite.setInteractive(); + this.presentationModeSprite.setVisible(false); this.presentationModeSprite.on('pointerup', this.switchLayoutMode.bind(this)); this.chatModeSprite = this.add.sprite(36, this.game.renderer.height - 2, 'layout_modes', 3); this.chatModeSprite.setScrollFactor(0, 0); this.chatModeSprite.setOrigin(0, 1); this.chatModeSprite.setInteractive(); + this.chatModeSprite.setVisible(false); this.chatModeSprite.on('pointerup', this.switchLayoutMode.bind(this)); - - // FIXME: change this to use the class for input + + // FIXME: change this to use the UserInputManager class for input this.input.keyboard.on('keyup-' + 'M', () => { this.switchLayoutMode(); }); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 553c9307..acb52059 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -14,6 +14,12 @@ export interface UserSimplePeer{ initiator?: boolean; } +export interface PeerConnectionListener { + onConnect(user: UserSimplePeer): void; + + onDisconnect(userId: string): void; +} + /** * This class manages connections to all the peers in the same group as me. */ @@ -24,6 +30,7 @@ export class SimplePeer { private PeerConnectionArray: Map = new Map(); private readonly updateLocalStreamCallback: (media: MediaStream) => void; + private readonly peerConnectionListeners: Array = new Array(); constructor(Connection: Connection, WebRtcRoomId: string = "test-webrtc") { this.Connection = Connection; @@ -34,6 +41,14 @@ export class SimplePeer { this.initialise(); } + public registerPeerConnectionListener(peerConnectionListener: PeerConnectionListener) { + this.peerConnectionListeners.push(peerConnectionListener); + } + + public getNbConnections(): number { + return this.PeerConnectionArray.size; + } + /** * permit to listen when user could start visio */ @@ -182,6 +197,10 @@ export class SimplePeer { }); this.addMedia(user.userId); + + for (let peerConnectionListener of this.peerConnectionListeners) { + peerConnectionListener.onConnect(user); + } } /** @@ -203,6 +222,9 @@ export class SimplePeer { peer.destroy(); this.PeerConnectionArray.delete(userId) //console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size); + for (let peerConnectionListener of this.peerConnectionListeners) { + peerConnectionListener.onDisconnect(userId); + } } catch (err) { console.error("closeConnection", err) } From beb0d1ef0aaf0dffeba8bbacf35c73ea5762e00f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 17 Aug 2020 16:18:39 +0200 Subject: [PATCH 11/51] Linter fix --- front/src/WebRtc/LayoutManager.ts | 4 ++-- front/src/WebRtc/SimplePeer.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index a0001693..6695fe7f 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -117,10 +117,10 @@ class LayoutManager { HtmlUtils.getElementByIdOrFail('chat-mode').style.display = 'flex'; } - for (let div of this.importantDivs.values()) { + for (const div of this.importantDivs.values()) { this.positionDiv(div, DivImportance.Important); } - for (let div of this.normalDivs.values()) { + for (const div of this.normalDivs.values()) { this.positionDiv(div, DivImportance.Normal); } } diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index acb52059..fdc2d0c2 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -198,7 +198,7 @@ export class SimplePeer { this.addMedia(user.userId); - for (let peerConnectionListener of this.peerConnectionListeners) { + for (const peerConnectionListener of this.peerConnectionListeners) { peerConnectionListener.onConnect(user); } } @@ -222,7 +222,7 @@ export class SimplePeer { peer.destroy(); this.PeerConnectionArray.delete(userId) //console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size); - for (let peerConnectionListener of this.peerConnectionListeners) { + for (const peerConnectionListener of this.peerConnectionListeners) { peerConnectionListener.onDisconnect(userId); } } catch (err) { From 0f305b0c1224fb5792878e68412db80d103e8982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 17 Aug 2020 21:59:26 +0200 Subject: [PATCH 12/51] CSS cleanup --- front/dist/resources/style/style.css | 37 ++++++---------------------- 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index b3d98539..30e099ef 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -23,31 +23,12 @@ body .message-info.info{ body .message-info.warning{ background: #ffa500d6; } -/*.webrtc{ - display: none; - position: absolute; - right: 0px; - height: 100%; - width: 300px; -} -.webrtc.active{ - display: block; -}*/ - -/*.webrtc, .activeCam{}*/ -/*.activeCam*/ .video-container{ +.video-container{ position: relative; - /*height: 25%; - top: 10px; - margin: 5px; - right: -100px; - transition: all 0.2s ease;*/ - /*border-color: black; - border-style: solid; - border-width: 0.2px;*/ + transition: all 0.2s ease; background-color: #00000099; } -/*.activeCam*/ .video-container i{ +.video-container i{ position: absolute; width: 100px; height: 65px; @@ -60,10 +41,10 @@ body .message-info.warning{ font-size: 28px; color: white; } -/*.activeCam*/ .video-container img.active{ +.video-container img.active{ display: block; } -/*.activeCam*/ .video-container img{ +.video-container img{ position: absolute; display: none; width: 15px; @@ -75,17 +56,13 @@ body .message-info.warning{ padding: 10px; z-index: 2; } -/*.activeCam*/ .video-container video{ +.video-container video{ height: 100%; } -/*.webrtc:hover .activeCam .video-container{ - right: 10px; -}*/ -/*.activeCam*/ .video-container#div-myCamVideo{ +.video-container#div-myCamVideo{ border: none; } -/*.activeCam*/ #div-myCamVideo { position: absolute; From fc78249eaeb53c1f00e13af77ea26a9b41693969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 17 Aug 2020 22:03:08 +0200 Subject: [PATCH 13/51] Code cleanup --- front/src/WebRtc/MediaManager.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 9804317b..39a61738 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -187,18 +187,8 @@ export class MediaManager { addActiveVideo(userId : string, userName: string = ""){ this.webrtcInAudio.play(); - //const elementRemoteVideo = this.getElementByIdOrFail("activeCam"); userName = userName.toUpperCase(); const color = this.getColorByString(userName); - /*elementRemoteVideo.insertAdjacentHTML('beforeend', ` -
-
- - ${userName} - - -
- `);*/ const html = `
@@ -288,11 +278,6 @@ export class MediaManager { * @param userId */ removeActiveVideo(userId : string){ - /*const element = document.getElementById(`div-${userId}`); - if(!element){ - return; - } - element.remove();*/ layoutManager.remove(userId); this.remoteVideo.delete(userId); } From 208b91e52a476869e6dca7b917ae976f6152f4d6 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sat, 6 Jun 2020 17:03:10 +0200 Subject: [PATCH 14/51] Feature screen sharing - Send stream of screen sharing in peer connexion - Add button for share your screen --- front/dist/index.html | 11 +++ front/dist/resources/logos/monitor-close.svg | 44 ++++++++++++ front/dist/resources/logos/monitor.svg | 15 ++++ front/dist/resources/style/style.css | 4 ++ front/src/WebRtc/MediaManager.ts | 73 ++++++++++++++++++++ front/src/WebRtc/SimplePeer.ts | 58 ++++++++-------- 6 files changed, 174 insertions(+), 31 deletions(-) create mode 100644 front/dist/resources/logos/monitor-close.svg create mode 100644 front/dist/resources/logos/monitor.svg diff --git a/front/dist/index.html b/front/dist/index.html index 92a7bf3c..360d5a9a 100644 --- a/front/dist/index.html +++ b/front/dist/index.html @@ -77,6 +77,10 @@
+
+ + +
@@ -100,6 +104,13 @@ +
+ + +
+ --> diff --git a/front/dist/resources/logos/monitor-close.svg b/front/dist/resources/logos/monitor-close.svg new file mode 100644 index 00000000..80056e2d --- /dev/null +++ b/front/dist/resources/logos/monitor-close.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/front/dist/resources/logos/monitor.svg b/front/dist/resources/logos/monitor.svg new file mode 100644 index 00000000..d4b586c6 --- /dev/null +++ b/front/dist/resources/logos/monitor.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 30e099ef..413bce71 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -109,6 +109,10 @@ video#myCamVideo{ transition: all .2s; right: 134px; } +.btn-monitor{ + transition: all .2s; + right: 224px; +} /*.btn-call{ transition: all .1s; left: 0px; diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 39a61738..a11532ac 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -5,6 +5,9 @@ const videoConstraint: boolean|MediaTrackConstraints = { height: { ideal: 720 }, facingMode: "user" }; +interface MediaServiceInterface extends MediaDevices{ + getDisplayMedia(constrain: any) : Promise; +} type UpdatedLocalStreamCallback = (media: MediaStream) => void; @@ -12,10 +15,13 @@ type UpdatedLocalStreamCallback = (media: MediaStream) => void; // TODO: verify that microphone event listeners are not triggered plenty of time NOW (since MediaManager is created many times!!!!) export class MediaManager { localStream: MediaStream|null = null; + localScreenCapture: MediaStream|null = null; private remoteVideo: Map = new Map(); myCamVideo: HTMLVideoElement; cinemaClose: HTMLImageElement; cinema: HTMLImageElement; + monitorClose: HTMLImageElement; + monitor: HTMLImageElement; microphoneClose: HTMLImageElement; microphone: HTMLImageElement; webrtcInAudio: HTMLAudioElement; @@ -57,6 +63,21 @@ export class MediaManager { this.disabledCamera(); //update tracking }); + + this.monitorClose = document.getElementById('monitor-close'); + this.monitorClose.style.display = "block"; + this.monitorClose.addEventListener('click', (e: any) => { + e.preventDefault(); + this.enabledMonitor(); + //update tracking + }); + this.monitor = document.getElementById('monitor'); + this.monitor.style.display = "none"; + this.monitor.addEventListener('click', (e: any) => { + e.preventDefault(); + this.disabledMonitor(); + //update tracking + }); } onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void { @@ -126,6 +147,58 @@ export class MediaManager { }); } + enabledMonitor() { + this.monitorClose.style.display = "none"; + this.monitor.style.display = "block"; + this.getScreenMedia().then((stream) => { + this.updatedLocalStreamCallBack(stream); + }); + } + + disabledMonitor() { + this.monitorClose.style.display = "block"; + this.monitor.style.display = "none"; + this.localScreenCapture?.getTracks().forEach((track: MediaStreamTrack) => { + track.stop(); + }); + this.localScreenCapture = null; + this.getCamera().then((stream) => { + this.updatedLocalStreamCallBack(stream); + }); + } + + //get screen + getScreenMedia() : Promise{ + try { + return this._startScreenCapture() + .then((stream: MediaStream) => { + this.localScreenCapture = stream; + return stream; + }) + .catch((err: any) => { + console.error("Error => getScreenMedia => " + err); + throw err; + }); + }catch (err) { + return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars + reject(err); + }); + } + } + + private _startScreenCapture() { + if ((navigator as any).getDisplayMedia) { + return (navigator as any).getDisplayMedia({video: true}); + } else if ((navigator.mediaDevices as any).getDisplayMedia) { + return (navigator.mediaDevices as any).getDisplayMedia({video: true}); + } else { + //return navigator.mediaDevices.getUserMedia(({video: {mediaSource: 'screen'}} as any)); + return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars + reject("error sharing screen"); + }); + } + } + //get camera async getCamera(): Promise { if (navigator.mediaDevices === undefined) { diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index fdc2d0c2..96f047b7 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -156,22 +156,11 @@ export class SimplePeer { videoActive = true; } }); - if(microphoneActive){ - mediaManager.enabledMicrophoneByUserId(user.userId); - }else{ - mediaManager.disabledMicrophoneByUserId(user.userId); - } - if(videoActive){ - mediaManager.enabledVideoByUserId(user.userId); - }else{ - mediaManager.disabledVideoByUserId(user.userId); - } this.stream(user.userId, stream); }); /*peer.on('track', (track: MediaStreamTrack, stream: MediaStream) => { - this.stream(user.userId, stream); });*/ peer.on('close', () => { @@ -190,9 +179,19 @@ export class SimplePeer { }); peer.on('data', (chunk: Buffer) => { - const data = JSON.parse(chunk.toString('utf8')); - if(data.type === "stream"){ - this.stream(user.userId, data.stream); + let constraint = JSON.parse(chunk.toString('utf8')); + + if (constraint.audio) { + mediaManager.enabledMicrophoneByUserId(user.userId); + } else { + mediaManager.disabledMicrophoneByUserId(user.userId); + } + + if (constraint.video) { + mediaManager.enabledVideoByUserId(user.userId); + } else { + this.stream(user.userId); + mediaManager.disabledVideoByUserId(user.userId); } }); @@ -279,7 +278,7 @@ export class SimplePeer { * @param userId * @param stream */ - private stream(userId : string, stream: MediaStream) { + private stream(userId : string, stream?: MediaStream) { if(!stream){ mediaManager.disabledVideoByUserId(userId); mediaManager.disabledMicrophoneByUserId(userId); @@ -294,24 +293,21 @@ export class SimplePeer { */ private addMedia (userId : string) { try { - const localStream: MediaStream|null = mediaManager.localStream; - const peer = this.PeerConnectionArray.get(userId); - if(localStream === null) { - //send fake signal - if(peer === undefined){ - return; - } - peer.write(new Buffer(JSON.stringify({ - type: "stream", - stream: null - }))); - return; - } + let localStream: MediaStream | null = mediaManager.localStream; + let localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; + let peer = this.PeerConnectionArray.get(userId); if (peer === undefined) { - throw new Error('While adding media, cannot find user with ID '+userId); + throw new Error('While adding media, cannot find user with ID ' + userId); } - for (const track of localStream.getTracks()) { - peer.addTrack(track, localStream); + peer.write(new Buffer(JSON.stringify(mediaManager.constraintsMedia))); + if (localScreenCapture !== null) { + for (const track of localScreenCapture.getTracks()) { + peer.addTrack(track, localScreenCapture); + } + } else if (localStream) { + for (const track of localStream.getTracks()) { + peer.addTrack(track, localStream); + } } }catch (e) { console.error(`addMedia => addMedia => ${userId}`, e); From eed5333d693d57183f0895d81109b1c21fdeba1f Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sat, 6 Jun 2020 19:52:34 +0200 Subject: [PATCH 15/51] Stability simple peer --- front/src/WebRtc/SimplePeer.ts | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 96f047b7..78dc41f9 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -180,14 +180,13 @@ export class SimplePeer { peer.on('data', (chunk: Buffer) => { let constraint = JSON.parse(chunk.toString('utf8')); - if (constraint.audio) { mediaManager.enabledMicrophoneByUserId(user.userId); } else { mediaManager.disabledMicrophoneByUserId(user.userId); } - if (constraint.video) { + if (constraint.video || constraint.screen) { mediaManager.enabledVideoByUserId(user.userId); } else { this.stream(user.userId); @@ -295,18 +294,30 @@ export class SimplePeer { try { let localStream: MediaStream | null = mediaManager.localStream; let localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; - let peer = this.PeerConnectionArray.get(userId); - if (peer === undefined) { + let PeerConnection : any = this.PeerConnectionArray.get(userId); + if (PeerConnection === undefined) { throw new Error('While adding media, cannot find user with ID ' + userId); } - peer.write(new Buffer(JSON.stringify(mediaManager.constraintsMedia))); + PeerConnection.write(new Buffer(JSON.stringify(Object.assign(mediaManager.constraintsMedia, {screen: localScreenCapture !== null})))); + + //remove current stream + try { + if (PeerConnection._pc) { + PeerConnection._pc.getRemoteStreams().forEach((stream: MediaStream) => { + stream.getTracks().forEach((track: MediaStreamTrack) => { + PeerConnection.removeTrack(track, stream); + }); + }); + } + }catch (e) {} + if (localScreenCapture !== null) { for (const track of localScreenCapture.getTracks()) { - peer.addTrack(track, localScreenCapture); + PeerConnection.addTrack(track, localScreenCapture); } } else if (localStream) { for (const track of localStream.getTracks()) { - peer.addTrack(track, localStream); + PeerConnection.addTrack(track, localStream); } } }catch (e) { From 6c1b8122ef739b5be04deaf773331ae932e13264 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sat, 6 Jun 2020 20:13:30 +0200 Subject: [PATCH 16/51] Fix CI --- front/src/WebRtc/SimplePeer.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 78dc41f9..825bc612 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -294,23 +294,13 @@ export class SimplePeer { try { let localStream: MediaStream | null = mediaManager.localStream; let localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; - let PeerConnection : any = this.PeerConnectionArray.get(userId); - if (PeerConnection === undefined) { + let PeerConnection = this.PeerConnectionArray.get(userId); + + if (!PeerConnection || PeerConnection === undefined) { throw new Error('While adding media, cannot find user with ID ' + userId); } PeerConnection.write(new Buffer(JSON.stringify(Object.assign(mediaManager.constraintsMedia, {screen: localScreenCapture !== null})))); - //remove current stream - try { - if (PeerConnection._pc) { - PeerConnection._pc.getRemoteStreams().forEach((stream: MediaStream) => { - stream.getTracks().forEach((track: MediaStreamTrack) => { - PeerConnection.removeTrack(track, stream); - }); - }); - } - }catch (e) {} - if (localScreenCapture !== null) { for (const track of localScreenCapture.getTracks()) { PeerConnection.addTrack(track, localScreenCapture); From 209057e3fc1a5823f9db68e9b7e9e05a2e96fdf4 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 8 Jun 2020 09:20:36 +0200 Subject: [PATCH 17/51] New fictive user screen sharing - Create new fictive user - Add new fictive user in WebRtc group - Add screen sharing video on left side --- front/src/Connection.ts | 4 +- front/src/WebRtc/MediaManager.ts | 29 ++++++++++++-- front/src/WebRtc/SimplePeer.ts | 67 ++++++++++++++++++++++++-------- 3 files changed, 79 insertions(+), 21 deletions(-) diff --git a/front/src/Connection.ts b/front/src/Connection.ts index 04715df6..69121837 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -6,7 +6,7 @@ import {SetPlayerDetailsMessage} from "./Messages/SetPlayerDetailsMessage"; const SocketIo = require('socket.io-client'); import Socket = SocketIOClient.Socket; import {PlayerAnimationNames} from "./Phaser/Player/Animation"; -import {UserSimplePeer} from "./WebRtc/SimplePeer"; +import {UserSimplePeerInterface} from "./WebRtc/SimplePeer"; import {SignalData} from "simple-peer"; @@ -72,7 +72,7 @@ export interface GroupCreatedUpdatedMessageInterface { export interface WebRtcStartMessageInterface { roomId: string, - clients: UserSimplePeer[] + clients: UserSimplePeerInterface[] } export interface WebRtcDisconnectMessageInterface { diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index a11532ac..cfe4843a 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -1,3 +1,4 @@ +import * as SimplePeerNamespace from "simple-peer"; import {DivImportance, layoutManager} from "./LayoutManager"; const videoConstraint: boolean|MediaTrackConstraints = { @@ -30,8 +31,12 @@ export class MediaManager { video: videoConstraint }; updatedLocalStreamCallBacks : Set = new Set(); + // TODO: updatedScreenSharingCallBack should have same signature as updatedLocalStreamCallBacks + updatedScreenSharingCallBack : Function; + + constructor(updatedScreenSharingCallBack : Function) { + this.updatedScreenSharingCallBack = updatedScreenSharingCallBack; - constructor() { this.myCamVideo = this.getElementByIdOrFail('myCamVideo'); this.webrtcInAudio = this.getElementByIdOrFail('audio-webrtc-in'); this.webrtcInAudio.volume = 0.2; @@ -151,7 +156,7 @@ export class MediaManager { this.monitorClose.style.display = "none"; this.monitor.style.display = "block"; this.getScreenMedia().then((stream) => { - this.updatedLocalStreamCallBack(stream); + this.updatedScreenSharingCallBack(stream); }); } @@ -163,7 +168,7 @@ export class MediaManager { }); this.localScreenCapture = null; this.getCamera().then((stream) => { - this.updatedLocalStreamCallBack(stream); + this.updatedScreenSharingCallBack(stream); }); } @@ -278,6 +283,24 @@ export class MediaManager { this.remoteVideo.set(userId, this.getElementByIdOrFail(userId)); } + /** + * + * @param userId + */ + addScreenSharingActiveVideo(userId : string, userName: string = ""){ + this.webrtcInAudio.play(); + // FIXME: switch to DisplayManager! + let elementRemoteVideo = this.getElementByIdOrFail("activeScreenSharing"); + userName = userName.toUpperCase(); + let color = this.getColorByString(userName); + elementRemoteVideo.insertAdjacentHTML('beforeend', ` +
+ +
+ `); + this.remoteVideo.set(userId, document.getElementById(userId)); + } + /** * * @param userId diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 825bc612..6d39444e 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -8,7 +8,7 @@ import { mediaManager } from "./MediaManager"; import * as SimplePeerNamespace from "simple-peer"; const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); -export interface UserSimplePeer{ +export interface UserSimplePeerInterface{ userId: string; name?: string; initiator?: boolean; @@ -26,10 +26,11 @@ export interface PeerConnectionListener { export class SimplePeer { private Connection: Connection; private WebRtcRoomId: string; - private Users: Array = new Array(); + private Users: Array = new Array(); private PeerConnectionArray: Map = new Map(); private readonly updateLocalStreamCallback: (media: MediaStream) => void; + private readonly updateScreenSharingCallback: (media: MediaStream) => void; private readonly peerConnectionListeners: Array = new Array(); constructor(Connection: Connection, WebRtcRoomId: string = "test-webrtc") { @@ -37,7 +38,9 @@ export class SimplePeer { this.WebRtcRoomId = WebRtcRoomId; // We need to go through this weird bound function pointer in order to be able to "free" this reference later. this.updateLocalStreamCallback = this.updatedLocalStream.bind(this); + this.updateScreenSharingCallback = this.updatedScreenSharing.bind(this); mediaManager.onUpdateLocalStream(this.updateLocalStreamCallback); + mediaManager.onUpdateScreenSharing(this.updateScreenSharingCallback); this.initialise(); } @@ -93,7 +96,7 @@ export class SimplePeer { * server has two people connected, start the meet */ private startWebRtc() { - this.Users.forEach((user: UserSimplePeer) => { + this.Users.forEach((user: UserSimplePeerInterface) => { //if it's not an initiator, peer connection will be created when gamer will receive offer signal if(!user.initiator){ return; @@ -105,22 +108,24 @@ export class SimplePeer { /** * create peer connection to bind users */ - private createPeerConnection(user : UserSimplePeer) { + private createPeerConnection(user : UserSimplePeerInterface) : SimplePeerNamespace.Instance | null{ if(this.PeerConnectionArray.has(user.userId)) { - return; + return null; } - //console.log("Creating connection with peer "+user.userId); - let name = user.name; if(!name){ - const userSearch = this.Users.find((userSearch: UserSimplePeer) => userSearch.userId === user.userId); + const userSearch = this.Users.find((userSearch: UserSimplePeerInterface) => userSearch.userId === user.userId); if(userSearch) { name = userSearch.name; } } - mediaManager.removeActiveVideo(user.userId); - mediaManager.addActiveVideo(user.userId, name); + + let screenSharing : boolean = name !== undefined && name.indexOf("screenSharing") > -1; + if(!screenSharing) { + mediaManager.removeActiveVideo(user.userId); + mediaManager.addActiveVideo(user.userId, name); + } const peer : SimplePeerNamespace.Instance = new Peer({ initiator: user.initiator ? user.initiator : false, @@ -146,6 +151,11 @@ export class SimplePeer { }); peer.on('stream', (stream: MediaStream) => { + if(screenSharing){ + //add stream video on + return; + } + let videoActive = false; let microphoneActive = false; stream.getTracks().forEach((track : MediaStreamTrack) => { @@ -199,6 +209,7 @@ export class SimplePeer { for (const peerConnectionListener of this.peerConnectionListeners) { peerConnectionListener.onConnect(user); } + return peer; } /** @@ -301,11 +312,10 @@ export class SimplePeer { } PeerConnection.write(new Buffer(JSON.stringify(Object.assign(mediaManager.constraintsMedia, {screen: localScreenCapture !== null})))); - if (localScreenCapture !== null) { - for (const track of localScreenCapture.getTracks()) { - PeerConnection.addTrack(track, localScreenCapture); - } - } else if (localStream) { + if(!localStream){ + return; + } + if (localStream) { for (const track of localStream.getTracks()) { PeerConnection.addTrack(track, localStream); } @@ -316,8 +326,33 @@ export class SimplePeer { } updatedLocalStream(){ - this.Users.forEach((user: UserSimplePeer) => { + this.Users.forEach((user: UserSimplePeerInterface) => { this.addMedia(user.userId); }) } + + updatedScreenSharing() { + if (this.MediaManager.localScreenCapture) { + let screenSharingUser: UserSimplePeerInterface = { + userId: `screenSharing-${this.Connection.userId}`, + initiator: true + }; + let PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser); + if (!PeerConnectionScreenSharing) { + return; + } + for (const track of this.MediaManager.localScreenCapture.getTracks()) { + PeerConnectionScreenSharing.addTrack(track, this.MediaManager.localScreenCapture); + } + } else { + if (!this.PeerConnectionArray.has(`screenSharing-${this.Connection.userId}`)) { + return; + } + let PeerConnectionScreenSharing = this.PeerConnectionArray.get(`screenSharing-${this.Connection.userId}`); + if (!PeerConnectionScreenSharing) { + return; + } + PeerConnectionScreenSharing.destroy(); + } + } } From 3e2c5049f2e633ce6357da46df629d379e9b3da6 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 8 Jun 2020 09:36:07 +0200 Subject: [PATCH 18/51] Fix to add special screen sharing --- front/src/WebRtc/MediaManager.ts | 6 +++++- front/src/WebRtc/SimplePeer.ts | 20 +++----------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index cfe4843a..4341c52e 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -298,7 +298,11 @@ export class MediaManager { `); - this.remoteVideo.set(userId, document.getElementById(userId)); + let activeHTMLVideoElement : HTMLElement|null = document.getElementById(userId); + if(!activeHTMLVideoElement){ + return; + } + this.remoteVideo.set(userId, (activeHTMLVideoElement as HTMLVideoElement)); } /** diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 6d39444e..f24cc31d 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -122,9 +122,11 @@ export class SimplePeer { } let screenSharing : boolean = name !== undefined && name.indexOf("screenSharing") > -1; + mediaManager.removeActiveVideo(user.userId); if(!screenSharing) { - mediaManager.removeActiveVideo(user.userId); mediaManager.addActiveVideo(user.userId, name); + }else{ + mediaManager.addScreenSharingActiveVideo(user.userId, name); } const peer : SimplePeerNamespace.Instance = new Peer({ @@ -151,22 +153,6 @@ export class SimplePeer { }); peer.on('stream', (stream: MediaStream) => { - if(screenSharing){ - //add stream video on - return; - } - - let videoActive = false; - let microphoneActive = false; - stream.getTracks().forEach((track : MediaStreamTrack) => { - if(track.kind === "audio"){ - microphoneActive = true; - } - if(track.kind === "video"){ - videoActive = true; - } - }); - this.stream(user.userId, stream); }); From 0bbed7717a57f317a1f9efde5173f8bada37034f Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 8 Jun 2020 22:52:25 +0200 Subject: [PATCH 19/51] Continue screen sharing --- front/dist/index.html | 5 ++--- front/dist/resources/style/style.css | 30 ++++++++++++++++++++++++++++ front/src/WebRtc/SimplePeer.ts | 28 +++++++++++++++++++------- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/front/dist/index.html b/front/dist/index.html index 360d5a9a..02ec0205 100644 --- a/front/dist/index.html +++ b/front/dist/index.html @@ -108,12 +108,11 @@ - --> +
+
diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 413bce71..d0dc2cd4 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -365,3 +365,33 @@ body { .chat-mode > div:last-child { flex-grow: 5; } + +/*SCREEN SHARING*/ +.active-screen-sharing video{ + transform: scaleX(1); +} +.active-screen-sharing .screen-sharing-video-container video:hover{ + width: 50%; +} +.active-screen-sharing .screen-sharing-video-container video{ + position: absolute; + width: 25%; + height: auto; + left: 0; + top: 0; + transition: all 0.2s ease; +} + +.active-screen-sharing .screen-sharing-video-container video:nth-child(1){ + /*this is for camera of user*/ + top: 0%; +} +.active-screen-sharing .screen-sharing-video-container video:nth-child(2){ + top: 25%; +} +.active-screen-sharing .screen-sharing-video-container video:nth-child(3){ + top: 50%; +} +.active-screen-sharing .screen-sharing-video-container video:nth-child(4) { + top: 75%; +} diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index f24cc31d..6f5fd69a 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -289,14 +289,22 @@ export class SimplePeer { */ private addMedia (userId : string) { try { - let localStream: MediaStream | null = mediaManager.localStream; - let localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; let PeerConnection = this.PeerConnectionArray.get(userId); - - if (!PeerConnection || PeerConnection === undefined) { + if (!PeerConnection) { throw new Error('While adding media, cannot find user with ID ' + userId); } - PeerConnection.write(new Buffer(JSON.stringify(Object.assign(mediaManager.constraintsMedia, {screen: localScreenCapture !== null})))); + + if(userId.indexOf("screenSharing") > -1 && mediaManager.localScreenCapture){ + for (const track of mediaManager.localScreenCapture.getTracks()) { + PeerConnection.addTrack(track, mediaManager.localScreenCapture); + } + return; + } + + let localStream: MediaStream | null = mediaManager.localStream; + let localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; + + PeerConnection.write(new Buffer(JSON.stringify(Object.assign(this.MediaManager.constraintsMedia, {screen: localScreenCapture !== null})))); if(!localStream){ return; @@ -321,15 +329,21 @@ export class SimplePeer { if (this.MediaManager.localScreenCapture) { let screenSharingUser: UserSimplePeerInterface = { userId: `screenSharing-${this.Connection.userId}`, + name: 'screenSharing', initiator: true }; let PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser); if (!PeerConnectionScreenSharing) { return; } - for (const track of this.MediaManager.localScreenCapture.getTracks()) { - PeerConnectionScreenSharing.addTrack(track, this.MediaManager.localScreenCapture); + try { + for (const track of this.MediaManager.localScreenCapture.getTracks()) { + PeerConnectionScreenSharing.addTrack(track, this.MediaManager.localScreenCapture); + } + }catch (e) { + console.error("updatedScreenSharing => ", e); } + this.MediaManager.addStreamRemoteVideo(screenSharingUser.userId, this.MediaManager.localScreenCapture); } else { if (!this.PeerConnectionArray.has(`screenSharing-${this.Connection.userId}`)) { return; From a4f42111d7543a0bb87bcdc18ca0c4126b62ba48 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Thu, 11 Jun 2020 23:18:06 +0200 Subject: [PATCH 20/51] Update screen sharing feature --- back/src/Controller/IoSocketController.ts | 33 +++-- front/src/Connection.ts | 28 ++++- front/src/WebRtc/MediaManager.ts | 14 ++- front/src/WebRtc/SimplePeer.ts | 140 +++++++++++++++++----- 4 files changed, 171 insertions(+), 44 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 28dd2da2..2f99f1e6 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -28,6 +28,7 @@ enum SockerIoEvent { USER_MOVED = "user-moved", // From server to client USER_LEFT = "user-left", // From server to client WEBRTC_SIGNAL = "webrtc-signal", + WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal", WEBRTC_START = "webrtc-start", WEBRTC_DISCONNECT = "webrtc-disconect", MESSAGE_ERROR = "message-error", @@ -226,18 +227,11 @@ export class IoSocketController { }); socket.on(SockerIoEvent.WEBRTC_SIGNAL, (data: unknown) => { - if (!isWebRtcSignalMessageInterface(data)) { - socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SIGNAL message.'}); - console.warn('Invalid WEBRTC_SIGNAL message received: ', data); - return; - } - //send only at user - const client = this.sockets.get(data.receiverId); - if (client === undefined) { - console.warn("While exchanging a WebRTC signal: client with id ", data.receiverId, " does not exist. This might be a race condition."); - return; - } - return client.emit(SockerIoEvent.WEBRTC_SIGNAL, data); + this.emit((socket as ExSocketInterface), data, SockerIoEvent.WEBRTC_SIGNAL); + }); + + socket.on(SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, (data: unknown) => { + this.emit((socket as ExSocketInterface), data, SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL); }); socket.on(SockerIoEvent.DISCONNECT, () => { @@ -284,6 +278,21 @@ export class IoSocketController { }); } + emit(socket: ExSocketInterface, data: unknown, event: SockerIoEvent){ + if (!isWebRtcSignalMessageInterface(data)) { + socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SIGNAL message.'}); + console.warn('Invalid WEBRTC_SIGNAL message received: ', data); + return; + } + //send only at user + const client = this.sockets.get(data.receiverId); + if (client === undefined) { + console.warn("While exchanging a WebRTC signal: client with id ", data.receiverId, " does not exist. This might be a race condition."); + return; + } + return client.emit(event, data); + } + searchClientByIdOrFail(userId: string): ExSocketInterface { const client: ExSocketInterface|undefined = this.sockets.get(userId); if (client === undefined) { diff --git a/front/src/Connection.ts b/front/src/Connection.ts index 69121837..bceef68a 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -9,9 +9,9 @@ import {PlayerAnimationNames} from "./Phaser/Player/Animation"; import {UserSimplePeerInterface} from "./WebRtc/SimplePeer"; import {SignalData} from "simple-peer"; - enum EventMessage{ WEBRTC_SIGNAL = "webrtc-signal", + WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal", WEBRTC_START = "webrtc-start", JOIN_ROOM = "join-room", // bi-directional USER_POSITION = "user-position", // bi-directional @@ -197,6 +197,15 @@ export class Connection implements Connection { }); } + sendWebrtcScreenSharingSignal(signal: any, roomId: string, userId? : string|null, receiverId? : string) { + return this.getSocket().emit(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, { + userId: userId ? userId : this.userId, + receiverId: receiverId ? receiverId : this.userId, + roomId: roomId, + signal: signal + }); + } + public receiveWebrtcStart(callback: (message: WebRtcStartMessageInterface) => void) { this.socket.on(EventMessage.WEBRTC_START, callback); } @@ -205,6 +214,23 @@ export class Connection implements Connection { return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback); } + receiveWebrtcScreenSharingSignal(callback: Function) { + return this.getSocket().on(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, callback); + } + + private errorMessage(): void { + this.getSocket().on(EventMessage.MESSAGE_ERROR, (message: string) => { + console.error(EventMessage.MESSAGE_ERROR, message); + }) + } + + private disconnectServer(): void { + this.getSocket().on(EventMessage.CONNECT_ERROR, () => { + this.GameManager.switchToDisconnectedScene(); + }); + + } + public onServerDisconnected(callback: (reason: string) => void): void { this.socket.on('disconnect', (reason: string) => { if (reason === 'io client disconnect') { diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 4341c52e..706b9f49 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -287,14 +287,13 @@ export class MediaManager { * * @param userId */ - addScreenSharingActiveVideo(userId : string, userName: string = ""){ + addScreenSharingActiveVideo(userId : string){ + userId = `screen-sharing-${userId}`; this.webrtcInAudio.play(); // FIXME: switch to DisplayManager! let elementRemoteVideo = this.getElementByIdOrFail("activeScreenSharing"); - userName = userName.toUpperCase(); - let color = this.getColorByString(userName); elementRemoteVideo.insertAdjacentHTML('beforeend', ` -
+
`); @@ -302,6 +301,7 @@ export class MediaManager { if(!activeHTMLVideoElement){ return; } + console.log(userId, (activeHTMLVideoElement as HTMLVideoElement)); this.remoteVideo.set(userId, (activeHTMLVideoElement as HTMLVideoElement)); } @@ -372,6 +372,9 @@ export class MediaManager { } remoteVideo.srcObject = stream; } + addStreamRemoteScreenSharing(userId : string, stream : MediaStream){ + this.addStreamRemoteVideo(`screen-sharing-${userId}`, stream); + } /** * @@ -381,6 +384,9 @@ export class MediaManager { layoutManager.remove(userId); this.remoteVideo.delete(userId); } + removeActiveScreenSharingVideo(userId : string) { + this.removeActiveVideo(`screen-sharing-${userId}`) + } isConnecting(userId : string): void { const connectingSpinnerDiv = this.getSpinner(userId); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 6f5fd69a..81bffd6d 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -28,6 +28,7 @@ export class SimplePeer { private WebRtcRoomId: string; private Users: Array = new Array(); + private PeerScreenSharingConnectionArray: Map = new Map(); private PeerConnectionArray: Map = new Map(); private readonly updateLocalStreamCallback: (media: MediaStream) => void; private readonly updateScreenSharingCallback: (media: MediaStream) => void; @@ -62,6 +63,11 @@ export class SimplePeer { this.receiveWebrtcSignal(message); }); + //receive signal by gemer + this.Connection.receiveWebrtcScreenSharingSignal((message: any) => { + this.receiveWebrtcScreenSharingSignal(message); + }); + mediaManager.activeVisio(); mediaManager.getCamera().then(() => { @@ -108,7 +114,8 @@ export class SimplePeer { /** * create peer connection to bind users */ - private createPeerConnection(user : UserSimplePeerInterface) : SimplePeerNamespace.Instance | null{ + private createPeerConnection(user : UserSimplePeerInterface, screenSharing: boolean = false) : SimplePeerNamespace.Instance | null{ + console.log("createPeerConnection => screenSharing", screenSharing) if(this.PeerConnectionArray.has(user.userId)) { return null; } @@ -121,12 +128,11 @@ export class SimplePeer { } } - let screenSharing : boolean = name !== undefined && name.indexOf("screenSharing") > -1; mediaManager.removeActiveVideo(user.userId); - if(!screenSharing) { - mediaManager.addActiveVideo(user.userId, name); + if(screenSharing) { + mediaManager.addScreenSharingActiveVideo(user.userId); }else{ - mediaManager.addScreenSharingActiveVideo(user.userId, name); + mediaManager.addActiveVideo(user.userId, name); } const peer : SimplePeerNamespace.Instance = new Peer({ @@ -145,10 +151,19 @@ export class SimplePeer { ] }, }); - this.PeerConnectionArray.set(user.userId, peer); + if(screenSharing){ + this.PeerScreenSharingConnectionArray.set(user.userId, peer); + }else { + this.PeerConnectionArray.set(user.userId, peer); + } //start listen signal for the peer connection peer.on('signal', (data: unknown) => { + console.log("screenSharing", screenSharing); + if(screenSharing){ + this.sendWebrtcScreenSharingSignal(data, user.userId); + return; + } this.sendWebrtcSignal(data, user.userId); }); @@ -160,6 +175,9 @@ export class SimplePeer { });*/ peer.on('close', () => { + if(screenSharing){ + this.closeScreenSharingConnection(user.userId); + } this.closeConnection(user.userId); }); @@ -190,7 +208,11 @@ export class SimplePeer { } }); - this.addMedia(user.userId); + if(screenSharing){ + this.addMediaScreenSharing(user.userId); + }else { + this.addMedia(user.userId); + } for (const peerConnectionListener of this.peerConnectionListeners) { peerConnectionListener.onConnect(user); @@ -225,6 +247,30 @@ export class SimplePeer { } } + /** + * This is triggered twice. Once by the server, and once by a remote client disconnecting + * + * @param userId + */ + private closeScreenSharingConnection(userId : string) { + try { + mediaManager.removeActiveScreenSharingVideo(userId); + let peer = this.PeerScreenSharingConnectionArray.get(userId); + if (peer === undefined) { + console.warn("Tried to close connection for user "+userId+" but could not find user") + return; + } + // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" + // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. + //console.log('Closing connection with '+userId); + peer.destroy(); + this.PeerScreenSharingConnectionArray.delete(userId) + //console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size); + } catch (err) { + console.error("closeConnection", err) + } + } + public closeAllConnections() { for (const userId of this.PeerConnectionArray.keys()) { this.closeConnection(userId); @@ -244,6 +290,7 @@ export class SimplePeer { * @param data */ private sendWebrtcSignal(data: unknown, userId : string) { + console.log("sendWebrtcSignal", data); try { this.Connection.sendWebrtcSignal(data, this.WebRtcRoomId, null, userId); }catch (e) { @@ -251,6 +298,20 @@ export class SimplePeer { } } + /** + * + * @param userId + * @param data + */ + private sendWebrtcScreenSharingSignal(data: any, userId : string) { + console.log("sendWebrtcScreenSharingSignal", data); + try { + this.Connection.sendWebrtcScreenSharingSignal(data, this.WebRtcRoomId, null, userId); + }catch (e) { + console.error(`sendWebrtcSignal => ${userId}`, e); + } + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any private receiveWebrtcSignal(data: WebRtcSignalMessageInterface) { try { @@ -269,6 +330,24 @@ export class SimplePeer { } } + private receiveWebrtcScreenSharingSignal(data: any) { + console.log("receiveWebrtcScreenSharingSignal", data); + try { + //if offer type, create peer connection + if(data.signal.type === "offer"){ + this.createPeerConnection(data, true); + } + let peer = this.PeerConnectionArray.get(data.userId); + if (peer !== undefined) { + peer.signal(data.signal); + } else { + console.error('Could not find peer whose ID is "'+data.userId+'" in PeerConnectionArray'); + } + } catch (e) { + console.error(`receiveWebrtcSignal => ${data.userId}`, e); + } + } + /** * * @param userId @@ -293,18 +372,8 @@ export class SimplePeer { if (!PeerConnection) { throw new Error('While adding media, cannot find user with ID ' + userId); } - - if(userId.indexOf("screenSharing") > -1 && mediaManager.localScreenCapture){ - for (const track of mediaManager.localScreenCapture.getTracks()) { - PeerConnection.addTrack(track, mediaManager.localScreenCapture); - } - return; - } - let localStream: MediaStream | null = mediaManager.localStream; - let localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; - - PeerConnection.write(new Buffer(JSON.stringify(Object.assign(this.MediaManager.constraintsMedia, {screen: localScreenCapture !== null})))); + PeerConnection.write(new Buffer(JSON.stringify(mediaManager.constraintsMedia))); if(!localStream){ return; @@ -319,6 +388,21 @@ export class SimplePeer { } } + private addMediaScreenSharing (userId : any = null) { + let PeerConnection = this.PeerScreenSharingConnectionArray.get(userId); + if (!PeerConnection) { + throw new Error('While adding media, cannot find user with ID ' + userId); + } + let localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; + if(!localScreenCapture){ + return; + } + /*for (const track of localScreenCapture.getTracks()) { + PeerConnection.addTrack(track, localScreenCapture); + }*/ + return; + } + updatedLocalStream(){ this.Users.forEach((user: UserSimplePeerInterface) => { this.addMedia(user.userId); @@ -326,29 +410,31 @@ export class SimplePeer { } updatedScreenSharing() { - if (this.MediaManager.localScreenCapture) { + if (mediaManager.localScreenCapture) { + if(!this.Connection.userId){ + return; + } let screenSharingUser: UserSimplePeerInterface = { - userId: `screenSharing-${this.Connection.userId}`, - name: 'screenSharing', + userId: this.Connection.userId, initiator: true }; - let PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser); + let PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser, true); if (!PeerConnectionScreenSharing) { return; } try { - for (const track of this.MediaManager.localScreenCapture.getTracks()) { - PeerConnectionScreenSharing.addTrack(track, this.MediaManager.localScreenCapture); + for (const track of mediaManager.localScreenCapture.getTracks()) { + PeerConnectionScreenSharing.addTrack(track, mediaManager.localScreenCapture); } }catch (e) { console.error("updatedScreenSharing => ", e); } - this.MediaManager.addStreamRemoteVideo(screenSharingUser.userId, this.MediaManager.localScreenCapture); + mediaManager.addStreamRemoteScreenSharing(screenSharingUser.userId, mediaManager.localScreenCapture); } else { - if (!this.PeerConnectionArray.has(`screenSharing-${this.Connection.userId}`)) { + if (!this.Connection.userId || !this.PeerScreenSharingConnectionArray.has(this.Connection.userId)) { return; } - let PeerConnectionScreenSharing = this.PeerConnectionArray.get(`screenSharing-${this.Connection.userId}`); + let PeerConnectionScreenSharing = this.PeerScreenSharingConnectionArray.get(this.Connection.userId); if (!PeerConnectionScreenSharing) { return; } From a8f27e60844fd80257434a0725fb0c3a1e0da2bf Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sun, 14 Jun 2020 14:47:16 +0200 Subject: [PATCH 21/51] Create event to start webrtc screen charing --- back/src/Controller/IoSocketController.ts | 25 ++++- .../Model/Websocket/WebRtcSignalMessage.ts | 11 ++ front/src/Connection.ts | 17 ++- front/src/WebRtc/MediaManager.ts | 5 + front/src/WebRtc/SimplePeer.ts | 103 ++++++++++++------ 5 files changed, 123 insertions(+), 38 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 2f99f1e6..81a7b16b 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -17,7 +17,7 @@ import os from 'os'; import {TokenInterface} from "../Controller/AuthenticateController"; import {isJoinRoomMessageInterface} from "../Model/Websocket/JoinRoomMessage"; import {isPointInterface, PointInterface} from "../Model/Websocket/PointInterface"; -import {isWebRtcSignalMessageInterface} from "../Model/Websocket/WebRtcSignalMessage"; +import {isWebRtcSignalMessageInterface, isWebRtcScreenSharingSignalMessageInterface, isWebRtcScreenSharingStartMessageInterface} from "../Model/Websocket/WebRtcSignalMessage"; import {UserInGroupInterface} from "../Model/Websocket/UserInGroupInterface"; enum SockerIoEvent { @@ -30,6 +30,7 @@ enum SockerIoEvent { WEBRTC_SIGNAL = "webrtc-signal", WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal", WEBRTC_START = "webrtc-start", + WEBRTC_SCREEN_SHARING_START = "webrtc-screen-sharing-start", WEBRTC_DISCONNECT = "webrtc-disconect", MESSAGE_ERROR = "message-error", GROUP_CREATE_UPDATE = "group-create-update", @@ -231,7 +232,17 @@ export class IoSocketController { }); socket.on(SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, (data: unknown) => { - this.emit((socket as ExSocketInterface), data, SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL); + this.emitScreenSharing((socket as ExSocketInterface), data, SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL); + }); + + socket.on(SockerIoEvent.WEBRTC_SCREEN_SHARING_START, (data: unknown) => { + console.log(SockerIoEvent.WEBRTC_SCREEN_SHARING_START, data); + if (!isWebRtcScreenSharingStartMessageInterface(data)) { + socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SIGNAL message.'}); + console.warn('Invalid WEBRTC_SIGNAL message received: ', data); + return; + } + this.Io.in(data.roomId).emit(SockerIoEvent.WEBRTC_SCREEN_SHARING_START, data); }); socket.on(SockerIoEvent.DISCONNECT, () => { @@ -293,6 +304,16 @@ export class IoSocketController { return client.emit(event, data); } + emitScreenSharing(socket: ExSocketInterface, data: unknown, event: SockerIoEvent){ + if (!isWebRtcScreenSharingSignalMessageInterface(data)) { + socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SIGNAL message.'}); + console.warn('Invalid WEBRTC_SIGNAL message received: ', data); + return; + } + //share at all others clients send only at user + return socket.broadcast.emit(event, data); + } + searchClientByIdOrFail(userId: string): ExSocketInterface { const client: ExSocketInterface|undefined = this.sockets.get(userId); if (client === undefined) { diff --git a/back/src/Model/Websocket/WebRtcSignalMessage.ts b/back/src/Model/Websocket/WebRtcSignalMessage.ts index 7edffdfa..8236d338 100644 --- a/back/src/Model/Websocket/WebRtcSignalMessage.ts +++ b/back/src/Model/Websocket/WebRtcSignalMessage.ts @@ -7,4 +7,15 @@ export const isWebRtcSignalMessageInterface = roomId: tg.isString, signal: tg.isUnknown }).get(); +export const isWebRtcScreenSharingSignalMessageInterface = + new tg.IsInterface().withProperties({ + userId: tg.isString, + roomId: tg.isString, + signal: tg.isUnknown + }).get(); +export const isWebRtcScreenSharingStartMessageInterface = + new tg.IsInterface().withProperties({ + userId: tg.isString, + roomId: tg.isString + }).get(); export type WebRtcSignalMessageInterface = tg.GuardedType; diff --git a/front/src/Connection.ts b/front/src/Connection.ts index bceef68a..50750c59 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -13,6 +13,7 @@ enum EventMessage{ WEBRTC_SIGNAL = "webrtc-signal", WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal", WEBRTC_START = "webrtc-start", + WEBRTC_SCREEN_SHARING_START = "webrtc-screen-sharing-start", JOIN_ROOM = "join-room", // bi-directional USER_POSITION = "user-position", // bi-directional USER_MOVED = "user-moved", // From server to client @@ -197,10 +198,16 @@ export class Connection implements Connection { }); } - sendWebrtcScreenSharingSignal(signal: any, roomId: string, userId? : string|null, receiverId? : string) { + sendWebrtcScreenSharingStart(roomId: string) { + return this.getSocket().emit(EventMessage.WEBRTC_SCREEN_SHARING_START, { + userId: this.userId, + roomId: roomId + }); + } + + sendWebrtcScreenSharingSignal(signal: any, roomId: string, userId? : string|null) { return this.getSocket().emit(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, { - userId: userId ? userId : this.userId, - receiverId: receiverId ? receiverId : this.userId, + userId: userId, roomId: roomId, signal: signal }); @@ -210,6 +217,10 @@ export class Connection implements Connection { this.socket.on(EventMessage.WEBRTC_START, callback); } + public receiveWebrtcScreenSharingStart(callback: (message: WebRtcDisconnectMessageInterface) => void) { + this.socket.on(EventMessage.WEBRTC_SCREEN_SHARING_START, callback); + } + public receiveWebrtcSignal(callback: (message: WebRtcSignalMessageInterface) => void) { return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback); } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 706b9f49..167faded 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -405,6 +405,7 @@ export class MediaManager { } isError(userId : string): void { + console.log("isError", `div-${userId}`); const element = document.getElementById(`div-${userId}`); if(!element){ return; @@ -415,6 +416,10 @@ export class MediaManager { } errorDiv.style.display = 'block'; } + isErrorScreenSharing(userId : string): void { + this.isError(`screen-sharing-${userId}`); + } + private getSpinner(userId : string): HTMLDivElement|null { const element = document.getElementById(`div-${userId}`); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 81bffd6d..0377ea1a 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -63,8 +63,16 @@ export class SimplePeer { this.receiveWebrtcSignal(message); }); + this.Connection.receiveWebrtcScreenSharingStart((message: WebRtcDisconnectMessageInterface) => { + console.log("receiveWebrtcScreenSharingStart => initiator", message.userId === this.Connection.userId); + if(message.userId === this.Connection.userId) { + console.log("receiveWebrtcScreenSharingStart => initiator => create peer connexion"); + this.receiveWebrtcScreenSharingStart(message); + } + }); + //receive signal by gemer - this.Connection.receiveWebrtcScreenSharingSignal((message: any) => { + this.Connection.receiveWebrtcScreenSharingSignal((message: WebRtcDisconnectMessageInterface) => { this.receiveWebrtcScreenSharingSignal(message); }); @@ -98,6 +106,31 @@ export class SimplePeer { this.startWebRtc(); } + private receiveWebrtcScreenSharingStart(data: WebRtcDisconnectMessageInterface) { + console.log("receiveWebrtcScreenSharingStart", data); + let screenSharingUser: UserSimplePeerInterface = { + userId: data.userId, + initiator: this.Connection.userId === data.userId + }; + let PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser, true); + if (!PeerConnectionScreenSharing) { + console.error("receiveWebrtcScreenSharingStart => cannot create peer connexion", PeerConnectionScreenSharing); + return; + } + console.log(`receiveWebrtcScreenSharingStart => ${screenSharingUser.initiator}`, mediaManager.localScreenCapture) + if (!mediaManager.localScreenCapture) { + return; + } + try { + for (const track of mediaManager.localScreenCapture.getTracks()) { + PeerConnectionScreenSharing.addTrack(track, mediaManager.localScreenCapture); + } + } catch (e) { + console.error("updatedScreenSharing => ", e); + } + mediaManager.addStreamRemoteScreenSharing(screenSharingUser.userId, mediaManager.localScreenCapture); + } + /** * server has two people connected, start the meet */ @@ -115,8 +148,10 @@ export class SimplePeer { * create peer connection to bind users */ private createPeerConnection(user : UserSimplePeerInterface, screenSharing: boolean = false) : SimplePeerNamespace.Instance | null{ - console.log("createPeerConnection => screenSharing", screenSharing) - if(this.PeerConnectionArray.has(user.userId)) { + if( + (screenSharing && this.PeerScreenSharingConnectionArray.has(user.userId)) + || (!screenSharing && this.PeerConnectionArray.has(user.userId)) + ){ return null; } @@ -128,14 +163,15 @@ export class SimplePeer { } } - mediaManager.removeActiveVideo(user.userId); if(screenSharing) { + mediaManager.removeActiveScreenSharingVideo(user.userId); mediaManager.addScreenSharingActiveVideo(user.userId); }else{ + mediaManager.removeActiveVideo(user.userId); mediaManager.addActiveVideo(user.userId, name); } - const peer : SimplePeerNamespace.Instance = new Peer({ + const peerOption : SimplePeerNamespace.Instance = new Peer({ initiator: user.initiator ? user.initiator : false, reconnectTimer: 10000, config: { @@ -149,8 +185,10 @@ export class SimplePeer { credential: 'itcugcOHxle9Acqi$' }, ] - }, - }); + } + }; + console.log("peerOption", peerOption); + let peer : SimplePeerNamespace.Instance = new Peer(peerOption); if(screenSharing){ this.PeerScreenSharingConnectionArray.set(user.userId, peer); }else { @@ -159,7 +197,6 @@ export class SimplePeer { //start listen signal for the peer connection peer.on('signal', (data: unknown) => { - console.log("screenSharing", screenSharing); if(screenSharing){ this.sendWebrtcScreenSharingSignal(data, user.userId); return; @@ -168,7 +205,7 @@ export class SimplePeer { }); peer.on('stream', (stream: MediaStream) => { - this.stream(user.userId, stream); + this.stream(user.userId, stream, screenSharing); }); /*peer.on('track', (track: MediaStreamTrack, stream: MediaStream) => { @@ -177,6 +214,7 @@ export class SimplePeer { peer.on('close', () => { if(screenSharing){ this.closeScreenSharingConnection(user.userId); + return; } this.closeConnection(user.userId); }); @@ -184,6 +222,10 @@ export class SimplePeer { // eslint-disable-next-line @typescript-eslint/no-explicit-any peer.on('error', (err: any) => { console.error(`error => ${user.userId} => ${err.code}`, err); + if(screenSharing){ + //mediaManager.isErrorScreenSharing(user.userId); + return; + } mediaManager.isError(user.userId); }); @@ -194,6 +236,7 @@ export class SimplePeer { peer.on('data', (chunk: Buffer) => { let constraint = JSON.parse(chunk.toString('utf8')); + console.log("data", constraint); if (constraint.audio) { mediaManager.enabledMicrophoneByUserId(user.userId); } else { @@ -237,7 +280,8 @@ export class SimplePeer { // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. //console.log('Closing connection with '+userId); peer.destroy(); - this.PeerConnectionArray.delete(userId) + this.PeerConnectionArray.delete(userId); + this.closeScreenSharingConnection(userId); //console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size); for (const peerConnectionListener of this.peerConnectionListeners) { peerConnectionListener.onDisconnect(userId); @@ -306,7 +350,7 @@ export class SimplePeer { private sendWebrtcScreenSharingSignal(data: any, userId : string) { console.log("sendWebrtcScreenSharingSignal", data); try { - this.Connection.sendWebrtcScreenSharingSignal(data, this.WebRtcRoomId, null, userId); + this.Connection.sendWebrtcScreenSharingSignal(data, this.WebRtcRoomId, userId); }catch (e) { console.error(`sendWebrtcSignal => ${userId}`, e); } @@ -337,11 +381,11 @@ export class SimplePeer { if(data.signal.type === "offer"){ this.createPeerConnection(data, true); } - let peer = this.PeerConnectionArray.get(data.userId); + let peer = this.PeerScreenSharingConnectionArray.get(data.userId); if (peer !== undefined) { peer.signal(data.signal); } else { - console.error('Could not find peer whose ID is "'+data.userId+'" in PeerConnectionArray'); + console.error('Could not find peer whose ID is "'+data.userId+'" in receiveWebrtcScreenSharingSignal'); } } catch (e) { console.error(`receiveWebrtcSignal => ${data.userId}`, e); @@ -353,7 +397,16 @@ export class SimplePeer { * @param userId * @param stream */ - private stream(userId : string, stream?: MediaStream) { + private stream(userId : string, stream?: MediaStream, screenSharing?: boolean) { + console.log(`stream => ${userId} => screenSharing => ${screenSharing}`, stream); + if(screenSharing){ + if(!stream){ + mediaManager.removeActiveScreenSharingVideo(userId); + return; + } + mediaManager.addStreamRemoteScreenSharing(userId, stream); + return; + } if(!stream){ mediaManager.disabledVideoByUserId(userId); mediaManager.disabledMicrophoneByUserId(userId); @@ -411,34 +464,18 @@ export class SimplePeer { updatedScreenSharing() { if (mediaManager.localScreenCapture) { - if(!this.Connection.userId){ - return; - } - let screenSharingUser: UserSimplePeerInterface = { - userId: this.Connection.userId, - initiator: true - }; - let PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser, true); - if (!PeerConnectionScreenSharing) { - return; - } - try { - for (const track of mediaManager.localScreenCapture.getTracks()) { - PeerConnectionScreenSharing.addTrack(track, mediaManager.localScreenCapture); - } - }catch (e) { - console.error("updatedScreenSharing => ", e); - } - mediaManager.addStreamRemoteScreenSharing(screenSharingUser.userId, mediaManager.localScreenCapture); + this.Connection.sendWebrtcScreenSharingStart(this.WebRtcRoomId); } else { if (!this.Connection.userId || !this.PeerScreenSharingConnectionArray.has(this.Connection.userId)) { return; } let PeerConnectionScreenSharing = this.PeerScreenSharingConnectionArray.get(this.Connection.userId); + console.log("updatedScreenSharing => destroy", PeerConnectionScreenSharing); if (!PeerConnectionScreenSharing) { return; } PeerConnectionScreenSharing.destroy(); + this.PeerScreenSharingConnectionArray.delete(this.Connection.userId); } } } From 4b729581938dbf417cebe6c1589acdcbc9c083f6 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sun, 14 Jun 2020 20:53:18 +0200 Subject: [PATCH 22/51] Fix peer connexion for two player with screen sharing --- back/src/Controller/IoSocketController.ts | 23 +++++----- front/dist/resources/style/style.css | 18 +++++--- front/src/Connection.ts | 12 ----- front/src/WebRtc/SimplePeer.ts | 56 +++++++++-------------- 4 files changed, 45 insertions(+), 64 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 81a7b16b..0c1956f3 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -30,7 +30,6 @@ enum SockerIoEvent { WEBRTC_SIGNAL = "webrtc-signal", WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal", WEBRTC_START = "webrtc-start", - WEBRTC_SCREEN_SHARING_START = "webrtc-screen-sharing-start", WEBRTC_DISCONNECT = "webrtc-disconect", MESSAGE_ERROR = "message-error", GROUP_CREATE_UPDATE = "group-create-update", @@ -45,6 +44,8 @@ export class IoSocketController { private nbClientsGauge: Gauge; private nbClientsPerRoomGauge: Gauge; + private offerScreenSharingByClient: Map> = new Map>(); + constructor(server: http.Server) { this.Io = socketIO(server); this.nbClientsGauge = new Gauge({ @@ -235,16 +236,6 @@ export class IoSocketController { this.emitScreenSharing((socket as ExSocketInterface), data, SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL); }); - socket.on(SockerIoEvent.WEBRTC_SCREEN_SHARING_START, (data: unknown) => { - console.log(SockerIoEvent.WEBRTC_SCREEN_SHARING_START, data); - if (!isWebRtcScreenSharingStartMessageInterface(data)) { - socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SIGNAL message.'}); - console.warn('Invalid WEBRTC_SIGNAL message received: ', data); - return; - } - this.Io.in(data.roomId).emit(SockerIoEvent.WEBRTC_SCREEN_SHARING_START, data); - }); - socket.on(SockerIoEvent.DISCONNECT, () => { const Client = (socket as ExSocketInterface); try { @@ -310,8 +301,16 @@ export class IoSocketController { console.warn('Invalid WEBRTC_SIGNAL message received: ', data); return; } + if(data && data.signal && (data.signal as any).type === "offer"){ + let roomOffer = this.offerScreenSharingByClient.get(data.roomId); + if(!roomOffer){ + roomOffer = new Map(); + } + roomOffer.set(data.userId, data.signal); + this.offerScreenSharingByClient.set(data.roomId, roomOffer); + } //share at all others clients send only at user - return socket.broadcast.emit(event, data); + return socket.in(data.roomId).emit(event, data); } searchClientByIdOrFail(userId: string): ExSocketInterface { diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index d0dc2cd4..ac1b1527 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -370,28 +370,34 @@ body { .active-screen-sharing video{ transform: scaleX(1); } +.screen-sharing-video-container { + width: 25%; + position: absolute; +} .active-screen-sharing .screen-sharing-video-container video:hover{ - width: 50%; + width: 200%; + z-index: 11; } .active-screen-sharing .screen-sharing-video-container video{ position: absolute; - width: 25%; + width: 100%; height: auto; left: 0; top: 0; transition: all 0.2s ease; + z-index: 1; } -.active-screen-sharing .screen-sharing-video-container video:nth-child(1){ +.active-screen-sharing .screen-sharing-video-container:nth-child(1){ /*this is for camera of user*/ top: 0%; } -.active-screen-sharing .screen-sharing-video-container video:nth-child(2){ +.active-screen-sharing .screen-sharing-video-container:nth-child(2){ top: 25%; } -.active-screen-sharing .screen-sharing-video-container video:nth-child(3){ +.active-screen-sharing .screen-sharing-video-container:nth-child(3){ top: 50%; } -.active-screen-sharing .screen-sharing-video-container video:nth-child(4) { +.active-screen-sharing .screen-sharing-video-container:nth-child(4) { top: 75%; } diff --git a/front/src/Connection.ts b/front/src/Connection.ts index 50750c59..2ac81f30 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -13,7 +13,6 @@ enum EventMessage{ WEBRTC_SIGNAL = "webrtc-signal", WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal", WEBRTC_START = "webrtc-start", - WEBRTC_SCREEN_SHARING_START = "webrtc-screen-sharing-start", JOIN_ROOM = "join-room", // bi-directional USER_POSITION = "user-position", // bi-directional USER_MOVED = "user-moved", // From server to client @@ -198,13 +197,6 @@ export class Connection implements Connection { }); } - sendWebrtcScreenSharingStart(roomId: string) { - return this.getSocket().emit(EventMessage.WEBRTC_SCREEN_SHARING_START, { - userId: this.userId, - roomId: roomId - }); - } - sendWebrtcScreenSharingSignal(signal: any, roomId: string, userId? : string|null) { return this.getSocket().emit(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, { userId: userId, @@ -217,10 +209,6 @@ export class Connection implements Connection { this.socket.on(EventMessage.WEBRTC_START, callback); } - public receiveWebrtcScreenSharingStart(callback: (message: WebRtcDisconnectMessageInterface) => void) { - this.socket.on(EventMessage.WEBRTC_SCREEN_SHARING_START, callback); - } - public receiveWebrtcSignal(callback: (message: WebRtcSignalMessageInterface) => void) { return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback); } diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 0377ea1a..e0dbe4a6 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -63,14 +63,6 @@ export class SimplePeer { this.receiveWebrtcSignal(message); }); - this.Connection.receiveWebrtcScreenSharingStart((message: WebRtcDisconnectMessageInterface) => { - console.log("receiveWebrtcScreenSharingStart => initiator", message.userId === this.Connection.userId); - if(message.userId === this.Connection.userId) { - console.log("receiveWebrtcScreenSharingStart => initiator => create peer connexion"); - this.receiveWebrtcScreenSharingStart(message); - } - }); - //receive signal by gemer this.Connection.receiveWebrtcScreenSharingSignal((message: WebRtcDisconnectMessageInterface) => { this.receiveWebrtcScreenSharingSignal(message); @@ -106,31 +98,6 @@ export class SimplePeer { this.startWebRtc(); } - private receiveWebrtcScreenSharingStart(data: WebRtcDisconnectMessageInterface) { - console.log("receiveWebrtcScreenSharingStart", data); - let screenSharingUser: UserSimplePeerInterface = { - userId: data.userId, - initiator: this.Connection.userId === data.userId - }; - let PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser, true); - if (!PeerConnectionScreenSharing) { - console.error("receiveWebrtcScreenSharingStart => cannot create peer connexion", PeerConnectionScreenSharing); - return; - } - console.log(`receiveWebrtcScreenSharingStart => ${screenSharingUser.initiator}`, mediaManager.localScreenCapture) - if (!mediaManager.localScreenCapture) { - return; - } - try { - for (const track of mediaManager.localScreenCapture.getTracks()) { - PeerConnectionScreenSharing.addTrack(track, mediaManager.localScreenCapture); - } - } catch (e) { - console.error("updatedScreenSharing => ", e); - } - mediaManager.addStreamRemoteScreenSharing(screenSharingUser.userId, mediaManager.localScreenCapture); - } - /** * server has two people connected, start the meet */ @@ -464,7 +431,28 @@ export class SimplePeer { updatedScreenSharing() { if (mediaManager.localScreenCapture) { - this.Connection.sendWebrtcScreenSharingStart(this.WebRtcRoomId); + + //this.Connection.sendWebrtcScreenSharingStart(this.WebRtcRoomId); + + if(!this.Connection.userId){ + return; + } + let screenSharingUser: UserSimplePeerInterface = { + userId: this.Connection.userId, + initiator: true + }; + let PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser, true); + if (!PeerConnectionScreenSharing) { + return; + } + try { + for (const track of mediaManager.localScreenCapture.getTracks()) { + PeerConnectionScreenSharing.addTrack(track, mediaManager.localScreenCapture); + } + }catch (e) { + console.error("updatedScreenSharing => ", e); + } + mediaManager.addStreamRemoteScreenSharing(screenSharingUser.userId, mediaManager.localScreenCapture); } else { if (!this.Connection.userId || !this.PeerScreenSharingConnectionArray.has(this.Connection.userId)) { return; From 2e61c2ef6296bd526e397f29587059ee87901ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 18 Aug 2020 00:12:38 +0200 Subject: [PATCH 23/51] Getting back code in compilable fashion after huge rebase --- front/src/Connection.ts | 21 ++++----------------- front/src/Phaser/Game/GameScene.ts | 4 ++-- front/src/WebRtc/MediaManager.ts | 27 +++++++++++++++++++-------- front/src/WebRtc/SimplePeer.ts | 20 ++++++++++---------- 4 files changed, 35 insertions(+), 37 deletions(-) diff --git a/front/src/Connection.ts b/front/src/Connection.ts index 2ac81f30..f2c72d64 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -197,8 +197,8 @@ export class Connection implements Connection { }); } - sendWebrtcScreenSharingSignal(signal: any, roomId: string, userId? : string|null) { - return this.getSocket().emit(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, { + public sendWebrtcScreenSharingSignal(signal: unknown, roomId: string, userId? : string|null) { + return this.socket.emit(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, { userId: userId, roomId: roomId, signal: signal @@ -213,21 +213,8 @@ export class Connection implements Connection { return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback); } - receiveWebrtcScreenSharingSignal(callback: Function) { - return this.getSocket().on(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, callback); - } - - private errorMessage(): void { - this.getSocket().on(EventMessage.MESSAGE_ERROR, (message: string) => { - console.error(EventMessage.MESSAGE_ERROR, message); - }) - } - - private disconnectServer(): void { - this.getSocket().on(EventMessage.CONNECT_ERROR, () => { - this.GameManager.switchToDisconnectedScene(); - }); - + public receiveWebrtcScreenSharingSignal(callback: Function) { + return this.socket.on(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, callback); } public onServerDisconnected(callback: (reason: string) => void): void { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index c4517545..3c3a6536 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -18,7 +18,7 @@ import {PlayerMovement} from "./PlayerMovement"; import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator"; import {RemotePlayer} from "../Entity/RemotePlayer"; import {Queue} from 'queue-typescript'; -import {SimplePeer, UserSimplePeer} from "../../WebRtc/SimplePeer"; +import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; import {FourOFourSceneName} from "../Reconnecting/FourOFourScene"; import {loadAllLayers} from "../Entity/body_character"; @@ -229,7 +229,7 @@ export class GameScene extends Phaser.Scene { this.simplePeer = new SimplePeer(this.connection); const self = this; this.simplePeer.registerPeerConnectionListener({ - onConnect(user: UserSimplePeer) { + onConnect(user: UserSimplePeerInterface) { self.presentationModeSprite.setVisible(true); self.chatModeSprite.setVisible(true); }, diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 167faded..d7b40c39 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -11,6 +11,7 @@ interface MediaServiceInterface extends MediaDevices{ } type UpdatedLocalStreamCallback = (media: MediaStream) => void; +type UpdatedScreenSharingCallback = (media: MediaStream) => void; // TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only) // TODO: verify that microphone event listeners are not triggered plenty of time NOW (since MediaManager is created many times!!!!) @@ -31,11 +32,10 @@ export class MediaManager { video: videoConstraint }; updatedLocalStreamCallBacks : Set = new Set(); - // TODO: updatedScreenSharingCallBack should have same signature as updatedLocalStreamCallBacks - updatedScreenSharingCallBack : Function; + updatedScreenSharingCallBacks : Set = new Set(); - constructor(updatedScreenSharingCallBack : Function) { - this.updatedScreenSharingCallBack = updatedScreenSharingCallBack; + + constructor() { this.myCamVideo = this.getElementByIdOrFail('myCamVideo'); this.webrtcInAudio = this.getElementByIdOrFail('audio-webrtc-in'); @@ -69,14 +69,14 @@ export class MediaManager { //update tracking }); - this.monitorClose = document.getElementById('monitor-close'); + this.monitorClose = this.getElementByIdOrFail('monitor-close'); this.monitorClose.style.display = "block"; this.monitorClose.addEventListener('click', (e: any) => { e.preventDefault(); this.enabledMonitor(); //update tracking }); - this.monitor = document.getElementById('monitor'); + this.monitor = this.getElementByIdOrFail('monitor'); this.monitor.style.display = "none"; this.monitor.addEventListener('click', (e: any) => { e.preventDefault(); @@ -90,6 +90,11 @@ export class MediaManager { this.updatedLocalStreamCallBacks.add(callback); } + onUpdateScreenSharing(callback: UpdatedScreenSharingCallback): void { + + this.updatedScreenSharingCallBacks.add(callback); + } + removeUpdateLocalStreamEventListener(callback: UpdatedLocalStreamCallback): void { this.updatedLocalStreamCallBacks.delete(callback); } @@ -100,6 +105,12 @@ export class MediaManager { } } + private triggerUpdatedScreenSharingCallbacks(stream: MediaStream): void { + for (const callback of this.updatedScreenSharingCallBacks) { + callback(stream); + } + } + activeVisio(){ const gameOverlay = this.getElementByIdOrFail('game-overlay'); gameOverlay.classList.add('active'); @@ -156,7 +167,7 @@ export class MediaManager { this.monitorClose.style.display = "none"; this.monitor.style.display = "block"; this.getScreenMedia().then((stream) => { - this.updatedScreenSharingCallBack(stream); + this.triggerUpdatedScreenSharingCallbacks(stream); }); } @@ -168,7 +179,7 @@ export class MediaManager { }); this.localScreenCapture = null; this.getCamera().then((stream) => { - this.updatedScreenSharingCallBack(stream); + this.triggerUpdatedScreenSharingCallbacks(stream); }); } diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index e0dbe4a6..582b8eb2 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -15,7 +15,7 @@ export interface UserSimplePeerInterface{ } export interface PeerConnectionListener { - onConnect(user: UserSimplePeer): void; + onConnect(user: UserSimplePeerInterface): void; onDisconnect(userId: string): void; } @@ -138,7 +138,7 @@ export class SimplePeer { mediaManager.addActiveVideo(user.userId, name); } - const peerOption : SimplePeerNamespace.Instance = new Peer({ + const peer : SimplePeerNamespace.Instance = new Peer({ initiator: user.initiator ? user.initiator : false, reconnectTimer: 10000, config: { @@ -153,9 +153,7 @@ export class SimplePeer { }, ] } - }; - console.log("peerOption", peerOption); - let peer : SimplePeerNamespace.Instance = new Peer(peerOption); + }); if(screenSharing){ this.PeerScreenSharingConnectionArray.set(user.userId, peer); }else { @@ -434,11 +432,12 @@ export class SimplePeer { //this.Connection.sendWebrtcScreenSharingStart(this.WebRtcRoomId); - if(!this.Connection.userId){ + const userId = this.Connection.getUserId(); + if(!userId){ return; } let screenSharingUser: UserSimplePeerInterface = { - userId: this.Connection.userId, + userId, initiator: true }; let PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser, true); @@ -454,16 +453,17 @@ export class SimplePeer { } mediaManager.addStreamRemoteScreenSharing(screenSharingUser.userId, mediaManager.localScreenCapture); } else { - if (!this.Connection.userId || !this.PeerScreenSharingConnectionArray.has(this.Connection.userId)) { + const userId = this.Connection.getUserId(); + if (!userId || !this.PeerScreenSharingConnectionArray.has(userId)) { return; } - let PeerConnectionScreenSharing = this.PeerScreenSharingConnectionArray.get(this.Connection.userId); + let PeerConnectionScreenSharing = this.PeerScreenSharingConnectionArray.get(userId); console.log("updatedScreenSharing => destroy", PeerConnectionScreenSharing); if (!PeerConnectionScreenSharing) { return; } PeerConnectionScreenSharing.destroy(); - this.PeerScreenSharingConnectionArray.delete(this.Connection.userId); + this.PeerScreenSharingConnectionArray.delete(userId); } } } From cc1cb2f671c42574e115fe1c8b7b28bbe84ef2dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 18 Aug 2020 14:59:50 +0200 Subject: [PATCH 24/51] Fixing linting --- front/src/Connection.ts | 10 ++++++++-- front/src/WebRtc/MediaManager.ts | 26 +++++++++++++------------- front/src/WebRtc/SimplePeer.ts | 32 ++++++++++++++++---------------- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/front/src/Connection.ts b/front/src/Connection.ts index f2c72d64..b7926328 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -81,7 +81,13 @@ export interface WebRtcDisconnectMessageInterface { export interface WebRtcSignalMessageInterface { userId: string, - receiverId: string, + receiverId: string, // TODO: is this needed? (can we merge this with WebRtcScreenSharingMessageInterface?) + roomId: string, + signal: SignalData +} + +export interface WebRtcScreenSharingMessageInterface { + userId: string, roomId: string, signal: SignalData } @@ -213,7 +219,7 @@ export class Connection implements Connection { return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback); } - public receiveWebrtcScreenSharingSignal(callback: Function) { + public receiveWebrtcScreenSharingSignal(callback: (message: WebRtcScreenSharingMessageInterface) => void) { return this.socket.on(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, callback); } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index d7b40c39..b63dc515 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -6,9 +6,6 @@ const videoConstraint: boolean|MediaTrackConstraints = { height: { ideal: 720 }, facingMode: "user" }; -interface MediaServiceInterface extends MediaDevices{ - getDisplayMedia(constrain: any) : Promise; -} type UpdatedLocalStreamCallback = (media: MediaStream) => void; type UpdatedScreenSharingCallback = (media: MediaStream) => void; @@ -71,14 +68,14 @@ export class MediaManager { this.monitorClose = this.getElementByIdOrFail('monitor-close'); this.monitorClose.style.display = "block"; - this.monitorClose.addEventListener('click', (e: any) => { + this.monitorClose.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.enabledMonitor(); //update tracking }); this.monitor = this.getElementByIdOrFail('monitor'); this.monitor.style.display = "none"; - this.monitor.addEventListener('click', (e: any) => { + this.monitor.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.disabledMonitor(); //update tracking @@ -191,8 +188,8 @@ export class MediaManager { this.localScreenCapture = stream; return stream; }) - .catch((err: any) => { - console.error("Error => getScreenMedia => " + err); + .catch((err: unknown) => { + console.error("Error => getScreenMedia => ", err); throw err; }); }catch (err) { @@ -203,10 +200,13 @@ export class MediaManager { } private _startScreenCapture() { - if ((navigator as any).getDisplayMedia) { - return (navigator as any).getDisplayMedia({video: true}); - } else if ((navigator.mediaDevices as any).getDisplayMedia) { - return (navigator.mediaDevices as any).getDisplayMedia({video: true}); + // getDisplayMedia was moved to mediaDevices in 2018. Typescript definitions are not up to date yet. + // See: https://github.com/w3c/mediacapture-screen-share/pull/86 + // https://github.com/microsoft/TypeScript/issues/31821 + if ((navigator as any).getDisplayMedia) { // eslint-disable-line @typescript-eslint/no-explicit-any + return (navigator as any).getDisplayMedia({video: true}); // eslint-disable-line @typescript-eslint/no-explicit-any + } else if ((navigator.mediaDevices as any).getDisplayMedia) { // eslint-disable-line @typescript-eslint/no-explicit-any + return (navigator.mediaDevices as any).getDisplayMedia({video: true}); // eslint-disable-line @typescript-eslint/no-explicit-any } else { //return navigator.mediaDevices.getUserMedia(({video: {mediaSource: 'screen'}} as any)); return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars @@ -302,13 +302,13 @@ export class MediaManager { userId = `screen-sharing-${userId}`; this.webrtcInAudio.play(); // FIXME: switch to DisplayManager! - let elementRemoteVideo = this.getElementByIdOrFail("activeScreenSharing"); + const elementRemoteVideo = this.getElementByIdOrFail("activeScreenSharing"); elementRemoteVideo.insertAdjacentHTML('beforeend', `
`); - let activeHTMLVideoElement : HTMLElement|null = document.getElementById(userId); + const activeHTMLVideoElement : HTMLElement|null = document.getElementById(userId); if(!activeHTMLVideoElement){ return; } diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 582b8eb2..72786d63 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -1,6 +1,6 @@ import { Connection, - WebRtcDisconnectMessageInterface, + WebRtcDisconnectMessageInterface, WebRtcScreenSharingMessageInterface, WebRtcSignalMessageInterface, WebRtcStartMessageInterface } from "../Connection"; @@ -64,7 +64,7 @@ export class SimplePeer { }); //receive signal by gemer - this.Connection.receiveWebrtcScreenSharingSignal((message: WebRtcDisconnectMessageInterface) => { + this.Connection.receiveWebrtcScreenSharingSignal((message: WebRtcScreenSharingMessageInterface) => { this.receiveWebrtcScreenSharingSignal(message); }); @@ -200,7 +200,7 @@ export class SimplePeer { }); peer.on('data', (chunk: Buffer) => { - let constraint = JSON.parse(chunk.toString('utf8')); + const constraint = JSON.parse(chunk.toString('utf8')); console.log("data", constraint); if (constraint.audio) { mediaManager.enabledMicrophoneByUserId(user.userId); @@ -264,7 +264,7 @@ export class SimplePeer { private closeScreenSharingConnection(userId : string) { try { mediaManager.removeActiveScreenSharingVideo(userId); - let peer = this.PeerScreenSharingConnectionArray.get(userId); + const peer = this.PeerScreenSharingConnectionArray.get(userId); if (peer === undefined) { console.warn("Tried to close connection for user "+userId+" but could not find user") return; @@ -312,12 +312,12 @@ export class SimplePeer { * @param userId * @param data */ - private sendWebrtcScreenSharingSignal(data: any, userId : string) { + private sendWebrtcScreenSharingSignal(data: unknown, userId : string) { console.log("sendWebrtcScreenSharingSignal", data); try { this.Connection.sendWebrtcScreenSharingSignal(data, this.WebRtcRoomId, userId); }catch (e) { - console.error(`sendWebrtcSignal => ${userId}`, e); + console.error(`sendWebrtcScreenSharingSignal => ${userId}`, e); } } @@ -339,14 +339,14 @@ export class SimplePeer { } } - private receiveWebrtcScreenSharingSignal(data: any) { + private receiveWebrtcScreenSharingSignal(data: WebRtcScreenSharingMessageInterface) { console.log("receiveWebrtcScreenSharingSignal", data); try { //if offer type, create peer connection if(data.signal.type === "offer"){ this.createPeerConnection(data, true); } - let peer = this.PeerScreenSharingConnectionArray.get(data.userId); + const peer = this.PeerScreenSharingConnectionArray.get(data.userId); if (peer !== undefined) { peer.signal(data.signal); } else { @@ -386,11 +386,11 @@ export class SimplePeer { */ private addMedia (userId : string) { try { - let PeerConnection = this.PeerConnectionArray.get(userId); + const PeerConnection = this.PeerConnectionArray.get(userId); if (!PeerConnection) { throw new Error('While adding media, cannot find user with ID ' + userId); } - let localStream: MediaStream | null = mediaManager.localStream; + const localStream: MediaStream | null = mediaManager.localStream; PeerConnection.write(new Buffer(JSON.stringify(mediaManager.constraintsMedia))); if(!localStream){ @@ -406,12 +406,12 @@ export class SimplePeer { } } - private addMediaScreenSharing (userId : any = null) { - let PeerConnection = this.PeerScreenSharingConnectionArray.get(userId); + private addMediaScreenSharing(userId : string) { + const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId); if (!PeerConnection) { throw new Error('While adding media, cannot find user with ID ' + userId); } - let localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; + const localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; if(!localScreenCapture){ return; } @@ -436,11 +436,11 @@ export class SimplePeer { if(!userId){ return; } - let screenSharingUser: UserSimplePeerInterface = { + const screenSharingUser: UserSimplePeerInterface = { userId, initiator: true }; - let PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser, true); + const PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser, true); if (!PeerConnectionScreenSharing) { return; } @@ -457,7 +457,7 @@ export class SimplePeer { if (!userId || !this.PeerScreenSharingConnectionArray.has(userId)) { return; } - let PeerConnectionScreenSharing = this.PeerScreenSharingConnectionArray.get(userId); + const PeerConnectionScreenSharing = this.PeerScreenSharingConnectionArray.get(userId); console.log("updatedScreenSharing => destroy", PeerConnectionScreenSharing); if (!PeerConnectionScreenSharing) { return; From 6c5772e84986187b568d39a4e6a709696e99a033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 18 Aug 2020 15:31:42 +0200 Subject: [PATCH 25/51] Fixing typipng in back --- back/src/Controller/IoSocketController.ts | 2 +- back/src/Model/Websocket/WebRtcSignalMessage.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 0c1956f3..923f36a9 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -301,7 +301,7 @@ export class IoSocketController { console.warn('Invalid WEBRTC_SIGNAL message received: ', data); return; } - if(data && data.signal && (data.signal as any).type === "offer"){ + if(data.signal.type === "offer"){ let roomOffer = this.offerScreenSharingByClient.get(data.roomId); if(!roomOffer){ roomOffer = new Map(); diff --git a/back/src/Model/Websocket/WebRtcSignalMessage.ts b/back/src/Model/Websocket/WebRtcSignalMessage.ts index 8236d338..56a19060 100644 --- a/back/src/Model/Websocket/WebRtcSignalMessage.ts +++ b/back/src/Model/Websocket/WebRtcSignalMessage.ts @@ -1,17 +1,22 @@ import * as tg from "generic-type-guard"; +export const isSignalData = + new tg.IsInterface().withProperties({ + type: tg.isOptional(tg.isString) + }).get(); + export const isWebRtcSignalMessageInterface = new tg.IsInterface().withProperties({ userId: tg.isString, receiverId: tg.isString, roomId: tg.isString, - signal: tg.isUnknown + signal: isSignalData }).get(); export const isWebRtcScreenSharingSignalMessageInterface = new tg.IsInterface().withProperties({ userId: tg.isString, roomId: tg.isString, - signal: tg.isUnknown + signal: isSignalData }).get(); export const isWebRtcScreenSharingStartMessageInterface = new tg.IsInterface().withProperties({ @@ -19,3 +24,4 @@ export const isWebRtcScreenSharingStartMessageInterface = roomId: tg.isString }).get(); export type WebRtcSignalMessageInterface = tg.GuardedType; +export type WebRtcScreenSharingMessageInterface = tg.GuardedType; From 011953428359e28eb55320172023b65cb895579d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 20 Aug 2020 00:05:00 +0200 Subject: [PATCH 26/51] First version of screen-sharing that works when a user is joining a group after screen sharing begun. --- back/src/Controller/IoSocketController.ts | 39 +++-- .../Model/Websocket/WebRtcSignalMessage.ts | 7 - front/dist/resources/style/style.css | 36 ----- front/src/Connection.ts | 17 +- front/src/WebRtc/MediaManager.ts | 51 +++--- front/src/WebRtc/SimplePeer.ts | 148 ++++++++++-------- 6 files changed, 130 insertions(+), 168 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 923f36a9..501c6145 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -44,8 +44,6 @@ export class IoSocketController { private nbClientsGauge: Gauge; private nbClientsPerRoomGauge: Gauge; - private offerScreenSharingByClient: Map> = new Map>(); - constructor(server: http.Server) { this.Io = socketIO(server); this.nbClientsGauge = new Gauge({ @@ -229,11 +227,11 @@ export class IoSocketController { }); socket.on(SockerIoEvent.WEBRTC_SIGNAL, (data: unknown) => { - this.emit((socket as ExSocketInterface), data, SockerIoEvent.WEBRTC_SIGNAL); + this.emitVideo((socket as ExSocketInterface), data); }); socket.on(SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, (data: unknown) => { - this.emitScreenSharing((socket as ExSocketInterface), data, SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL); + this.emitScreenSharing((socket as ExSocketInterface), data); }); socket.on(SockerIoEvent.DISCONNECT, () => { @@ -280,7 +278,7 @@ export class IoSocketController { }); } - emit(socket: ExSocketInterface, data: unknown, event: SockerIoEvent){ + emitVideo(socket: ExSocketInterface, data: unknown){ if (!isWebRtcSignalMessageInterface(data)) { socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SIGNAL message.'}); console.warn('Invalid WEBRTC_SIGNAL message received: ', data); @@ -292,25 +290,22 @@ export class IoSocketController { console.warn("While exchanging a WebRTC signal: client with id ", data.receiverId, " does not exist. This might be a race condition."); return; } - return client.emit(event, data); + return client.emit(SockerIoEvent.WEBRTC_SIGNAL, data); } - emitScreenSharing(socket: ExSocketInterface, data: unknown, event: SockerIoEvent){ - if (!isWebRtcScreenSharingSignalMessageInterface(data)) { - socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SIGNAL message.'}); - console.warn('Invalid WEBRTC_SIGNAL message received: ', data); + emitScreenSharing(socket: ExSocketInterface, data: unknown){ + if (!isWebRtcSignalMessageInterface(data)) { + socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid WEBRTC_SCREEN_SHARING message.'}); + console.warn('Invalid WEBRTC_SCREEN_SHARING message received: ', data); return; } - if(data.signal.type === "offer"){ - let roomOffer = this.offerScreenSharingByClient.get(data.roomId); - if(!roomOffer){ - roomOffer = new Map(); - } - roomOffer.set(data.userId, data.signal); - this.offerScreenSharingByClient.set(data.roomId, roomOffer); + //send only at user + const client = this.sockets.get(data.receiverId); + if (client === undefined) { + console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.receiverId, " does not exist. This might be a race condition."); + return; } - //share at all others clients send only at user - return socket.in(data.roomId).emit(event, data); + return client.emit(SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, data); } searchClientByIdOrFail(userId: string): ExSocketInterface { @@ -393,13 +388,15 @@ export class IoSocketController { if (this.Io.sockets.adapter.rooms[roomId].length < 2 /*|| this.Io.sockets.adapter.rooms[roomId].length >= 4*/) { return; } + + // TODO: scanning all sockets is maybe not the most efficient const clients: Array = (Object.values(this.Io.sockets.sockets) as Array) .filter((client: ExSocketInterface) => client.webRtcRoomId && client.webRtcRoomId === roomId); //send start at one client to initialise offer webrtc //send all users in room to create PeerConnection in front clients.forEach((client: ExSocketInterface, index: number) => { - const clientsId = clients.reduce((tabs: Array, clientId: ExSocketInterface, indexClientId: number) => { + const peerClients = clients.reduce((tabs: Array, clientId: ExSocketInterface, indexClientId: number) => { if (!clientId.userId || clientId.userId === client.userId) { return tabs; } @@ -411,7 +408,7 @@ export class IoSocketController { return tabs; }, []); - client.emit(SockerIoEvent.WEBRTC_START, {clients: clientsId, roomId: roomId}); + client.emit(SockerIoEvent.WEBRTC_START, {clients: peerClients, roomId: roomId}); }); } diff --git a/back/src/Model/Websocket/WebRtcSignalMessage.ts b/back/src/Model/Websocket/WebRtcSignalMessage.ts index 56a19060..4f59f617 100644 --- a/back/src/Model/Websocket/WebRtcSignalMessage.ts +++ b/back/src/Model/Websocket/WebRtcSignalMessage.ts @@ -12,16 +12,9 @@ export const isWebRtcSignalMessageInterface = roomId: tg.isString, signal: isSignalData }).get(); -export const isWebRtcScreenSharingSignalMessageInterface = - new tg.IsInterface().withProperties({ - userId: tg.isString, - roomId: tg.isString, - signal: isSignalData - }).get(); export const isWebRtcScreenSharingStartMessageInterface = new tg.IsInterface().withProperties({ userId: tg.isString, roomId: tg.isString }).get(); export type WebRtcSignalMessageInterface = tg.GuardedType; -export type WebRtcScreenSharingMessageInterface = tg.GuardedType; diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index ac1b1527..413bce71 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -365,39 +365,3 @@ body { .chat-mode > div:last-child { flex-grow: 5; } - -/*SCREEN SHARING*/ -.active-screen-sharing video{ - transform: scaleX(1); -} -.screen-sharing-video-container { - width: 25%; - position: absolute; -} -.active-screen-sharing .screen-sharing-video-container video:hover{ - width: 200%; - z-index: 11; -} -.active-screen-sharing .screen-sharing-video-container video{ - position: absolute; - width: 100%; - height: auto; - left: 0; - top: 0; - transition: all 0.2s ease; - z-index: 1; -} - -.active-screen-sharing .screen-sharing-video-container:nth-child(1){ - /*this is for camera of user*/ - top: 0%; -} -.active-screen-sharing .screen-sharing-video-container:nth-child(2){ - top: 25%; -} -.active-screen-sharing .screen-sharing-video-container:nth-child(3){ - top: 50%; -} -.active-screen-sharing .screen-sharing-video-container:nth-child(4) { - top: 75%; -} diff --git a/front/src/Connection.ts b/front/src/Connection.ts index b7926328..ec1db6b1 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -80,14 +80,8 @@ export interface WebRtcDisconnectMessageInterface { } export interface WebRtcSignalMessageInterface { - userId: string, - receiverId: string, // TODO: is this needed? (can we merge this with WebRtcScreenSharingMessageInterface?) - roomId: string, - signal: SignalData -} - -export interface WebRtcScreenSharingMessageInterface { - userId: string, + userId: string, // TODO: is this needed? + receiverId: string, roomId: string, signal: SignalData } @@ -203,9 +197,10 @@ export class Connection implements Connection { }); } - public sendWebrtcScreenSharingSignal(signal: unknown, roomId: string, userId? : string|null) { + public sendWebrtcScreenSharingSignal(signal: unknown, roomId: string, userId? : string|null, receiverId? : string) { return this.socket.emit(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, { - userId: userId, + userId: userId ? userId : this.userId, + receiverId: receiverId ? receiverId : this.userId, roomId: roomId, signal: signal }); @@ -219,7 +214,7 @@ export class Connection implements Connection { return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback); } - public receiveWebrtcScreenSharingSignal(callback: (message: WebRtcScreenSharingMessageInterface) => void) { + public receiveWebrtcScreenSharingSignal(callback: (message: WebRtcSignalMessageInterface) => void) { return this.socket.on(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, callback); } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index b63dc515..635174be 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -42,13 +42,13 @@ export class MediaManager { this.microphoneClose.style.display = "none"; this.microphoneClose.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); - this.enabledMicrophone(); + this.enableMicrophone(); //update tracking }); this.microphone = this.getElementByIdOrFail('microphone'); this.microphone.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); - this.disabledMicrophone(); + this.disableMicrophone(); //update tracking }); @@ -56,13 +56,13 @@ export class MediaManager { this.cinemaClose.style.display = "none"; this.cinemaClose.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); - this.enabledCamera(); + this.enableCamera(); //update tracking }); this.cinema = this.getElementByIdOrFail('cinema'); this.cinema.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); - this.disabledCamera(); + this.disableCamera(); //update tracking }); @@ -70,24 +70,24 @@ export class MediaManager { this.monitorClose.style.display = "block"; this.monitorClose.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); - this.enabledMonitor(); + this.enableScreenSharing(); //update tracking }); this.monitor = this.getElementByIdOrFail('monitor'); this.monitor.style.display = "none"; this.monitor.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); - this.disabledMonitor(); + this.disableScreenSharing(); //update tracking }); } - onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void { + public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void { this.updatedLocalStreamCallBacks.add(callback); } - onUpdateScreenSharing(callback: UpdatedScreenSharingCallback): void { + public onUpdateScreenSharing(callback: UpdatedScreenSharingCallback): void { this.updatedScreenSharingCallBacks.add(callback); } @@ -108,12 +108,12 @@ export class MediaManager { } } - activeVisio(){ + showGameOverlay(){ const gameOverlay = this.getElementByIdOrFail('game-overlay'); gameOverlay.classList.add('active'); } - enabledCamera() { + private enableCamera() { this.cinemaClose.style.display = "none"; this.cinema.style.display = "block"; this.constraintsMedia.video = videoConstraint; @@ -122,7 +122,7 @@ export class MediaManager { }); } - disabledCamera() { + private disableCamera() { this.cinemaClose.style.display = "block"; this.cinema.style.display = "none"; this.constraintsMedia.video = false; @@ -137,7 +137,7 @@ export class MediaManager { }); } - enabledMicrophone() { + private enableMicrophone() { this.microphoneClose.style.display = "none"; this.microphone.style.display = "block"; this.constraintsMedia.audio = true; @@ -146,7 +146,7 @@ export class MediaManager { }); } - disabledMicrophone() { + private disableMicrophone() { this.microphoneClose.style.display = "block"; this.microphone.style.display = "none"; this.constraintsMedia.audio = false; @@ -160,7 +160,7 @@ export class MediaManager { }); } - enabledMonitor() { + private enableScreenSharing() { this.monitorClose.style.display = "none"; this.monitor.style.display = "block"; this.getScreenMedia().then((stream) => { @@ -168,7 +168,7 @@ export class MediaManager { }); } - disabledMonitor() { + private disableScreenSharing() { this.monitorClose.style.display = "block"; this.monitor.style.display = "none"; this.localScreenCapture?.getTracks().forEach((track: MediaStreamTrack) => { @@ -299,21 +299,18 @@ export class MediaManager { * @param userId */ addScreenSharingActiveVideo(userId : string){ - userId = `screen-sharing-${userId}`; this.webrtcInAudio.play(); - // FIXME: switch to DisplayManager! - const elementRemoteVideo = this.getElementByIdOrFail("activeScreenSharing"); - elementRemoteVideo.insertAdjacentHTML('beforeend', ` -
+ + userId = `screen-sharing-${userId}`; + const html = ` +
- `); - const activeHTMLVideoElement : HTMLElement|null = document.getElementById(userId); - if(!activeHTMLVideoElement){ - return; - } - console.log(userId, (activeHTMLVideoElement as HTMLVideoElement)); - this.remoteVideo.set(userId, (activeHTMLVideoElement as HTMLVideoElement)); + `; + + layoutManager.add(DivImportance.Important, userId, html); + + this.remoteVideo.set(userId, this.getElementByIdOrFail(userId)); } /** diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 72786d63..0d2dd068 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -1,6 +1,6 @@ import { Connection, - WebRtcDisconnectMessageInterface, WebRtcScreenSharingMessageInterface, + WebRtcDisconnectMessageInterface, WebRtcSignalMessageInterface, WebRtcStartMessageInterface } from "../Connection"; @@ -30,18 +30,18 @@ export class SimplePeer { private PeerScreenSharingConnectionArray: Map = new Map(); private PeerConnectionArray: Map = new Map(); - private readonly updateLocalStreamCallback: (media: MediaStream) => void; - private readonly updateScreenSharingCallback: (media: MediaStream) => void; + private readonly sendLocalVideoStreamCallback: (media: MediaStream) => void; + private readonly sendLocalScreenSharingStreamCallback: (media: MediaStream) => void; private readonly peerConnectionListeners: Array = new Array(); constructor(Connection: Connection, WebRtcRoomId: string = "test-webrtc") { this.Connection = Connection; this.WebRtcRoomId = WebRtcRoomId; // We need to go through this weird bound function pointer in order to be able to "free" this reference later. - this.updateLocalStreamCallback = this.updatedLocalStream.bind(this); - this.updateScreenSharingCallback = this.updatedScreenSharing.bind(this); - mediaManager.onUpdateLocalStream(this.updateLocalStreamCallback); - mediaManager.onUpdateScreenSharing(this.updateScreenSharingCallback); + this.sendLocalVideoStreamCallback = this.sendLocalVideoStream.bind(this); + this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this); + mediaManager.onUpdateLocalStream(this.sendLocalVideoStreamCallback); + mediaManager.onUpdateScreenSharing(this.sendLocalScreenSharingStreamCallback); this.initialise(); } @@ -64,11 +64,11 @@ export class SimplePeer { }); //receive signal by gemer - this.Connection.receiveWebrtcScreenSharingSignal((message: WebRtcScreenSharingMessageInterface) => { + this.Connection.receiveWebrtcScreenSharingSignal((message: WebRtcSignalMessageInterface) => { this.receiveWebrtcScreenSharingSignal(message); }); - mediaManager.activeVisio(); + mediaManager.showGameOverlay(); mediaManager.getCamera().then(() => { //receive message start @@ -88,7 +88,7 @@ export class SimplePeer { private receiveWebrtcStart(data: WebRtcStartMessageInterface) { this.WebRtcRoomId = data.roomId; this.Users = data.clients; - // Note: the clients array contain the list of all clients (event the ones we are already connected to in case a user joints a group) + // Note: the clients array contain the list of all clients (even the ones we are already connected to in case a user joints a group) // So we can receive a request we already had before. (which will abort at the first line of createPeerConnection) // TODO: refactor this to only send a message to connect to one user (rather than several users). // This would be symmetrical to the way we handle disconnection. @@ -102,6 +102,7 @@ export class SimplePeer { * server has two people connected, start the meet */ private startWebRtc() { + console.warn('startWebRtc startWebRtc'); this.Users.forEach((user: UserSimplePeerInterface) => { //if it's not an initiator, peer connection will be created when gamer will receive offer signal if(!user.initiator){ @@ -131,8 +132,11 @@ export class SimplePeer { } if(screenSharing) { - mediaManager.removeActiveScreenSharingVideo(user.userId); - mediaManager.addScreenSharingActiveVideo(user.userId); + // We should display the screen sharing ONLY if we are not initiator + if (!user.initiator) { + mediaManager.removeActiveScreenSharingVideo(user.userId); + mediaManager.addScreenSharingActiveVideo(user.userId); + } }else{ mediaManager.removeActiveVideo(user.userId); mediaManager.addActiveVideo(user.userId, name); @@ -156,17 +160,18 @@ export class SimplePeer { }); if(screenSharing){ this.PeerScreenSharingConnectionArray.set(user.userId, peer); - }else { + } else { this.PeerConnectionArray.set(user.userId, peer); } //start listen signal for the peer connection peer.on('signal', (data: unknown) => { if(screenSharing){ + //console.log('Sending WebRTC offer for screen sharing ', data, ' to ', user.userId); this.sendWebrtcScreenSharingSignal(data, user.userId); - return; + } else { + this.sendWebrtcSignal(data, user.userId); } - this.sendWebrtcSignal(data, user.userId); }); peer.on('stream', (stream: MediaStream) => { @@ -197,6 +202,12 @@ export class SimplePeer { peer.on('connect', () => { mediaManager.isConnected(user.userId); console.info(`connect => ${user.userId}`); + + // When a connection is established to a video stream, and if a screen sharing is taking place, + // the user sharing screen should also initiate a connection to the remote user! + if (screenSharing === false && mediaManager.localScreenCapture) { + this.sendLocalScreenSharingStreamToUser(user.userId); + } }); peer.on('data', (chunk: Buffer) => { @@ -217,9 +228,9 @@ export class SimplePeer { }); if(screenSharing){ - this.addMediaScreenSharing(user.userId); + this.pushScreenSharingToRemoteUser(user.userId); }else { - this.addMedia(user.userId); + this.pushVideoToRemoteUser(user.userId); } for (const peerConnectionListener of this.peerConnectionListeners) { @@ -290,7 +301,7 @@ export class SimplePeer { * Unregisters any held event handler. */ public unregister() { - mediaManager.removeUpdateLocalStreamEventListener(this.updateLocalStreamCallback); + mediaManager.removeUpdateLocalStreamEventListener(this.sendLocalVideoStreamCallback); } /** @@ -299,7 +310,6 @@ export class SimplePeer { * @param data */ private sendWebrtcSignal(data: unknown, userId : string) { - console.log("sendWebrtcSignal", data); try { this.Connection.sendWebrtcSignal(data, this.WebRtcRoomId, null, userId); }catch (e) { @@ -315,7 +325,7 @@ export class SimplePeer { private sendWebrtcScreenSharingSignal(data: unknown, userId : string) { console.log("sendWebrtcScreenSharingSignal", data); try { - this.Connection.sendWebrtcScreenSharingSignal(data, this.WebRtcRoomId, userId); + this.Connection.sendWebrtcScreenSharingSignal(data, this.WebRtcRoomId, null, userId); }catch (e) { console.error(`sendWebrtcScreenSharingSignal => ${userId}`, e); } @@ -339,7 +349,7 @@ export class SimplePeer { } } - private receiveWebrtcScreenSharingSignal(data: WebRtcScreenSharingMessageInterface) { + private receiveWebrtcScreenSharingSignal(data: WebRtcSignalMessageInterface) { console.log("receiveWebrtcScreenSharingSignal", data); try { //if offer type, create peer connection @@ -384,7 +394,7 @@ export class SimplePeer { * * @param userId */ - private addMedia (userId : string) { + private pushVideoToRemoteUser(userId : string) { try { const PeerConnection = this.PeerConnectionArray.get(userId); if (!PeerConnection) { @@ -396,74 +406,80 @@ export class SimplePeer { if(!localStream){ return; } - if (localStream) { - for (const track of localStream.getTracks()) { - PeerConnection.addTrack(track, localStream); - } + + for (const track of localStream.getTracks()) { + PeerConnection.addTrack(track, localStream); } }catch (e) { - console.error(`addMedia => addMedia => ${userId}`, e); + console.error(`pushVideoToRemoteUser => ${userId}`, e); } } - private addMediaScreenSharing(userId : string) { + private pushScreenSharingToRemoteUser(userId : string) { const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId); if (!PeerConnection) { - throw new Error('While adding media, cannot find user with ID ' + userId); + throw new Error('While pushing screen sharing, cannot find user with ID ' + userId); } const localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; if(!localScreenCapture){ return; } - /*for (const track of localScreenCapture.getTracks()) { + + for (const track of localScreenCapture.getTracks()) { PeerConnection.addTrack(track, localScreenCapture); - }*/ + } return; } - updatedLocalStream(){ + public sendLocalVideoStream(){ this.Users.forEach((user: UserSimplePeerInterface) => { - this.addMedia(user.userId); + this.pushVideoToRemoteUser(user.userId); }) } - updatedScreenSharing() { + /** + * Triggered locally when clicking on the screen sharing button + */ + public sendLocalScreenSharingStream() { if (mediaManager.localScreenCapture) { - - //this.Connection.sendWebrtcScreenSharingStart(this.WebRtcRoomId); - - const userId = this.Connection.getUserId(); - if(!userId){ - return; + for (const user of this.Users) { + this.sendLocalScreenSharingStreamToUser(user.userId); } - const screenSharingUser: UserSimplePeerInterface = { - userId, - initiator: true - }; - const PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser, true); - if (!PeerConnectionScreenSharing) { - return; - } - try { - for (const track of mediaManager.localScreenCapture.getTracks()) { - PeerConnectionScreenSharing.addTrack(track, mediaManager.localScreenCapture); - } - }catch (e) { - console.error("updatedScreenSharing => ", e); - } - mediaManager.addStreamRemoteScreenSharing(screenSharingUser.userId, mediaManager.localScreenCapture); } else { - const userId = this.Connection.getUserId(); - if (!userId || !this.PeerScreenSharingConnectionArray.has(userId)) { - return; + for (const user of this.Users) { + this.stopLocalScreenSharingStreamToUser(user.userId); } - const PeerConnectionScreenSharing = this.PeerScreenSharingConnectionArray.get(userId); - console.log("updatedScreenSharing => destroy", PeerConnectionScreenSharing); - if (!PeerConnectionScreenSharing) { - return; - } - PeerConnectionScreenSharing.destroy(); - this.PeerScreenSharingConnectionArray.delete(userId); } } + + private sendLocalScreenSharingStreamToUser(userId: string): void { + // If a connection already exists with user (because it is already sharing a screen with us... let's use this connection) + if (this.PeerScreenSharingConnectionArray.has(userId)) { + this.pushScreenSharingToRemoteUser(userId); + return; + } + + const screenSharingUser: UserSimplePeerInterface = { + userId, + initiator: true + }; + const PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser, true); + if (!PeerConnectionScreenSharing) { + return; + } + } + + private stopLocalScreenSharingStreamToUser(userId: string): void { + const PeerConnectionScreenSharing = this.PeerScreenSharingConnectionArray.get(userId); + if (!PeerConnectionScreenSharing) { + throw new Error('Weird, screen sharing connection to user ' + userId + 'not found') + } + + console.log("updatedScreenSharing => destroy", PeerConnectionScreenSharing); + // FIXME: maybe we don't want to destroy the connexion if it is used in the other way around! + // FIXME: maybe we don't want to destroy the connexion if it is used in the other way around! + // FIXME: maybe we don't want to destroy the connexion if it is used in the other way around! + PeerConnectionScreenSharing.destroy(); + this.PeerScreenSharingConnectionArray.delete(userId); + } } From 894f7c8009dda668763a5af4962dceb8a3b44779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 20 Aug 2020 15:21:07 +0200 Subject: [PATCH 27/51] Removing useless roomID parameter in WebRtcSignal message --- back/src/Model/Websocket/WebRtcSignalMessage.ts | 1 - front/src/Connection.ts | 11 ++++------- front/src/WebRtc/SimplePeer.ts | 4 ++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/back/src/Model/Websocket/WebRtcSignalMessage.ts b/back/src/Model/Websocket/WebRtcSignalMessage.ts index 4f59f617..865319be 100644 --- a/back/src/Model/Websocket/WebRtcSignalMessage.ts +++ b/back/src/Model/Websocket/WebRtcSignalMessage.ts @@ -9,7 +9,6 @@ export const isWebRtcSignalMessageInterface = new tg.IsInterface().withProperties({ userId: tg.isString, receiverId: tg.isString, - roomId: tg.isString, signal: isSignalData }).get(); export const isWebRtcScreenSharingStartMessageInterface = diff --git a/front/src/Connection.ts b/front/src/Connection.ts index ec1db6b1..4cb95d01 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -82,7 +82,6 @@ export interface WebRtcDisconnectMessageInterface { export interface WebRtcSignalMessageInterface { userId: string, // TODO: is this needed? receiverId: string, - roomId: string, signal: SignalData } @@ -188,22 +187,20 @@ export class Connection implements Connection { this.socket.on(EventMessage.CONNECT_ERROR, callback) } - public sendWebrtcSignal(signal: unknown, roomId: string, userId? : string|null, receiverId? : string) { + public sendWebrtcSignal(signal: unknown, userId? : string|null, receiverId? : string) { return this.socket.emit(EventMessage.WEBRTC_SIGNAL, { userId: userId ? userId : this.userId, receiverId: receiverId ? receiverId : this.userId, - roomId: roomId, signal: signal - }); + } as WebRtcSignalMessageInterface); } - public sendWebrtcScreenSharingSignal(signal: unknown, roomId: string, userId? : string|null, receiverId? : string) { + public sendWebrtcScreenSharingSignal(signal: unknown, userId? : string|null, receiverId? : string) { return this.socket.emit(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, { userId: userId ? userId : this.userId, receiverId: receiverId ? receiverId : this.userId, - roomId: roomId, signal: signal - }); + } as WebRtcSignalMessageInterface); } public receiveWebrtcStart(callback: (message: WebRtcStartMessageInterface) => void) { diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 0d2dd068..17a92b5b 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -311,7 +311,7 @@ export class SimplePeer { */ private sendWebrtcSignal(data: unknown, userId : string) { try { - this.Connection.sendWebrtcSignal(data, this.WebRtcRoomId, null, userId); + this.Connection.sendWebrtcSignal(data, null, userId); }catch (e) { console.error(`sendWebrtcSignal => ${userId}`, e); } @@ -325,7 +325,7 @@ export class SimplePeer { private sendWebrtcScreenSharingSignal(data: unknown, userId : string) { console.log("sendWebrtcScreenSharingSignal", data); try { - this.Connection.sendWebrtcScreenSharingSignal(data, this.WebRtcRoomId, null, userId); + this.Connection.sendWebrtcScreenSharingSignal(data, null, userId); }catch (e) { console.error(`sendWebrtcScreenSharingSignal => ${userId}`, e); } From 27ffb6b13d77972de5dc29616e8629ebacae063f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 20 Aug 2020 16:56:10 +0200 Subject: [PATCH 28/51] Refactoring SimplePeer code: splitting Peer instantiation into 2 subclasses (VideoPeer and ScreenSharingPeer). This leads to way leaner code. --- back/src/Controller/IoSocketController.ts | 10 +- .../Model/Websocket/WebRtcSignalMessage.ts | 1 - front/src/Connection.ts | 26 ++- front/src/WebRtc/ScreenSharingPeer.ts | 106 +++++++++ front/src/WebRtc/SimplePeer.ts | 209 +++++------------- front/src/WebRtc/VideoPeer.ts | 128 +++++++++++ front/tsconfig.json | 3 +- 7 files changed, 307 insertions(+), 176 deletions(-) create mode 100644 front/src/WebRtc/ScreenSharingPeer.ts create mode 100644 front/src/WebRtc/VideoPeer.ts diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 501c6145..2eca7e44 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -290,7 +290,10 @@ export class IoSocketController { console.warn("While exchanging a WebRTC signal: client with id ", data.receiverId, " does not exist. This might be a race condition."); return; } - return client.emit(SockerIoEvent.WEBRTC_SIGNAL, data); + return client.emit(SockerIoEvent.WEBRTC_SIGNAL, { + userId: socket.userId, + signal: data.signal + }); } emitScreenSharing(socket: ExSocketInterface, data: unknown){ @@ -305,7 +308,10 @@ export class IoSocketController { console.warn("While exchanging a WEBRTC_SCREEN_SHARING signal: client with id ", data.receiverId, " does not exist. This might be a race condition."); return; } - return client.emit(SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, data); + return client.emit(SockerIoEvent.WEBRTC_SCREEN_SHARING_SIGNAL, { + userId: socket.userId, + signal: data.signal + }); } searchClientByIdOrFail(userId: string): ExSocketInterface { diff --git a/back/src/Model/Websocket/WebRtcSignalMessage.ts b/back/src/Model/Websocket/WebRtcSignalMessage.ts index 865319be..5a0dd1af 100644 --- a/back/src/Model/Websocket/WebRtcSignalMessage.ts +++ b/back/src/Model/Websocket/WebRtcSignalMessage.ts @@ -7,7 +7,6 @@ export const isSignalData = export const isWebRtcSignalMessageInterface = new tg.IsInterface().withProperties({ - userId: tg.isString, receiverId: tg.isString, signal: isSignalData }).get(); diff --git a/front/src/Connection.ts b/front/src/Connection.ts index 4cb95d01..783b5d41 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -79,12 +79,16 @@ export interface WebRtcDisconnectMessageInterface { userId: string } -export interface WebRtcSignalMessageInterface { - userId: string, // TODO: is this needed? +export interface WebRtcSignalSentMessageInterface { receiverId: string, signal: SignalData } +export interface WebRtcSignalReceivedMessageInterface { + userId: string, + signal: SignalData +} + export interface StartMapInterface { mapUrlStart: string, startInstance: string @@ -187,31 +191,29 @@ export class Connection implements Connection { this.socket.on(EventMessage.CONNECT_ERROR, callback) } - public sendWebrtcSignal(signal: unknown, userId? : string|null, receiverId? : string) { + public sendWebrtcSignal(signal: unknown, receiverId : string) { return this.socket.emit(EventMessage.WEBRTC_SIGNAL, { - userId: userId ? userId : this.userId, - receiverId: receiverId ? receiverId : this.userId, + receiverId: receiverId, signal: signal - } as WebRtcSignalMessageInterface); + } as WebRtcSignalSentMessageInterface); } - public sendWebrtcScreenSharingSignal(signal: unknown, userId? : string|null, receiverId? : string) { + public sendWebrtcScreenSharingSignal(signal: unknown, receiverId : string) { return this.socket.emit(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, { - userId: userId ? userId : this.userId, - receiverId: receiverId ? receiverId : this.userId, + receiverId: receiverId, signal: signal - } as WebRtcSignalMessageInterface); + } as WebRtcSignalSentMessageInterface); } public receiveWebrtcStart(callback: (message: WebRtcStartMessageInterface) => void) { this.socket.on(EventMessage.WEBRTC_START, callback); } - public receiveWebrtcSignal(callback: (message: WebRtcSignalMessageInterface) => void) { + public receiveWebrtcSignal(callback: (message: WebRtcSignalReceivedMessageInterface) => void) { return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback); } - public receiveWebrtcScreenSharingSignal(callback: (message: WebRtcSignalMessageInterface) => void) { + public receiveWebrtcScreenSharingSignal(callback: (message: WebRtcSignalReceivedMessageInterface) => void) { return this.socket.on(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, callback); } diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts new file mode 100644 index 00000000..4b03940c --- /dev/null +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -0,0 +1,106 @@ +import * as SimplePeerNamespace from "simple-peer"; +import {mediaManager} from "./MediaManager"; +import {Connection} from "../Connection"; + +const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); + +/** + * A peer connection used to transmit video / audio signals between 2 peers. + */ +export class ScreenSharingPeer extends Peer { + constructor(private userId: string, initiator: boolean, private connection: Connection) { + super({ + initiator: initiator ? initiator : false, + reconnectTimer: 10000, + config: { + iceServers: [ + { + urls: 'stun:stun.l.google.com:19302' + }, + { + urls: 'turn:numb.viagenie.ca', + username: 'g.parant@thecodingmachine.com', + credential: 'itcugcOHxle9Acqi$' + }, + ] + } + }); + + //start listen signal for the peer connection + this.on('signal', (data: unknown) => { + this.sendWebrtcScreenSharingSignal(data); + }); + + this.on('stream', (stream: MediaStream) => { + this.stream(stream); + }); + + /*this.on('track', (track: MediaStreamTrack, stream: MediaStream) => { + });*/ + + this.on('close', () => { + this.closeScreenSharingConnection(); + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.on('error', (err: any) => { + console.error(`screen sharing error => ${this.userId} => ${err.code}`, err); + //mediaManager.isErrorScreenSharing(this.userId); + }); + + this.on('connect', () => { + // FIXME: we need to put the loader on the screen sharing connection + mediaManager.isConnected(this.userId); + console.info(`connect => ${this.userId}`); + }); + + this.pushScreenSharingToRemoteUser(); + } + + private sendWebrtcScreenSharingSignal(data: unknown) { + console.log("sendWebrtcScreenSharingSignal", data); + try { + this.connection.sendWebrtcScreenSharingSignal(data, this.userId); + }catch (e) { + console.error(`sendWebrtcScreenSharingSignal => ${this.userId}`, e); + } + } + + /** + * Sends received stream to screen. + */ + private stream(stream?: MediaStream) { + console.log(`ScreenSharingPeer::stream => ${this.userId}`, stream); + console.log(`stream => ${this.userId} => `, stream); + if(!stream){ + mediaManager.removeActiveScreenSharingVideo(this.userId); + } else { + mediaManager.addStreamRemoteScreenSharing(this.userId, stream); + } + } + + public closeScreenSharingConnection() { + try { + mediaManager.removeActiveScreenSharingVideo(this.userId); + // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" + // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. + //console.log('Closing connection with '+userId); + this.destroy(); + //console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size); + } catch (err) { + console.error("closeConnection", err) + } + } + + private pushScreenSharingToRemoteUser() { + const localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; + if(!localScreenCapture){ + return; + } + + for (const track of localScreenCapture.getTracks()) { + this.addTrack(track, localScreenCapture); + } + return; + } +} diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 17a92b5b..489f07a7 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -1,11 +1,13 @@ import { Connection, WebRtcDisconnectMessageInterface, - WebRtcSignalMessageInterface, + WebRtcSignalReceivedMessageInterface, WebRtcStartMessageInterface } from "../Connection"; import { mediaManager } from "./MediaManager"; import * as SimplePeerNamespace from "simple-peer"; +import {ScreenSharingPeer} from "./ScreenSharingPeer"; +import {VideoPeer} from "./VideoPeer"; const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); export interface UserSimplePeerInterface{ @@ -28,8 +30,8 @@ export class SimplePeer { private WebRtcRoomId: string; private Users: Array = new Array(); - private PeerScreenSharingConnectionArray: Map = new Map(); - private PeerConnectionArray: Map = new Map(); + private PeerScreenSharingConnectionArray: Map = new Map(); + private PeerConnectionArray: Map = new Map(); private readonly sendLocalVideoStreamCallback: (media: MediaStream) => void; private readonly sendLocalScreenSharingStreamCallback: (media: MediaStream) => void; private readonly peerConnectionListeners: Array = new Array(); @@ -59,12 +61,12 @@ export class SimplePeer { private initialise() { //receive signal by gemer - this.Connection.receiveWebrtcSignal((message: WebRtcSignalMessageInterface) => { + this.Connection.receiveWebrtcSignal((message: WebRtcSignalReceivedMessageInterface) => { this.receiveWebrtcSignal(message); }); //receive signal by gemer - this.Connection.receiveWebrtcScreenSharingSignal((message: WebRtcSignalMessageInterface) => { + this.Connection.receiveWebrtcScreenSharingSignal((message: WebRtcSignalReceivedMessageInterface) => { this.receiveWebrtcScreenSharingSignal(message); }); @@ -115,10 +117,9 @@ export class SimplePeer { /** * create peer connection to bind users */ - private createPeerConnection(user : UserSimplePeerInterface, screenSharing: boolean = false) : SimplePeerNamespace.Instance | null{ + private createPeerConnection(user : UserSimplePeerInterface) : VideoPeer | null{ if( - (screenSharing && this.PeerScreenSharingConnectionArray.has(user.userId)) - || (!screenSharing && this.PeerConnectionArray.has(user.userId)) + this.PeerConnectionArray.has(user.userId) ){ return null; } @@ -131,107 +132,43 @@ export class SimplePeer { } } - if(screenSharing) { - // We should display the screen sharing ONLY if we are not initiator - if (!user.initiator) { - mediaManager.removeActiveScreenSharingVideo(user.userId); - mediaManager.addScreenSharingActiveVideo(user.userId); - } - }else{ - mediaManager.removeActiveVideo(user.userId); - mediaManager.addActiveVideo(user.userId, name); - } - - const peer : SimplePeerNamespace.Instance = new Peer({ - initiator: user.initiator ? user.initiator : false, - reconnectTimer: 10000, - config: { - iceServers: [ - { - urls: 'stun:stun.l.google.com:19302' - }, - { - urls: 'turn:numb.viagenie.ca', - username: 'g.parant@thecodingmachine.com', - credential: 'itcugcOHxle9Acqi$' - }, - ] - } - }); - if(screenSharing){ - this.PeerScreenSharingConnectionArray.set(user.userId, peer); - } else { - this.PeerConnectionArray.set(user.userId, peer); - } - - //start listen signal for the peer connection - peer.on('signal', (data: unknown) => { - if(screenSharing){ - //console.log('Sending WebRTC offer for screen sharing ', data, ' to ', user.userId); - this.sendWebrtcScreenSharingSignal(data, user.userId); - } else { - this.sendWebrtcSignal(data, user.userId); - } - }); - - peer.on('stream', (stream: MediaStream) => { - this.stream(user.userId, stream, screenSharing); - }); - - /*peer.on('track', (track: MediaStreamTrack, stream: MediaStream) => { - });*/ - - peer.on('close', () => { - if(screenSharing){ - this.closeScreenSharingConnection(user.userId); - return; - } - this.closeConnection(user.userId); - }); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - peer.on('error', (err: any) => { - console.error(`error => ${user.userId} => ${err.code}`, err); - if(screenSharing){ - //mediaManager.isErrorScreenSharing(user.userId); - return; - } - mediaManager.isError(user.userId); - }); + mediaManager.removeActiveVideo(user.userId); + mediaManager.addActiveVideo(user.userId, name); + let peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); + // When a connection is established to a video stream, and if a screen sharing is taking place, + // the user sharing screen should also initiate a connection to the remote user! peer.on('connect', () => { - mediaManager.isConnected(user.userId); - console.info(`connect => ${user.userId}`); - - // When a connection is established to a video stream, and if a screen sharing is taking place, - // the user sharing screen should also initiate a connection to the remote user! - if (screenSharing === false && mediaManager.localScreenCapture) { + if (mediaManager.localScreenCapture) { this.sendLocalScreenSharingStreamToUser(user.userId); } }); + this.PeerConnectionArray.set(user.userId, peer); - peer.on('data', (chunk: Buffer) => { - const constraint = JSON.parse(chunk.toString('utf8')); - console.log("data", constraint); - if (constraint.audio) { - mediaManager.enabledMicrophoneByUserId(user.userId); - } else { - mediaManager.disabledMicrophoneByUserId(user.userId); - } - - if (constraint.video || constraint.screen) { - mediaManager.enabledVideoByUserId(user.userId); - } else { - this.stream(user.userId); - mediaManager.disabledVideoByUserId(user.userId); - } - }); - - if(screenSharing){ - this.pushScreenSharingToRemoteUser(user.userId); - }else { - this.pushVideoToRemoteUser(user.userId); + for (const peerConnectionListener of this.peerConnectionListeners) { + peerConnectionListener.onConnect(user); } + return peer; + } + + /** + * create peer connection to bind users + */ + private createPeerScreenSharingConnection(user : UserSimplePeerInterface) : ScreenSharingPeer | null{ + if( + this.PeerScreenSharingConnectionArray.has(user.userId) + ){ + return null; + } + + // We should display the screen sharing ONLY if we are not initiator + if (!user.initiator) { + mediaManager.removeActiveScreenSharingVideo(user.userId); + mediaManager.addScreenSharingActiveVideo(user.userId); + } + + let peer = new ScreenSharingPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); + this.PeerScreenSharingConnectionArray.set(user.userId, peer); for (const peerConnectionListener of this.peerConnectionListeners) { peerConnectionListener.onConnect(user); @@ -246,16 +183,16 @@ export class SimplePeer { */ private closeConnection(userId : string) { try { - mediaManager.removeActiveVideo(userId); + //mediaManager.removeActiveVideo(userId); const peer = this.PeerConnectionArray.get(userId); if (peer === undefined) { console.warn("Tried to close connection for user "+userId+" but could not find user") return; } + peer.closeConnection(); // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. //console.log('Closing connection with '+userId); - peer.destroy(); this.PeerConnectionArray.delete(userId); this.closeScreenSharingConnection(userId); //console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size); @@ -283,7 +220,7 @@ export class SimplePeer { // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. //console.log('Closing connection with '+userId); - peer.destroy(); + peer.closeScreenSharingConnection(); this.PeerScreenSharingConnectionArray.delete(userId) //console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size); } catch (err) { @@ -295,6 +232,10 @@ export class SimplePeer { for (const userId of this.PeerConnectionArray.keys()) { this.closeConnection(userId); } + + for (const userId of this.PeerScreenSharingConnectionArray.keys()) { + this.closeScreenSharingConnection(userId); + } } /** @@ -304,35 +245,8 @@ export class SimplePeer { mediaManager.removeUpdateLocalStreamEventListener(this.sendLocalVideoStreamCallback); } - /** - * - * @param userId - * @param data - */ - private sendWebrtcSignal(data: unknown, userId : string) { - try { - this.Connection.sendWebrtcSignal(data, null, userId); - }catch (e) { - console.error(`sendWebrtcSignal => ${userId}`, e); - } - } - - /** - * - * @param userId - * @param data - */ - private sendWebrtcScreenSharingSignal(data: unknown, userId : string) { - console.log("sendWebrtcScreenSharingSignal", data); - try { - this.Connection.sendWebrtcScreenSharingSignal(data, null, userId); - }catch (e) { - console.error(`sendWebrtcScreenSharingSignal => ${userId}`, e); - } - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private receiveWebrtcSignal(data: WebRtcSignalMessageInterface) { + private receiveWebrtcSignal(data: WebRtcSignalReceivedMessageInterface) { try { //if offer type, create peer connection if(data.signal.type === "offer"){ @@ -349,12 +263,12 @@ export class SimplePeer { } } - private receiveWebrtcScreenSharingSignal(data: WebRtcSignalMessageInterface) { + private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) { console.log("receiveWebrtcScreenSharingSignal", data); try { //if offer type, create peer connection if(data.signal.type === "offer"){ - this.createPeerConnection(data, true); + this.createPeerScreenSharingConnection(data); } const peer = this.PeerScreenSharingConnectionArray.get(data.userId); if (peer !== undefined) { @@ -367,29 +281,6 @@ export class SimplePeer { } } - /** - * - * @param userId - * @param stream - */ - private stream(userId : string, stream?: MediaStream, screenSharing?: boolean) { - console.log(`stream => ${userId} => screenSharing => ${screenSharing}`, stream); - if(screenSharing){ - if(!stream){ - mediaManager.removeActiveScreenSharingVideo(userId); - return; - } - mediaManager.addStreamRemoteScreenSharing(userId, stream); - return; - } - if(!stream){ - mediaManager.disabledVideoByUserId(userId); - mediaManager.disabledMicrophoneByUserId(userId); - return; - } - mediaManager.addStreamRemoteVideo(userId, stream); - } - /** * * @param userId @@ -463,7 +354,7 @@ export class SimplePeer { userId, initiator: true }; - const PeerConnectionScreenSharing = this.createPeerConnection(screenSharingUser, true); + const PeerConnectionScreenSharing = this.createPeerScreenSharingConnection(screenSharingUser); if (!PeerConnectionScreenSharing) { return; } diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts new file mode 100644 index 00000000..bb624250 --- /dev/null +++ b/front/src/WebRtc/VideoPeer.ts @@ -0,0 +1,128 @@ +import * as SimplePeerNamespace from "simple-peer"; +import {mediaManager} from "./MediaManager"; +import {Connection} from "../Connection"; + +const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); + +/** + * A peer connection used to transmit video / audio signals between 2 peers. + */ +export class VideoPeer extends Peer { + constructor(private userId: string, initiator: boolean, private connection: Connection) { + super({ + initiator: initiator ? initiator : false, + reconnectTimer: 10000, + config: { + iceServers: [ + { + urls: 'stun:stun.l.google.com:19302' + }, + { + urls: 'turn:numb.viagenie.ca', + username: 'g.parant@thecodingmachine.com', + credential: 'itcugcOHxle9Acqi$' + }, + ] + } + }); + + //start listen signal for the peer connection + this.on('signal', (data: unknown) => { + this.sendWebrtcSignal(data); + }); + + this.on('stream', (stream: MediaStream) => { + this.stream(stream); + }); + + /*peer.on('track', (track: MediaStreamTrack, stream: MediaStream) => { + });*/ + + this.on('close', () => { + this.closeConnection(); + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.on('error', (err: any) => { + console.error(`error => ${this.userId} => ${err.code}`, err); + mediaManager.isError(userId); + }); + + this.on('connect', () => { + mediaManager.isConnected(this.userId); + console.info(`connect => ${this.userId}`); + }); + + this.on('data', (chunk: Buffer) => { + const constraint = JSON.parse(chunk.toString('utf8')); + console.log("data", constraint); + if (constraint.audio) { + mediaManager.enabledMicrophoneByUserId(this.userId); + } else { + mediaManager.disabledMicrophoneByUserId(this.userId); + } + + if (constraint.video || constraint.screen) { + mediaManager.enabledVideoByUserId(this.userId); + } else { + this.stream(undefined); + mediaManager.disabledVideoByUserId(this.userId); + } + }); + + this.pushVideoToRemoteUser(); + } + + private sendWebrtcSignal(data: unknown) { + try { + this.connection.sendWebrtcSignal(data, this.userId); + }catch (e) { + console.error(`sendWebrtcSignal => ${this.userId}`, e); + } + } + + /** + * Sends received stream to screen. + */ + private stream(stream?: MediaStream) { + console.log(`VideoPeer::stream => ${this.userId}`, stream); + if(!stream){ + mediaManager.disabledVideoByUserId(this.userId); + mediaManager.disabledMicrophoneByUserId(this.userId); + } else { + mediaManager.addStreamRemoteVideo(this.userId, stream); + } + } + + /** + * This is triggered twice. Once by the server, and once by a remote client disconnecting + */ + public closeConnection() { + try { + mediaManager.removeActiveVideo(this.userId); + // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" + // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. + //console.log('Closing connection with '+userId); + this.destroy(); + } catch (err) { + console.error("closeConnection", err) + } + } + + private pushVideoToRemoteUser() { + try { + const localStream: MediaStream | null = mediaManager.localStream; + this.write(new Buffer(JSON.stringify(mediaManager.constraintsMedia))); + + if(!localStream){ + return; + } + + for (const track of localStream.getTracks()) { + this.addTrack(track, localStream); + } + }catch (e) { + console.error(`pushVideoToRemoteUser => ${this.userId}`, e); + } + } +} diff --git a/front/tsconfig.json b/front/tsconfig.json index e56a6ee7..64d71e42 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -3,9 +3,8 @@ "outDir": "./dist/", "sourceMap": true, "moduleResolution": "node", - "noImplicitAny": true, "module": "CommonJS", - "target": "es5", + "target": "es6", "downlevelIteration": true, "jsx": "react", "allowJs": true, From 11624394792adc9e7ae3776e524d35093572c7d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 20 Aug 2020 22:23:22 +0200 Subject: [PATCH 29/51] Overloading destroy method instead of having a separate method to remove video. --- front/src/WebRtc/ScreenSharingPeer.ts | 8 ++++---- front/src/WebRtc/SimplePeer.ts | 4 ++-- front/src/WebRtc/VideoPeer.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index 4b03940c..3ce3c409 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -39,7 +39,7 @@ export class ScreenSharingPeer extends Peer { });*/ this.on('close', () => { - this.closeScreenSharingConnection(); + this.destroy(); }); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -79,16 +79,16 @@ export class ScreenSharingPeer extends Peer { } } - public closeScreenSharingConnection() { + public destroy(error?: Error): void { try { mediaManager.removeActiveScreenSharingVideo(this.userId); // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. //console.log('Closing connection with '+userId); - this.destroy(); + super.destroy(error); //console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size); } catch (err) { - console.error("closeConnection", err) + console.error("ScreenSharingPeer::destroy", err) } } diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 489f07a7..498a8608 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -189,7 +189,7 @@ export class SimplePeer { console.warn("Tried to close connection for user "+userId+" but could not find user") return; } - peer.closeConnection(); + peer.destroy(); // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. //console.log('Closing connection with '+userId); @@ -220,7 +220,7 @@ export class SimplePeer { // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. //console.log('Closing connection with '+userId); - peer.closeScreenSharingConnection(); + peer.destroy(); this.PeerScreenSharingConnectionArray.delete(userId) //console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size); } catch (err) { diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index bb624250..ec7f2576 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -39,7 +39,7 @@ export class VideoPeer extends Peer { });*/ this.on('close', () => { - this.closeConnection(); + this.destroy(); }); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -97,15 +97,15 @@ export class VideoPeer extends Peer { /** * This is triggered twice. Once by the server, and once by a remote client disconnecting */ - public closeConnection() { + public destroy(error?: Error): void { try { mediaManager.removeActiveVideo(this.userId); // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. //console.log('Closing connection with '+userId); - this.destroy(); + super.destroy(error); } catch (err) { - console.error("closeConnection", err) + console.error("VideoPeer::destroy", err) } } From dc36af19bc221813f66433a5f0922a56de579679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 20 Aug 2020 22:23:38 +0200 Subject: [PATCH 30/51] Detecting press on "stop screen sharing" --- front/src/WebRtc/MediaManager.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 635174be..63814ee4 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -186,6 +186,14 @@ export class MediaManager { return this._startScreenCapture() .then((stream: MediaStream) => { this.localScreenCapture = stream; + + // If stream ends (for instance if user clicks the stop screen sharing button in the browser), let's close the view + for (const track of stream.getTracks()) { + track.onended = () => { + this.disableScreenSharing(); + }; + } + return stream; }) .catch((err: unknown) => { From c442d6ce670582d3d19397c5fdedd6c420109d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 20 Aug 2020 22:29:14 +0200 Subject: [PATCH 31/51] Lint --- front/src/WebRtc/SimplePeer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 498a8608..02573273 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -135,7 +135,7 @@ export class SimplePeer { mediaManager.removeActiveVideo(user.userId); mediaManager.addActiveVideo(user.userId, name); - let peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); + const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); // When a connection is established to a video stream, and if a screen sharing is taking place, // the user sharing screen should also initiate a connection to the remote user! peer.on('connect', () => { @@ -167,7 +167,7 @@ export class SimplePeer { mediaManager.addScreenSharingActiveVideo(user.userId); } - let peer = new ScreenSharingPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); + const peer = new ScreenSharingPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); this.PeerScreenSharingConnectionArray.set(user.userId, peer); for (const peerConnectionListener of this.peerConnectionListeners) { From 2ae19b9f30a1a44bf50c09d71805d6906135da5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 20 Aug 2020 22:34:50 +0200 Subject: [PATCH 32/51] Fixing build --- back/src/Controller/IoSocketController.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 2eca7e44..1d5bbe06 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -13,11 +13,10 @@ import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined"; import {MessageUserMoved} from "../Model/Websocket/MessageUserMoved"; import si from "systeminformation"; import {Gauge} from "prom-client"; -import os from 'os'; import {TokenInterface} from "../Controller/AuthenticateController"; import {isJoinRoomMessageInterface} from "../Model/Websocket/JoinRoomMessage"; import {isPointInterface, PointInterface} from "../Model/Websocket/PointInterface"; -import {isWebRtcSignalMessageInterface, isWebRtcScreenSharingSignalMessageInterface, isWebRtcScreenSharingStartMessageInterface} from "../Model/Websocket/WebRtcSignalMessage"; +import {isWebRtcSignalMessageInterface} from "../Model/Websocket/WebRtcSignalMessage"; import {UserInGroupInterface} from "../Model/Websocket/UserInGroupInterface"; enum SockerIoEvent { From f60b02f1dcfbb25172aca831ce4bfa5afa56e77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 20 Aug 2020 22:57:34 +0200 Subject: [PATCH 33/51] Putting a wider onhover surface when clicking on one of the buttons to manage screen sharing or video/mic --- front/dist/index.html | 26 +++++++++++++------------- front/dist/resources/style/style.css | 11 +++++++++-- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/front/dist/index.html b/front/dist/index.html index 02ec0205..68940afe 100644 --- a/front/dist/index.html +++ b/front/dist/index.html @@ -68,19 +68,19 @@
-
-
- - -
-
- - -
-
- - -
+
+
+
+ + +
+
+ + +
+
+ +
diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 413bce71..382a4444 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -79,6 +79,13 @@ video#myCamVideo{ } +.btn-cam-action { + position: absolute; + bottom: 0px; + right: 0px; + width: 450px; + height: 150px; +} /*btn animation*/ .btn-cam-action div{ cursor: pointer; @@ -93,7 +100,7 @@ video#myCamVideo{ transition-timing-function: ease-in-out; bottom: 20px; } -#activeCam:hover .btn-cam-action div{ +.btn-cam-action:hover div{ transform: translateY(0); } .btn-cam-action div:hover{ @@ -106,7 +113,7 @@ video#myCamVideo{ right: 44px; } .btn-video{ - transition: all .2s; + transition: all .25s; right: 134px; } .btn-monitor{ From 91f422d0c34283fe42add59564ed2172adcc2c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 21 Aug 2020 22:53:17 +0200 Subject: [PATCH 34/51] Fixing stop of stream in bi-directional screen sharing. --- front/src/WebRtc/MediaManager.ts | 54 ++++++++++++++++++++------- front/src/WebRtc/ScreenSharingPeer.ts | 33 +++++++++++++--- front/src/WebRtc/SimplePeer.ts | 45 ++++++++++++++-------- 3 files changed, 98 insertions(+), 34 deletions(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 63814ee4..314fea14 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -1,5 +1,5 @@ -import * as SimplePeerNamespace from "simple-peer"; import {DivImportance, layoutManager} from "./LayoutManager"; +import {HtmlUtils} from "./HtmlUtils"; const videoConstraint: boolean|MediaTrackConstraints = { width: { ideal: 1280 }, @@ -8,7 +8,8 @@ const videoConstraint: boolean|MediaTrackConstraints = { }; type UpdatedLocalStreamCallback = (media: MediaStream) => void; -type UpdatedScreenSharingCallback = (media: MediaStream) => void; +type StartScreenSharingCallback = (media: MediaStream) => void; +type StopScreenSharingCallback = (media: MediaStream) => void; // TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only) // TODO: verify that microphone event listeners are not triggered plenty of time NOW (since MediaManager is created many times!!!!) @@ -29,7 +30,8 @@ export class MediaManager { video: videoConstraint }; updatedLocalStreamCallBacks : Set = new Set(); - updatedScreenSharingCallBacks : Set = new Set(); + startScreenSharingCallBacks : Set = new Set(); + stopScreenSharingCallBacks : Set = new Set(); constructor() { @@ -87,9 +89,14 @@ export class MediaManager { this.updatedLocalStreamCallBacks.add(callback); } - public onUpdateScreenSharing(callback: UpdatedScreenSharingCallback): void { + public onStartScreenSharing(callback: StartScreenSharingCallback): void { - this.updatedScreenSharingCallBacks.add(callback); + this.startScreenSharingCallBacks.add(callback); + } + + public onStopScreenSharing(callback: StopScreenSharingCallback): void { + + this.stopScreenSharingCallBacks.add(callback); } removeUpdateLocalStreamEventListener(callback: UpdatedLocalStreamCallback): void { @@ -102,8 +109,14 @@ export class MediaManager { } } - private triggerUpdatedScreenSharingCallbacks(stream: MediaStream): void { - for (const callback of this.updatedScreenSharingCallBacks) { + private triggerStartedScreenSharingCallbacks(stream: MediaStream): void { + for (const callback of this.startScreenSharingCallBacks) { + callback(stream); + } + } + + private triggerStoppedScreenSharingCallbacks(stream: MediaStream): void { + for (const callback of this.stopScreenSharingCallBacks) { callback(stream); } } @@ -164,20 +177,26 @@ export class MediaManager { this.monitorClose.style.display = "none"; this.monitor.style.display = "block"; this.getScreenMedia().then((stream) => { - this.triggerUpdatedScreenSharingCallbacks(stream); + this.triggerStartedScreenSharingCallbacks(stream); }); } private disableScreenSharing() { this.monitorClose.style.display = "block"; this.monitor.style.display = "none"; + this.removeActiveScreenSharingVideo('me'); this.localScreenCapture?.getTracks().forEach((track: MediaStreamTrack) => { track.stop(); }); - this.localScreenCapture = null; + if (this.localScreenCapture === null) { + console.warn('Weird: trying to remove a screen sharing that is not enabled'); + return; + } + const localScreenCapture = this.localScreenCapture; this.getCamera().then((stream) => { - this.triggerUpdatedScreenSharingCallbacks(stream); + this.triggerStoppedScreenSharingCallbacks(localScreenCapture); }); + this.localScreenCapture = null; } //get screen @@ -194,6 +213,9 @@ export class MediaManager { }; } + this.addScreenSharingActiveVideo('me', DivImportance.Normal); + HtmlUtils.getElementByIdOrFail('screen-sharing-me').srcObject = stream; + return stream; }) .catch((err: unknown) => { @@ -306,8 +328,8 @@ export class MediaManager { * * @param userId */ - addScreenSharingActiveVideo(userId : string){ - this.webrtcInAudio.play(); + addScreenSharingActiveVideo(userId : string, divImportance: DivImportance = DivImportance.Important){ + //this.webrtcInAudio.play(); userId = `screen-sharing-${userId}`; const html = ` @@ -316,7 +338,7 @@ export class MediaManager {
`; - layoutManager.add(DivImportance.Important, userId, html); + layoutManager.add(divImportance, userId, html); this.remoteVideo.set(userId, this.getElementByIdOrFail(userId)); } @@ -389,6 +411,12 @@ export class MediaManager { remoteVideo.srcObject = stream; } 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 + const remoteVideo = this.remoteVideo.get(`screen-sharing-${userId}`); + if (remoteVideo === undefined) { + this.addScreenSharingActiveVideo(userId); + } + this.addStreamRemoteVideo(`screen-sharing-${userId}`, stream); } diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index 3ce3c409..35f43201 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -8,6 +8,11 @@ const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); * A peer connection used to transmit video / audio signals between 2 peers. */ export class ScreenSharingPeer extends Peer { + /** + * Whether this connection is currently receiving a video stream from a remote user. + */ + private isReceivingStream:boolean = false; + constructor(private userId: string, initiator: boolean, private connection: Connection) { super({ initiator: initiator ? initiator : false, @@ -35,13 +40,20 @@ export class ScreenSharingPeer extends Peer { this.stream(stream); }); - /*this.on('track', (track: MediaStreamTrack, stream: MediaStream) => { - });*/ - this.on('close', () => { this.destroy(); }); + this.on('data', (chunk: Buffer) => { + // We unfortunately need to rely on an event to let the other party know a stream has stopped. + // It seems there is no native way to detect that. + const message = JSON.parse(chunk.toString('utf8')); + if (message.streamEnded !== true) { + console.error('Unexpected message on screen sharing peer connection'); + } + mediaManager.removeActiveScreenSharingVideo(this.userId); + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any this.on('error', (err: any) => { console.error(`screen sharing error => ${this.userId} => ${err.code}`, err); @@ -74,11 +86,17 @@ export class ScreenSharingPeer extends Peer { console.log(`stream => ${this.userId} => `, stream); if(!stream){ mediaManager.removeActiveScreenSharingVideo(this.userId); + this.isReceivingStream = false; } else { mediaManager.addStreamRemoteScreenSharing(this.userId, stream); + this.isReceivingStream = true; } } + public isReceivingScreenSharingStream(): boolean { + return this.isReceivingStream; + } + public destroy(error?: Error): void { try { mediaManager.removeActiveScreenSharingVideo(this.userId); @@ -98,9 +116,12 @@ export class ScreenSharingPeer extends Peer { return; } - for (const track of localScreenCapture.getTracks()) { - this.addTrack(track, localScreenCapture); - } + this.addStream(localScreenCapture); return; } + + public stopPushingScreenSharingToRemoteUser(stream: MediaStream) { + this.removeStream(stream); + this.write(new Buffer(JSON.stringify({streamEnded: true}))); + } } diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 02573273..3acd65c5 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -34,6 +34,7 @@ export class SimplePeer { private PeerConnectionArray: Map = new Map(); private readonly sendLocalVideoStreamCallback: (media: MediaStream) => void; private readonly sendLocalScreenSharingStreamCallback: (media: MediaStream) => void; + private readonly stopLocalScreenSharingStreamCallback: (media: MediaStream) => void; private readonly peerConnectionListeners: Array = new Array(); constructor(Connection: Connection, WebRtcRoomId: string = "test-webrtc") { @@ -42,8 +43,10 @@ export class SimplePeer { // We need to go through this weird bound function pointer in order to be able to "free" this reference later. this.sendLocalVideoStreamCallback = this.sendLocalVideoStream.bind(this); this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this); + this.stopLocalScreenSharingStreamCallback = this.stopLocalScreenSharingStream.bind(this); mediaManager.onUpdateLocalStream(this.sendLocalVideoStreamCallback); - mediaManager.onUpdateScreenSharing(this.sendLocalScreenSharingStreamCallback); + mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback); + mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback); this.initialise(); } @@ -332,14 +335,22 @@ export class SimplePeer { * Triggered locally when clicking on the screen sharing button */ public sendLocalScreenSharingStream() { - if (mediaManager.localScreenCapture) { - for (const user of this.Users) { - this.sendLocalScreenSharingStreamToUser(user.userId); - } - } else { - for (const user of this.Users) { - this.stopLocalScreenSharingStreamToUser(user.userId); - } + if (!mediaManager.localScreenCapture) { + console.error('Could not find localScreenCapture to share') + return; + } + + for (const user of this.Users) { + this.sendLocalScreenSharingStreamToUser(user.userId); + } + } + + /** + * Triggered locally when clicking on the screen sharing button + */ + public stopLocalScreenSharingStream(stream: MediaStream) { + for (const user of this.Users) { + this.stopLocalScreenSharingStreamToUser(user.userId, stream); } } @@ -360,17 +371,21 @@ export class SimplePeer { } } - private stopLocalScreenSharingStreamToUser(userId: string): void { + private stopLocalScreenSharingStreamToUser(userId: string, stream: MediaStream): void { const PeerConnectionScreenSharing = this.PeerScreenSharingConnectionArray.get(userId); if (!PeerConnectionScreenSharing) { throw new Error('Weird, screen sharing connection to user ' + userId + 'not found') } console.log("updatedScreenSharing => destroy", PeerConnectionScreenSharing); - // FIXME: maybe we don't want to destroy the connexion if it is used in the other way around! - // FIXME: maybe we don't want to destroy the connexion if it is used in the other way around! - // FIXME: maybe we don't want to destroy the connexion if it is used in the other way around! - PeerConnectionScreenSharing.destroy(); - this.PeerScreenSharingConnectionArray.delete(userId); + + // Stop sending stream and close peer connection if peer is not sending stream too + PeerConnectionScreenSharing.stopPushingScreenSharingToRemoteUser(stream); + + if (!PeerConnectionScreenSharing.isReceivingScreenSharingStream()) { + PeerConnectionScreenSharing.destroy(); + + this.PeerScreenSharingConnectionArray.delete(userId); + } } } From b7c2f8be7bfae6c8a41979c2ea6df7c082b4ab67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sat, 22 Aug 2020 15:26:40 +0200 Subject: [PATCH 35/51] Adding colors for cam/mic/screen share button Microphone and camera are now red when disabled. Screen-share is green when enabled. Also, they are now always partially visible (they were completely hidden previously until hovering in the lower right corner) --- front/dist/index.html | 6 +++--- front/dist/resources/style/style.css | 8 +++++++- front/src/WebRtc/MediaManager.ts | 12 ++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/front/dist/index.html b/front/dist/index.html index 68940afe..5de00b3b 100644 --- a/front/dist/index.html +++ b/front/dist/index.html @@ -70,15 +70,15 @@
-
+
-
+
-
+
diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 382a4444..c5a3cc67 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -96,10 +96,16 @@ video#myCamVideo{ background: #666; box-shadow: 2px 2px 24px #444; border-radius: 48px; - transform: translateY(12vh); + transform: translateY(40px); transition-timing-function: ease-in-out; bottom: 20px; } +.btn-cam-action div.disabled { + background: #d75555; +} +.btn-cam-action div.enabled { + background: #73c973; +} .btn-cam-action:hover div{ transform: translateY(0); } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 314fea14..a043e51e 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -32,6 +32,9 @@ export class MediaManager { updatedLocalStreamCallBacks : Set = new Set(); startScreenSharingCallBacks : Set = new Set(); stopScreenSharingCallBacks : Set = new Set(); + private microphoneBtn: HTMLDivElement; + private cinemaBtn: HTMLDivElement; + private monitorBtn: HTMLDivElement; constructor() { @@ -40,6 +43,7 @@ export class MediaManager { this.webrtcInAudio = this.getElementByIdOrFail('audio-webrtc-in'); this.webrtcInAudio.volume = 0.2; + this.microphoneBtn = this.getElementByIdOrFail('btn-micro'); this.microphoneClose = this.getElementByIdOrFail('microphone-close'); this.microphoneClose.style.display = "none"; this.microphoneClose.addEventListener('click', (e: MouseEvent) => { @@ -54,6 +58,7 @@ export class MediaManager { //update tracking }); + this.cinemaBtn = this.getElementByIdOrFail('btn-video'); this.cinemaClose = this.getElementByIdOrFail('cinema-close'); this.cinemaClose.style.display = "none"; this.cinemaClose.addEventListener('click', (e: MouseEvent) => { @@ -68,6 +73,7 @@ export class MediaManager { //update tracking }); + this.monitorBtn = this.getElementByIdOrFail('btn-monitor'); this.monitorClose = this.getElementByIdOrFail('monitor-close'); this.monitorClose.style.display = "block"; this.monitorClose.addEventListener('click', (e: MouseEvent) => { @@ -128,6 +134,7 @@ export class MediaManager { private enableCamera() { this.cinemaClose.style.display = "none"; + this.cinemaBtn.classList.remove("disabled"); this.cinema.style.display = "block"; this.constraintsMedia.video = videoConstraint; this.getCamera().then((stream: MediaStream) => { @@ -138,6 +145,7 @@ export class MediaManager { private disableCamera() { this.cinemaClose.style.display = "block"; this.cinema.style.display = "none"; + this.cinemaBtn.classList.add("disabled"); this.constraintsMedia.video = false; this.myCamVideo.srcObject = null; if (this.localStream) { @@ -153,6 +161,7 @@ export class MediaManager { private enableMicrophone() { this.microphoneClose.style.display = "none"; this.microphone.style.display = "block"; + this.microphoneBtn.classList.remove("disabled"); this.constraintsMedia.audio = true; this.getCamera().then((stream) => { this.triggerUpdatedLocalStreamCallbacks(stream); @@ -162,6 +171,7 @@ export class MediaManager { private disableMicrophone() { this.microphoneClose.style.display = "block"; this.microphone.style.display = "none"; + this.microphoneBtn.classList.add("disabled"); this.constraintsMedia.audio = false; if(this.localStream) { this.localStream.getAudioTracks().forEach((MediaStreamTrack: MediaStreamTrack) => { @@ -176,6 +186,7 @@ export class MediaManager { private enableScreenSharing() { this.monitorClose.style.display = "none"; this.monitor.style.display = "block"; + this.monitorBtn.classList.add("enabled"); this.getScreenMedia().then((stream) => { this.triggerStartedScreenSharingCallbacks(stream); }); @@ -184,6 +195,7 @@ export class MediaManager { private disableScreenSharing() { this.monitorClose.style.display = "block"; this.monitor.style.display = "none"; + this.monitorBtn.classList.remove("enabled"); this.removeActiveScreenSharingVideo('me'); this.localScreenCapture?.getTracks().forEach((track: MediaStreamTrack) => { track.stop(); From 044495cf05bbacb3e403597e6608129d96c01393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 24 Aug 2020 14:19:36 +0200 Subject: [PATCH 36/51] Centering character in free space This commit adds the ability to put the character where there is free space when a discussion is hapening (either in presentation or chat mode) --- front/dist/resources/style/style.css | 6 +- front/src/Phaser/Game/GameScene.ts | 36 ++++++- front/src/WebRtc/LayoutManager.ts | 138 +++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 7 deletions(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index c5a3cc67..8d232fb5 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -317,13 +317,13 @@ body { flex: 0 0 75%; display: flex; justify-content: start; - /*align-items: flex-start;*/ + align-items: flex-start; flex-wrap: wrap; } .main-section > div { - margin: 5%; - flex-basis: 90%; + margin: 2%; + flex-basis: 96%; /*flex-shrink: 2;*/ } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 3c3a6536..431bd3db 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -9,7 +9,7 @@ import { PositionInterface } from "../../Connection"; import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player"; -import {DEBUG_MODE, POSITION_DELAY, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; +import {DEBUG_MODE, POSITION_DELAY, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledTileSet} from "../Map/ITiledMap"; import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character"; import {AddPlayerInterface} from "./AddPlayerInterface"; @@ -22,7 +22,7 @@ import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; import {FourOFourSceneName} from "../Reconnecting/FourOFourScene"; import {loadAllLayers} from "../Entity/body_character"; -import {layoutManager, LayoutMode} from "../../WebRtc/LayoutManager"; +import {CenterListener, layoutManager, LayoutMode} from "../../WebRtc/LayoutManager"; import Texture = Phaser.Textures.Texture; import Sprite = Phaser.GameObjects.Sprite; import CanvasTexture = Phaser.Textures.CanvasTexture; @@ -69,7 +69,7 @@ interface DeleteGroupEventInterface { groupId: string } -export class GameScene extends Phaser.Scene { +export class GameScene extends Phaser.Scene implements CenterListener { GameManager : GameManager; Terrains : Array; CurrentPlayer!: CurrentGamerInterface; @@ -408,6 +408,9 @@ export class GameScene extends Phaser.Scene { this.repositionCallback = this.reposition.bind(this); window.addEventListener('resize', this.repositionCallback); this.reposition(); + + // From now, this game scene will be notified of reposition events + layoutManager.setListener(this); } private switchLayoutMode(): void { @@ -527,7 +530,7 @@ export class GameScene extends Phaser.Scene { //todo: in a dedicated class/function? initCamera() { this.cameras.main.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels); - this.cameras.main.startFollow(this.CurrentPlayer); + this.updateCameraOffset(); this.cameras.main.setZoom(ZOOM_LEVEL); } @@ -874,5 +877,30 @@ export class GameScene extends Phaser.Scene { private reposition(): void { this.presentationModeSprite.setY(this.game.renderer.height - 2); this.chatModeSprite.setY(this.game.renderer.height - 2); + + // Recompute camera offset if needed + this.updateCameraOffset(); + } + + /** + * Updates the offset of the character compared to the center of the screen according to the layout mananger + * (tries to put the character in the center of the reamining space if there is a discussion going on. + */ + private updateCameraOffset(): void { + const array = layoutManager.findBiggestAvailableArray(); + let xCenter = (array.xEnd - array.xStart) / 2 + array.xStart; + let yCenter = (array.yEnd - array.yStart) / 2 + array.yStart; + + // Let's put this in Game coordinates by applying the zoom level: + xCenter /= ZOOM_LEVEL * RESOLUTION; + yCenter /= ZOOM_LEVEL * RESOLUTION; + + //console.log("updateCameraOffset", array, xCenter, yCenter, this.game.renderer.width, this.game.renderer.height); + + this.cameras.main.startFollow(this.CurrentPlayer, true, 1, 1, xCenter - this.game.renderer.width / 2, yCenter - this.game.renderer.height / 2); + } + + public onCenterChange(): void { + this.updateCameraOffset(); } } diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index 6695fe7f..c2bb683e 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -14,6 +14,14 @@ export enum DivImportance { Normal = "Normal", } +/** + * Classes implementing this interface can be notified when the center of the screen (the player position) should be + * changed. + */ +export interface CenterListener { + onCenterChange(): void; +} + /** * This class is in charge of the video-conference layout. * It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode. @@ -23,6 +31,11 @@ class LayoutManager { private importantDivs: Map = new Map(); private normalDivs: Map = new Map(); + private listener: CenterListener|null = null; + + public setListener(centerListener: CenterListener|null) { + this.listener = centerListener; + } public add(importance: DivImportance, userId: string, html: string): void { const div = document.createElement('div'); @@ -45,6 +58,7 @@ class LayoutManager { this.positionDiv(div, importance); this.adjustVideoChatClass(); + this.listener?.onCenterChange(); } private positionDiv(elem: HTMLDivElement, importance: DivImportance): void { @@ -72,6 +86,7 @@ class LayoutManager { div.remove(); this.importantDivs.delete(userId); this.adjustVideoChatClass(); + this.listener?.onCenterChange(); return; } @@ -80,6 +95,7 @@ class LayoutManager { div.remove(); this.normalDivs.delete(userId); this.adjustVideoChatClass(); + this.listener?.onCenterChange(); return; } @@ -123,11 +139,133 @@ class LayoutManager { for (const div of this.normalDivs.values()) { this.positionDiv(div, DivImportance.Normal); } + this.listener?.onCenterChange(); } public getLayoutMode(): LayoutMode { return this.mode; } + + /*public getGameCenter(): {x: number, y: number} { + + }*/ + + /** + * Tries to find the biggest available box of remaining space (this is a space where we can center the character) + */ + public findBiggestAvailableArray(): {xStart: number, yStart: number, xEnd: number, yEnd: number} { + if (this.mode === LayoutMode.VideoChat) { + const children = document.querySelectorAll('div.chat-mode > div'); + const htmlChildren = Array.from(children.values()); + + // No chat? Let's go full center + if (htmlChildren.length === 0) { + return { + xStart: 0, + yStart: 0, + xEnd: window.innerWidth, + yEnd: window.innerHeight + } + } + + const lastDiv = htmlChildren[htmlChildren.length - 1]; + // Compute area between top right of the last div and bottom right of window + const area1 = (window.innerWidth - (lastDiv.offsetLeft + lastDiv.offsetWidth)) + * (window.innerHeight - lastDiv.offsetTop); + + // Compute area between bottom of last div and bottom of the screen on whole width + const area2 = window.innerWidth + * (window.innerHeight - (lastDiv.offsetTop + lastDiv.offsetHeight)); + + if (area1 < 0 && area2 < 0) { + // If screen is full, let's not attempt something foolish and simply center character in the middle. + return { + xStart: 0, + yStart: 0, + xEnd: window.innerWidth, + yEnd: window.innerHeight + } + } + if (area1 <= area2) { + console.log('lastDiv', lastDiv.offsetTop, lastDiv.offsetHeight); + return { + xStart: 0, + yStart: lastDiv.offsetTop + lastDiv.offsetHeight, + xEnd: window.innerWidth, + yEnd: window.innerHeight + } + } else { + console.log('lastDiv', lastDiv.offsetTop); + return { + xStart: lastDiv.offsetLeft + lastDiv.offsetWidth, + yStart: lastDiv.offsetTop, + xEnd: window.innerWidth, + yEnd: window.innerHeight + } + } + } else { + // Possible destinations: at the center bottom or at the right bottom. + const mainSectionChildren = Array.from(document.querySelectorAll('div.main-section > div').values()); + const sidebarChildren = Array.from(document.querySelectorAll('aside.sidebar > div').values()); + + // Nothing? Let's center + if (mainSectionChildren.length === 0 && sidebarChildren.length === 0) { + return { + xStart: 0, + yStart: 0, + xEnd: window.innerWidth, + yEnd: window.innerHeight + } + } + + if (mainSectionChildren.length === 0) { + const lastSidebarDiv = sidebarChildren[sidebarChildren.length-1]; + + // No presentation? Let's center on the main-section space + return { + xStart: 0, + yStart: 0, + xEnd: lastSidebarDiv.offsetLeft, + yEnd: window.innerHeight + } + } + + // At this point, we know we have at least one element in the main section. + const lastPresentationDiv = mainSectionChildren[mainSectionChildren.length-1]; + + const presentationArea = (window.innerHeight - (lastPresentationDiv.offsetTop + lastPresentationDiv.offsetHeight)) + * (lastPresentationDiv.offsetLeft + lastPresentationDiv.offsetWidth); + + let leftSideBar: number; + let bottomSideBar: number; + if (sidebarChildren.length === 0) { + leftSideBar = HtmlUtils.getElementByIdOrFail('sidebar').offsetLeft; + bottomSideBar = 0; + } else { + const lastSideBarChildren = sidebarChildren[sidebarChildren.length - 1]; + leftSideBar = lastSideBarChildren.offsetLeft; + bottomSideBar = lastSideBarChildren.offsetTop + lastSideBarChildren.offsetHeight; + } + const sideBarArea = (window.innerWidth - leftSideBar) + * (window.innerHeight - bottomSideBar); + + if (presentationArea <= sideBarArea) { + return { + xStart: leftSideBar, + yStart: bottomSideBar, + xEnd: window.innerWidth, + yEnd: window.innerHeight + } + } else { + return { + xStart: 0, + yStart: lastPresentationDiv.offsetTop + lastPresentationDiv.offsetHeight, + xEnd: /*lastPresentationDiv.offsetLeft + lastPresentationDiv.offsetWidth*/ window.innerWidth , // To avoid flickering when a chat start, we center on the center of the screen, not the center of the main content area + yEnd: window.innerHeight + } + } + } + } } const layoutManager = new LayoutManager(); From 7f5f802b86c7101a2567b2f61d74559061646db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 24 Aug 2020 18:23:02 +0200 Subject: [PATCH 37/51] Avoiding flickering when entering presentation mode with no presentation --- front/src/WebRtc/LayoutManager.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index c2bb683e..575b0bb2 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -208,8 +208,8 @@ class LayoutManager { const mainSectionChildren = Array.from(document.querySelectorAll('div.main-section > div').values()); const sidebarChildren = Array.from(document.querySelectorAll('aside.sidebar > div').values()); - // Nothing? Let's center - if (mainSectionChildren.length === 0 && sidebarChildren.length === 0) { + // No presentation? Let's center on the screen + if (mainSectionChildren.length === 0) { return { xStart: 0, yStart: 0, @@ -218,18 +218,6 @@ class LayoutManager { } } - if (mainSectionChildren.length === 0) { - const lastSidebarDiv = sidebarChildren[sidebarChildren.length-1]; - - // No presentation? Let's center on the main-section space - return { - xStart: 0, - yStart: 0, - xEnd: lastSidebarDiv.offsetLeft, - yEnd: window.innerHeight - } - } - // At this point, we know we have at least one element in the main section. const lastPresentationDiv = mainSectionChildren[mainSectionChildren.length-1]; From 13272968cfdd339441e877bbef2e3a9689a7d389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 27 Aug 2020 10:09:47 +0200 Subject: [PATCH 38/51] Clicking on a video puts it in presentation mode Adding the ability to put a video in presentation mode by clicking on it. Also, adding small CSS animations on hover. --- front/dist/resources/style/style.css | 28 +++++++++++++++- front/src/WebRtc/LayoutManager.ts | 50 ++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 8d232fb5..2dbba223 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -256,6 +256,10 @@ body { .sidebar > div { max-height: 21%; } + + .sidebar > div:hover { + max-height: 25%; + } } @media (max-aspect-ratio: 1/1) { .main-container { @@ -274,6 +278,10 @@ body { .sidebar > div { max-width: 21%; } + + .sidebar > div:hover { + max-width: 25%; + } } .game { @@ -324,9 +332,16 @@ body { .main-section > div { margin: 2%; flex-basis: 96%; + transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, flex-basis 0.2s; + cursor: pointer; /*flex-shrink: 2;*/ } +.main-section > div:hover { + margin: 0%; + flex-basis: 100%; +} + .sidebar { flex: 0 0 25%; display: flex; @@ -334,6 +349,12 @@ body { .sidebar > div { margin: 2%; + transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, max-height 0.2s, max-width 0.2s; + cursor: pointer; +} + +.sidebar > div:hover { + margin: 0%; } /* Let's make sure videos are vertically centered if they need to be cropped */ @@ -354,11 +375,16 @@ body { padding: 1%; } -.chat-mode div { +.chat-mode > div { margin: 1%; max-height: 96%; + transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s; + cursor: pointer; } +.chat-mode > div:hover { + margin: 0%; +} .chat-mode.one-col > div { flex-basis: 98%; } diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index 575b0bb2..02818c78 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -42,6 +42,14 @@ class LayoutManager { div.innerHTML = html; div.id = "user-"+userId; div.className = "media-container" + div.onclick = () => { + const parentId = div.parentElement?.id; + if (parentId === 'sidebar' || parentId === 'chat-mode') { + this.focusOn(userId); + } else { + this.removeFocusOn(userId); + } + } if (importance === DivImportance.Important) { this.importantDivs.set(userId, div); @@ -76,6 +84,48 @@ class LayoutManager { } } + /** + * Put the screen in presentation mode and move elem in presentation mode (and all other videos in normal mode) + */ + private focusOn(userId: string): void { + const focusedDiv = this.getDivByUserId(userId); + for (const [importantUserId, importantDiv] of this.importantDivs.entries()) { + //this.positionDiv(importantDiv, DivImportance.Normal); + this.importantDivs.delete(importantUserId); + this.normalDivs.set(importantUserId, importantDiv); + } + this.normalDivs.delete(userId); + this.importantDivs.set(userId, focusedDiv); + //this.positionDiv(focusedDiv, DivImportance.Important); + this.switchLayoutMode(LayoutMode.Presentation); + } + + /** + * Removes userId from presentation mode + */ + private removeFocusOn(userId: string): void { + const importantDiv = this.importantDivs.get(userId); + if (importantDiv === undefined) { + throw new Error('Div with user id "'+userId+'" is not in important mode'); + } + this.normalDivs.set(userId, importantDiv); + this.importantDivs.delete(userId); + + this.positionDiv(importantDiv, DivImportance.Normal); + } + + private getDivByUserId(userId: string): HTMLDivElement { + let div = this.importantDivs.get(userId); + if (div !== undefined) { + return div; + } + div = this.normalDivs.get(userId); + if (div !== undefined) { + return div; + } + throw new Error('Could not find media with user id '+userId); + } + /** * Removes the DIV matching userId. */ From 168697eb461e94078a7b8fb2c75cf51b412ce603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sun, 30 Aug 2020 15:44:22 +0200 Subject: [PATCH 39/51] Adding a GameMap class that helps tracking when the properties of the tiles the user is changes (when the user moves) --- front/src/Phaser/Game/GameMap.ts | 97 ++++++++++++++++++++++++++++++ front/src/Phaser/Game/GameScene.ts | 10 +++ 2 files changed, 107 insertions(+) create mode 100644 front/src/Phaser/Game/GameMap.ts diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts new file mode 100644 index 00000000..0e99159b --- /dev/null +++ b/front/src/Phaser/Game/GameMap.ts @@ -0,0 +1,97 @@ +import {ITiledMap} from "../Map/ITiledMap"; + +export type PropertyChangeCallback = (oldValue: string | number | boolean | undefined, newValue: string | number | boolean | undefined) => void; + +/** + * A wrapper around a ITiledMap interface to provide additional capabilities. + * It is used to handle layer properties. + */ +export class GameMap { + private key: number|undefined; + private lastProperties = new Map(); + private callbacks = new Map>(); + + public constructor(private map: ITiledMap) { + } + + /** + * Sets the position of the current player (in pixels) + * This will trigger events if properties are changing. + */ + public setPosition(x: number, y: number) { + const xMap = Math.floor(x / this.map.tilewidth); + const yMap = Math.floor(y / this.map.tileheight); + const key = xMap + yMap * this.map.width; + if (key === this.key) { + return; + } + this.key = key; + + const newProps = this.getProperties(key); + const oldProps = this.lastProperties; + + // Let's compare the 2 maps: + // First new properties vs oldProperties + for (const [newPropName, newPropValue] of newProps.entries()) { + const oldPropValue = oldProps.get(newPropName); + if (oldPropValue !== newPropValue) { + this.trigger(newPropName, oldPropValue, newPropValue); + } + } + + for (const [oldPropName, oldPropValue] of oldProps.entries()) { + if (!newProps.has(oldPropName)) { + // We found a property that disappeared + this.trigger(oldPropName, oldPropValue, undefined); + } + } + + this.lastProperties = newProps; + } + + private getProperties(key: number): Map { + const properties = new Map(); + + for (const layer of this.map.layers) { + if (layer.type !== 'tilelayer') { + continue; + } + const tiles = layer.data as number[]; + if (tiles[key] == 0) { + continue; + } + // There is a tile in this layer, let's embed the properties + if (layer.properties !== undefined) { + for (const layerProperty of layer.properties) { + if (layerProperty.value === undefined) { + continue; + } + properties.set(layerProperty.name, layerProperty.value); + } + } + } + + return properties; + } + + private trigger(propName: string, oldValue: string | number | boolean | undefined, newValue: string | number | boolean | undefined) { + let callbacksArray = this.callbacks.get(propName); + if (callbacksArray !== undefined) { + for (const callback of callbacksArray) { + callback(oldValue, newValue); + } + } + } + + /** + * Registers a callback called when the user moves to a tile where the property propName is different from the last tile the user was on. + */ + public onPropertyChange(propName: string, callback: PropertyChangeCallback) { + let callbacksArray = this.callbacks.get(propName); + if (callbacksArray === undefined) { + callbacksArray = new Array(); + this.callbacks.set(propName, callbacksArray); + } + callbacksArray.push(callback); + } +} diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 431bd3db..1e394b96 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -28,6 +28,7 @@ import Sprite = Phaser.GameObjects.Sprite; import CanvasTexture = Phaser.Textures.CanvasTexture; import GameObject = Phaser.GameObjects.GameObject; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; +import {GameMap} from "./GameMap"; export enum Textures { @@ -109,6 +110,7 @@ export class GameScene extends Phaser.Scene implements CenterListener { private presentationModeSprite!: Sprite; private chatModeSprite!: Sprite; private repositionCallback!: (this: Window, ev: UIEvent) => void; + private gameMap!: GameMap; static createFromUrl(mapUrlFile: string, instance: string, key: string|null = null): GameScene { const mapKey = GameScene.getMapKeyByUrl(mapUrlFile); @@ -278,6 +280,7 @@ export class GameScene extends Phaser.Scene implements CenterListener { create(): void { //initalise map this.Map = this.add.tilemap(this.MapKey); + this.gameMap = new GameMap(this.mapFile); const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/')); this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => { this.Terrains.push(this.Map.addTilesetImage(tileset.name, `${mapDirUrl}/${tileset.image}`, tileset.tilewidth, tileset.tileheight, tileset.margin, tileset.spacing/*, tileset.firstgid*/)); @@ -411,6 +414,10 @@ export class GameScene extends Phaser.Scene implements CenterListener { // From now, this game scene will be notified of reposition events layoutManager.setListener(this); + + this.gameMap.onPropertyChange('startLayer', (oldValue, newValue) => { + console.log('startLayer', oldValue, newValue); + }); } private switchLayoutMode(): void { @@ -589,6 +596,9 @@ export class GameScene extends Phaser.Scene implements CenterListener { //listen event to share position of user this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this)) + this.CurrentPlayer.on(hasMovedEventName, (event: HasMovedEvent) => { + this.gameMap.setPosition(event.x, event.y); + }) }); } From 01319b50ca501eea81625d0886fa1382208ba336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sun, 30 Aug 2020 17:37:38 +0200 Subject: [PATCH 40/51] Adding a "openWebsite" property that opens websites when we walk over the zone. --- back/src/Assets/Maps/Floor0/floor0.json | 20 +++++++++++++++++++- front/src/Phaser/Game/GameMap.ts | 4 ++-- front/src/Phaser/Game/GameScene.ts | 9 +++++++-- website/dist/create-map.html | 12 ++++++++---- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/back/src/Assets/Maps/Floor0/floor0.json b/back/src/Assets/Maps/Floor0/floor0.json index 987004e6..21d4c52c 100644 --- a/back/src/Assets/Maps/Floor0/floor0.json +++ b/back/src/Assets/Maps/Floor0/floor0.json @@ -63,6 +63,24 @@ "x":0, "y":0 }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":34, + "id":18, + "name":"openSwile", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"https:\/\/app.swile.co\/" + }], + "type":"tilelayer", + "visible":true, + "width":46, + "x":0, + "y":0 + }, { "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "height":34, @@ -224,7 +242,7 @@ "x":0, "y":0 }], - "nextlayerid":18, + "nextlayerid":19, "nextobjectid":1, "orientation":"orthogonal", "renderorder":"right-down", diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 0e99159b..769f184e 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -1,6 +1,6 @@ import {ITiledMap} from "../Map/ITiledMap"; -export type PropertyChangeCallback = (oldValue: string | number | boolean | undefined, newValue: string | number | boolean | undefined) => void; +export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined) => void; /** * A wrapper around a ITiledMap interface to provide additional capabilities. @@ -78,7 +78,7 @@ export class GameMap { let callbacksArray = this.callbacks.get(propName); if (callbacksArray !== undefined) { for (const callback of callbacksArray) { - callback(oldValue, newValue); + callback(newValue, oldValue); } } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 1e394b96..8f9940c7 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -29,6 +29,7 @@ import CanvasTexture = Phaser.Textures.CanvasTexture; import GameObject = Phaser.GameObjects.GameObject; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import {GameMap} from "./GameMap"; +import {CoWebsiteManager} from "../../WebRtc/CoWebsiteManager"; export enum Textures { @@ -415,8 +416,12 @@ export class GameScene extends Phaser.Scene implements CenterListener { // From now, this game scene will be notified of reposition events layoutManager.setListener(this); - this.gameMap.onPropertyChange('startLayer', (oldValue, newValue) => { - console.log('startLayer', oldValue, newValue); + this.gameMap.onPropertyChange('openWebsite', (newValue, oldValue) => { + if (newValue === undefined) { + CoWebsiteManager.closeCoWebsite(); + } else { + CoWebsiteManager.loadCoWebsite(newValue as string); + } }); } diff --git a/website/dist/create-map.html b/website/dist/create-map.html index 6df98e52..e6037a9c 100644 --- a/website/dist/create-map.html +++ b/website/dist/create-map.html @@ -150,10 +150,14 @@
  • You can of course use the "#" notation in an exit scene URL (so an exit scene URL will point to a given entry scene URL)
  • - - - - +

    Opening a website when walking on the map

    +

    On your map, you can define special zones. When a player will pass over these zones, a website will open + (as an iframe on the right side of the screen)

    +

    In order to create a zone that opens websites:

    +
      +
    • You must create a specific layer.
    • +
    • In layer properties, you MUST add a boolean "openWebsite" property. The value of the property is the URL of the website to open (the URL must start with "https://")
    • +

    Pushing the map

    When your changes are ready, you need to "commit" and "push" (i.e. "upload") the changes back to GitHub. From a128ff117b3f09eb0ba5c49b7260282d4e120cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sun, 30 Aug 2020 17:40:04 +0200 Subject: [PATCH 41/51] code style --- front/src/Phaser/Game/GameMap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 769f184e..a588a4e6 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -75,7 +75,7 @@ export class GameMap { } private trigger(propName: string, oldValue: string | number | boolean | undefined, newValue: string | number | boolean | undefined) { - let callbacksArray = this.callbacks.get(propName); + const callbacksArray = this.callbacks.get(propName); if (callbacksArray !== undefined) { for (const callback of callbacksArray) { callback(newValue, oldValue); From 0a8ba3704992de4ba7d876c699025c89ba7bae10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 31 Aug 2020 12:18:00 +0200 Subject: [PATCH 42/51] Adding Jitsi meet support --- back/src/Assets/Maps/Floor0/floor0.json | 20 +++++++++++++++++- deeployer.libsonnet | 3 ++- docker-compose.yaml | 1 + front/src/Enum/EnvironmentVariable.ts | 4 +++- front/src/Phaser/Game/GameScene.ts | 28 ++++++++++++++++++++++++- front/src/WebRtc/CoWebsiteManager.ts | 21 +++++++++++++++++-- front/src/index.ts | 9 +++++++- front/webpack.config.js | 2 +- website/dist/create-map.html | 9 ++++++++ 9 files changed, 89 insertions(+), 8 deletions(-) diff --git a/back/src/Assets/Maps/Floor0/floor0.json b/back/src/Assets/Maps/Floor0/floor0.json index 21d4c52c..bf80c6e3 100644 --- a/back/src/Assets/Maps/Floor0/floor0.json +++ b/back/src/Assets/Maps/Floor0/floor0.json @@ -81,6 +81,24 @@ "x":0, "y":0 }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":34, + "id":19, + "name":"jitsyAmphi", + "opacity":1, + "properties":[ + { + "name":"jitsiRoom", + "type":"string", + "value":"tcm-amphi" + }], + "type":"tilelayer", + "visible":true, + "width":46, + "x":0, + "y":0 + }, { "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 294, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "height":34, @@ -242,7 +260,7 @@ "x":0, "y":0 }], - "nextlayerid":19, + "nextlayerid":20, "nextobjectid":1, "orientation":"orthogonal", "renderorder":"right-down", diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 09074148..528342ed 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -25,7 +25,8 @@ }, "ports": [80], "env": { - "API_URL": "api."+url + "API_URL": "api."+url, + "JITSI_URL": "meet.jit.si" } }, "website": { diff --git a/docker-compose.yaml b/docker-compose.yaml index 74bbafbf..d944e0f8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -22,6 +22,7 @@ services: image: thecodingmachine/nodejs:12 environment: DEBUG_MODE: "$DEBUG_MODE" + JITSI_URL: $JITSI_URL HOST: "0.0.0.0" NODE_ENV: development API_URL: api.workadventure.localhost diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index e35818bc..97a3f91b 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -1,5 +1,6 @@ const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true"; const API_URL = (typeof(window) !== 'undefined' ? window.location.protocol : 'http:') + '//' + (process.env.API_URL || "api.workadventure.localhost"); +const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL; const RESOLUTION = 3; const ZOOM_LEVEL = 1/*3/4*/; const POSITION_DELAY = 200; // Wait 200ms between sending position events @@ -11,5 +12,6 @@ export { RESOLUTION, ZOOM_LEVEL, POSITION_DELAY, - MAX_EXTRAPOLATION_TIME + MAX_EXTRAPOLATION_TIME, + JITSI_URL } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 8f9940c7..485b7dce 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -9,7 +9,7 @@ import { PositionInterface } from "../../Connection"; import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player"; -import {DEBUG_MODE, POSITION_DELAY, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; +import {DEBUG_MODE, JITSI_URL, POSITION_DELAY, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledTileSet} from "../Map/ITiledMap"; import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character"; import {AddPlayerInterface} from "./AddPlayerInterface"; @@ -423,6 +423,32 @@ export class GameScene extends Phaser.Scene implements CenterListener { CoWebsiteManager.loadCoWebsite(newValue as string); } }); + let jitsiApi: any; + this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue) => { + if (newValue === undefined) { + jitsiApi?.dispose(); + CoWebsiteManager.closeCoWebsite(); + } else { + CoWebsiteManager.insertCoWebsite((cowebsiteDiv => { + const domain = JITSI_URL; + const options = { + roomName: this.instance + "-" + newValue, + width: "100%", + height: "100%", + parentNode: cowebsiteDiv, + configOverwrite: { + prejoinPageEnabled: false + }, + interfaceConfigOverwrite: { + SHOW_CHROME_EXTENSION_BANNER: false, + MOBILE_APP_PROMO: false + } + }; + jitsiApi = new (window as any).JitsiMeetExternalAPI(domain, options); + jitsiApi.executeCommand('displayName', gameManager.getPlayerName()); + })) + } + }); } private switchLayoutMode(): void { diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 0150760c..1793335b 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -14,7 +14,24 @@ export class CoWebsiteManager { iframe.id = 'cowebsite-iframe'; iframe.src = url; cowebsiteDiv.appendChild(iframe); + //iframe.onload = () => { + // onload can be long to trigger. Maybe we should display the website, whatever happens, after 1 second? + CoWebsiteManager.fire(); + //} + } + + /** + * Just like loadCoWebsite but the div can be filled by the user. + */ + public static insertCoWebsite(callback: (cowebsite: HTMLDivElement) => void): void { + const cowebsiteDiv = HtmlUtils.getElementByIdOrFail("cowebsite"); + cowebsiteDiv.innerHTML = ''; + + callback(cowebsiteDiv); + //iframe.onload = () => { + // onload can be long to trigger. Maybe we should display the website, whatever happens, after 1 second? CoWebsiteManager.fire(); + //} } public static closeCoWebsite(): void { @@ -24,8 +41,8 @@ export class CoWebsiteManager { } public static getGameSize(): {width: number, height: number} { - const iframe = document.getElementById('cowebsite-iframe'); - if (iframe === null) { + const hasChildren = HtmlUtils.getElementByIdOrFail("cowebsite").children.length > 0; + if (hasChildren === false) { return { width: window.innerWidth, height: window.innerHeight diff --git a/front/src/index.ts b/front/src/index.ts index 75ad0fe6..5c198f9f 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -1,6 +1,6 @@ import 'phaser'; import GameConfig = Phaser.Types.Core.GameConfig; -import {DEBUG_MODE, RESOLUTION} from "./Enum/EnvironmentVariable"; +import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable"; import {cypressAsserter} from "./Cypress/CypressAsserter"; import {LoginScene} from "./Phaser/Login/LoginScene"; import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene"; @@ -13,6 +13,13 @@ import {CoWebsiteManager} from "./WebRtc/CoWebsiteManager"; //CoWebsiteManager.loadCoWebsite('https://thecodingmachine.com'); +// Load Jitsi if the environment variable is set. +if (JITSI_URL) { + const jitsiScript = document.createElement('script'); + jitsiScript.src = 'https://' + JITSI_URL + '/external_api.js'; + document.head.appendChild(jitsiScript); +} + const {width, height} = CoWebsiteManager.getGameSize(); const config: GameConfig = { diff --git a/front/webpack.config.js b/front/webpack.config.js index 61424eeb..7ffbcfcc 100644 --- a/front/webpack.config.js +++ b/front/webpack.config.js @@ -42,7 +42,7 @@ module.exports = { new webpack.ProvidePlugin({ Phaser: 'phaser' }), - new webpack.EnvironmentPlugin(['API_URL', 'DEBUG_MODE']) + new webpack.EnvironmentPlugin(['API_URL', 'DEBUG_MODE', 'JITSI_URL']) ], }; diff --git a/website/dist/create-map.html b/website/dist/create-map.html index e6037a9c..f1a4f7b8 100644 --- a/website/dist/create-map.html +++ b/website/dist/create-map.html @@ -159,6 +159,15 @@

  • In layer properties, you MUST add a boolean "openWebsite" property. The value of the property is the URL of the website to open (the URL must start with "https://")
  • +

    Opening a Jitsi meet when walking on the map

    +

    On your map, you can define special zones (meeting rooms) that will trigger the opening of a Jitsi meet. When a player will pass over these zones, a Jitsi meet will open + (as an iframe on the right side of the screen)

    +

    In order to create Jitsi meet zones:

    +
      +
    • You must create a specific layer.
    • +
    • In layer properties, you MUST add a boolean "jitsiRoom" property. The value of the property is the name of the room in Jitsi. Note: the name of the room will be prepended with the name of the instance of the map (so that different instances of the map have different rooms)
    • +
    +

    Pushing the map

    When your changes are ready, you need to "commit" and "push" (i.e. "upload") the changes back to GitHub. Just wait a few minutes, and your map will be propagated automatically to the GitHub pages web-server.

    From df7b5cc2e3adbef076cc33fe03373e08766c2509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 31 Aug 2020 14:03:40 +0200 Subject: [PATCH 43/51] Adding a "silent" notion (triggered in Jitsi meets) --- back/src/Controller/IoSocketController.ts | 26 ++++++++++++++++++- back/src/Model/UserInterface.ts | 5 ++-- back/src/Model/World.ts | 31 ++++++++++++++++++++++- front/src/Connection.ts | 5 ++++ front/src/Phaser/Game/GameScene.ts | 8 +++--- 5 files changed, 68 insertions(+), 7 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 1d5bbe06..5f1bc47f 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -33,7 +33,8 @@ enum SockerIoEvent { MESSAGE_ERROR = "message-error", GROUP_CREATE_UPDATE = "group-create-update", GROUP_DELETE = "group-delete", - SET_PLAYER_DETAILS = "set-player-details" + SET_PLAYER_DETAILS = "set-player-details", + SET_SILENT = "set_silent", // Set or unset the silent mode for this user. } export class IoSocketController { @@ -274,6 +275,29 @@ export class IoSocketController { Client.characterLayers = playerDetails.characterLayers; answerFn(Client.userId); }); + + socket.on(SockerIoEvent.SET_SILENT, (silent: unknown) => { + if (typeof silent !== "boolean") { + socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid SET_SILENT message.'}); + console.warn('Invalid SET_SILENT message received: ', silent); + return; + } + + try { + const Client = (socket as ExSocketInterface); + + // update position in the world + const world = this.Worlds.get(Client.roomId); + if (!world) { + console.error("Could not find world with id '", Client.roomId, "'"); + return; + } + world.setSilent(Client, silent); + } catch (e) { + console.error('An error occurred on "SET_SILENT"'); + console.error(e); + } + }); }); } diff --git a/back/src/Model/UserInterface.ts b/back/src/Model/UserInterface.ts index 743f8b4d..89994a31 100644 --- a/back/src/Model/UserInterface.ts +++ b/back/src/Model/UserInterface.ts @@ -4,5 +4,6 @@ import { PointInterface } from "./Websocket/PointInterface"; export interface UserInterface { id: string, group?: Group, - position: PointInterface -} \ No newline at end of file + position: PointInterface, + silent: boolean +} diff --git a/back/src/Model/World.ts b/back/src/Model/World.ts index 6d4fc205..8855702e 100644 --- a/back/src/Model/World.ts +++ b/back/src/Model/World.ts @@ -55,7 +55,8 @@ export class World { public join(socket : Identificable, userPosition: PointInterface): void { this.users.set(socket.userId, { id: socket.userId, - position: userPosition + position: userPosition, + silent: false // FIXME: silent should be set at the correct value when joining a room. }); // Let's call update position to trigger the join / leave room this.updatePosition(socket, userPosition); @@ -84,6 +85,10 @@ export class World { user.position = userPosition; + if (user.silent) { + return; + } + if (typeof user.group === 'undefined') { // If the user is not part of a group: // should he join a group? @@ -118,6 +123,26 @@ export class World { } } + setSilent(socket: Identificable, silent: boolean) { + const user = this.users.get(socket.userId); + if(typeof user === 'undefined') { + console.warn('In setSilent, could not find user with ID "'+socket.userId+'" in world.'); + return; + } + if (user.silent === silent) { + return; + } + + user.silent = silent; + if (silent && user.group !== undefined) { + this.leaveGroup(user); + } + if (!silent) { + // If we are back to life, let's trigger a position update to see if we can join some group. + this.updatePosition(socket, user.position); + } + } + /** * Makes a user leave a group and closes and destroy the group if the group contains only one remaining person. * @@ -145,6 +170,7 @@ export class World { * Looks for the closest user that is: * - close enough (distance <= minDistance) * - not in a group + * - not silent * OR * - close enough to a group (distance <= groupRadius) */ @@ -160,6 +186,9 @@ export class World { if(currentUser === user) { return; } + if (currentUser.silent) { + return; + } const distance = World.computeDistance(user, currentUser); // compute distance between peers. diff --git a/front/src/Connection.ts b/front/src/Connection.ts index 783b5d41..4a184c52 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -24,6 +24,7 @@ enum EventMessage{ SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id. CONNECT_ERROR = "connect_error", + SET_SILENT = "set_silent", // Set or unset the silent mode for this user. } export interface PointInterface { @@ -167,6 +168,10 @@ export class Connection implements Connection { this.socket.emit(EventMessage.USER_POSITION, point); } + public setSilent(silent: boolean): void { + this.socket.emit(EventMessage.SET_SILENT, silent); + } + public onUserJoins(callback: (message: MessageUserJoined) => void): void { this.socket.on(EventMessage.JOIN_ROOM, callback); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 485b7dce..c30bdf61 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -423,9 +423,10 @@ export class GameScene extends Phaser.Scene implements CenterListener { CoWebsiteManager.loadCoWebsite(newValue as string); } }); - let jitsiApi: any; + let jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue) => { if (newValue === undefined) { + this.connection.setSilent(false); jitsiApi?.dispose(); CoWebsiteManager.closeCoWebsite(); } else { @@ -444,9 +445,10 @@ export class GameScene extends Phaser.Scene implements CenterListener { MOBILE_APP_PROMO: false } }; - jitsiApi = new (window as any).JitsiMeetExternalAPI(domain, options); + jitsiApi = new (window as any).JitsiMeetExternalAPI(domain, options); // eslint-disable-line @typescript-eslint/no-explicit-any jitsiApi.executeCommand('displayName', gameManager.getPlayerName()); - })) + })); + this.connection.setSilent(true); } }); } From 9351719873db6a523dcc5953ab18aabae530158a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 31 Aug 2020 14:10:01 +0200 Subject: [PATCH 44/51] Adding the notion of silent zone --- back/src/Assets/Maps/Floor0/floor0.json | 2 +- front/src/Phaser/Game/GameScene.ts | 8 ++++++++ website/dist/create-map.html | 13 +++++++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/back/src/Assets/Maps/Floor0/floor0.json b/back/src/Assets/Maps/Floor0/floor0.json index bf80c6e3..38bf49e7 100644 --- a/back/src/Assets/Maps/Floor0/floor0.json +++ b/back/src/Assets/Maps/Floor0/floor0.json @@ -260,7 +260,7 @@ "x":0, "y":0 }], - "nextlayerid":20, + "nextlayerid":21, "nextobjectid":1, "orientation":"orthogonal", "renderorder":"right-down", diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index c30bdf61..a17eab22 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -450,6 +450,14 @@ export class GameScene extends Phaser.Scene implements CenterListener { })); this.connection.setSilent(true); } + }) + + this.gameMap.onPropertyChange('silent', (newValue, oldValue) => { + if (newValue === undefined || newValue === false || newValue === '') { + this.connection.setSilent(false); + } else { + this.connection.setSilent(true); + } }); } diff --git a/website/dist/create-map.html b/website/dist/create-map.html index f1a4f7b8..6eb1a266 100644 --- a/website/dist/create-map.html +++ b/website/dist/create-map.html @@ -156,7 +156,7 @@

    In order to create a zone that opens websites:

    • You must create a specific layer.
    • -
    • In layer properties, you MUST add a boolean "openWebsite" property. The value of the property is the URL of the website to open (the URL must start with "https://")
    • +
    • In layer properties, you MUST add a "openWebsite" property (of type "string"). The value of the property is the URL of the website to open (the URL must start with "https://")

    Opening a Jitsi meet when walking on the map

    @@ -165,7 +165,16 @@

    In order to create Jitsi meet zones:

    • You must create a specific layer.
    • -
    • In layer properties, you MUST add a boolean "jitsiRoom" property. The value of the property is the name of the room in Jitsi. Note: the name of the room will be prepended with the name of the instance of the map (so that different instances of the map have different rooms)
    • +
    • In layer properties, you MUST add a boolean "jitsiRoom" property (of type "string"). The value of the property is the name of the room in Jitsi. Note: the name of the room will be prepended with the name of the instance of the map (so that different instances of the map have different rooms)
    • +
    + +

    Making a "silent" zone

    +

    On your map, you can define special silent zones where nobody is allowed to talk. + In these zones, users will not speak to each others, even if they are next to each others.

    +

    In order to create a silent zone:

    +
      +
    • You must create a specific layer.
    • +
    • In layer properties, you MUST add a boolean "silent" property. If the silent property is checked, the users are entering the silent zone when they walk on any tile of the layer.

    Pushing the map

    From 8968627d6912cc2fe4da429ed7a98c84dc29a941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 31 Aug 2020 14:23:31 +0200 Subject: [PATCH 45/51] Adding Jitsi meeting in TCM maps --- back/src/Assets/Maps/Floor0/floor0.json | 56 +++++++++++++++++- back/src/Assets/Maps/Floor1/floor1.json | 79 ++++++++++++++++++++++++- 2 files changed, 133 insertions(+), 2 deletions(-) diff --git a/back/src/Assets/Maps/Floor0/floor0.json b/back/src/Assets/Maps/Floor0/floor0.json index 38bf49e7..6f1e7c09 100644 --- a/back/src/Assets/Maps/Floor0/floor0.json +++ b/back/src/Assets/Maps/Floor0/floor0.json @@ -15,6 +15,60 @@ "height":34, "infinite":false, "layers":[ + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":34, + "id":23, + "name":"patio", + "opacity":1, + "properties":[ + { + "name":"jitsiRoom", + "type":"string", + "value":"tcm-patio" + }], + "type":"tilelayer", + "visible":true, + "width":46, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":34, + "id":22, + "name":"chillzone-2", + "opacity":1, + "properties":[ + { + "name":"jitsiRoom", + "type":"string", + "value":"tcm-chillzone-2" + }], + "type":"tilelayer", + "visible":true, + "width":46, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":34, + "id":21, + "name":"chillzone-1", + "opacity":1, + "properties":[ + { + "name":"jitsiRoom", + "type":"string", + "value":"tcm-chillzone-1" + }], + "type":"tilelayer", + "visible":true, + "width":46, + "x":0, + "y":0 + }, { "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 0, 0, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 0, 0, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 309, 309, 309, 309, 309, 309, 309, 309, 309, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "height":34, @@ -260,7 +314,7 @@ "x":0, "y":0 }], - "nextlayerid":21, + "nextlayerid":24, "nextobjectid":1, "orientation":"orthogonal", "renderorder":"right-down", diff --git a/back/src/Assets/Maps/Floor1/floor1.json b/back/src/Assets/Maps/Floor1/floor1.json index 3ba25159..70cd8abc 100644 --- a/back/src/Assets/Maps/Floor1/floor1.json +++ b/back/src/Assets/Maps/Floor1/floor1.json @@ -21,6 +21,60 @@ "x":0, "y":0 }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":18, + "id":14, + "name":"radiant_meeting_room", + "opacity":1, + "properties":[ + { + "name":"jitsiRoom", + "type":"string", + "value":"tcm-radiant" + }], + "type":"tilelayer", + "visible":true, + "width":46, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":18, + "id":16, + "name":"white-meeting-room", + "opacity":1, + "properties":[ + { + "name":"jitsiRoom", + "type":"string", + "value":"tcm-white-room" + }], + "type":"tilelayer", + "visible":true, + "width":46, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 4861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":18, + "id":15, + "name":"dire-meeting-room", + "opacity":1, + "properties":[ + { + "name":"jitsiRoom", + "type":"string", + "value":"tcm-dire" + }], + "type":"tilelayer", + "visible":true, + "width":46, + "x":0, + "y":0 + }, { "data":[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1123, 1124, 1125, 1126, 1127, 1128, 1129, 1130, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139, 1140, 1141, 1142, 1143, 1144, 1145, 1146, 1147, 1148, 1149, 1150, 1151, 1152, 1153, 1154, 1155, 1156, 1157, 1158, 1159, 1160, 1161, 1162, 1163, 1164, 1165, 1166, 1167, 1168], "height":18, @@ -134,7 +188,7 @@ "x":0, "y":0 }], - "nextlayerid":14, + "nextlayerid":17, "nextobjectid":1, "orientation":"orthogonal", "renderorder":"right-down", @@ -505,6 +559,29 @@ }] }], "tilewidth":32 + }, + { + "columns":8, + "firstgid":4809, + "image":"..\/Floor0\/floortileset.png", + "imageheight":256, + "imagewidth":256, + "margin":0, + "name":"floortileset", + "spacing":0, + "tilecount":64, + "tileheight":32, + "tiles":[ + { + "id":37, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 }], "tilewidth":32, "type":"map", From c739037bc4206c2421440669e8c6f8466b455878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 31 Aug 2020 14:54:52 +0200 Subject: [PATCH 46/51] Camera was not properly closed in EnableCameraScene --- front/src/Phaser/Login/EnableCameraScene.ts | 3 ++ front/src/WebRtc/MediaManager.ts | 36 ++++++++++++++------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts index 7e631b6b..6fc1cd54 100644 --- a/front/src/Phaser/Login/EnableCameraScene.ts +++ b/front/src/Phaser/Login/EnableCameraScene.ts @@ -266,6 +266,9 @@ export class EnableCameraScene extends Phaser.Scene { this.soundMeter.stop(); window.removeEventListener('resize', this.repositionCallback); + mediaManager.stopCamera(); + mediaManager.stopMicrophone(); + // Do we have a start URL in the address bar? If so, let's redirect to this address const instanceAndMapUrl = this.findMapUrl(); if (instanceAndMapUrl !== null) { diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index a043e51e..ebd2a585 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -127,7 +127,7 @@ export class MediaManager { } } - showGameOverlay(){ + public showGameOverlay(){ const gameOverlay = this.getElementByIdOrFail('game-overlay'); gameOverlay.classList.add('active'); } @@ -148,11 +148,7 @@ export class MediaManager { this.cinemaBtn.classList.add("disabled"); this.constraintsMedia.video = false; this.myCamVideo.srcObject = null; - if (this.localStream) { - this.localStream.getVideoTracks().forEach((MediaStreamTrack: MediaStreamTrack) => { - MediaStreamTrack.stop(); - }); - } + this.stopCamera(); this.getCamera().then((stream) => { this.triggerUpdatedLocalStreamCallbacks(stream); }); @@ -173,11 +169,7 @@ export class MediaManager { this.microphone.style.display = "none"; this.microphoneBtn.classList.add("disabled"); this.constraintsMedia.audio = false; - if(this.localStream) { - this.localStream.getAudioTracks().forEach((MediaStreamTrack: MediaStreamTrack) => { - MediaStreamTrack.stop(); - }); - } + this.stopMicrophone(); this.getCamera().then((stream) => { this.triggerUpdatedLocalStreamCallbacks(stream); }); @@ -287,6 +279,28 @@ export class MediaManager { } } + /** + * Stops the camera from filming + */ + public stopCamera(): void { + if (this.localStream) { + for (const track of this.localStream.getVideoTracks()) { + track.stop(); + } + } + } + + /** + * Stops the microphone from listening + */ + public stopMicrophone(): void { + if (this.localStream) { + for (const track of this.localStream.getAudioTracks()) { + track.stop(); + } + } + } + setCamera(id: string): Promise { let video = this.constraintsMedia.video; if (typeof(video) === 'boolean' || video === undefined) { From 634eecd42a7d1d03528a5f703bdb4b6d79e34288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 31 Aug 2020 15:21:05 +0200 Subject: [PATCH 47/51] Fixing issue when both mic and cam are stopped --- front/src/WebRtc/MediaManager.ts | 29 ++++++++++++++++++--------- front/src/WebRtc/ScreenSharingPeer.ts | 6 +++--- front/src/WebRtc/SimplePeer.ts | 17 ++++++++++------ front/src/WebRtc/VideoPeer.ts | 2 +- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index ebd2a585..e8cb080d 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -7,9 +7,9 @@ const videoConstraint: boolean|MediaTrackConstraints = { facingMode: "user" }; -type UpdatedLocalStreamCallback = (media: MediaStream) => void; -type StartScreenSharingCallback = (media: MediaStream) => void; -type StopScreenSharingCallback = (media: MediaStream) => void; +export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void; +export type StartScreenSharingCallback = (media: MediaStream) => void; +export type StopScreenSharingCallback = (media: MediaStream) => void; // TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only) // TODO: verify that microphone event listeners are not triggered plenty of time NOW (since MediaManager is created many times!!!!) @@ -109,7 +109,7 @@ export class MediaManager { this.updatedLocalStreamCallBacks.delete(callback); } - private triggerUpdatedLocalStreamCallbacks(stream: MediaStream): void { + private triggerUpdatedLocalStreamCallbacks(stream: MediaStream|null): void { for (const callback of this.updatedLocalStreamCallBacks) { callback(stream); } @@ -142,16 +142,20 @@ export class MediaManager { }); } - private disableCamera() { + private async disableCamera() { 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(); - this.getCamera().then((stream) => { + + if (this.constraintsMedia.audio !== false) { + const stream = await this.getCamera(); this.triggerUpdatedLocalStreamCallbacks(stream); - }); + } else { + this.triggerUpdatedLocalStreamCallbacks(null); + } } private enableMicrophone() { @@ -159,20 +163,25 @@ export class MediaManager { this.microphone.style.display = "block"; this.microphoneBtn.classList.remove("disabled"); this.constraintsMedia.audio = true; + this.getCamera().then((stream) => { this.triggerUpdatedLocalStreamCallbacks(stream); }); } - private disableMicrophone() { + private async disableMicrophone() { this.microphoneClose.style.display = "block"; this.microphone.style.display = "none"; this.microphoneBtn.classList.add("disabled"); this.constraintsMedia.audio = false; this.stopMicrophone(); - this.getCamera().then((stream) => { + + if (this.constraintsMedia.video !== false) { + const stream = await this.getCamera(); this.triggerUpdatedLocalStreamCallbacks(stream); - }); + } else { + this.triggerUpdatedLocalStreamCallbacks(null); + } } private enableScreenSharing() { diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index 35f43201..3f43a70f 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -70,7 +70,7 @@ export class ScreenSharingPeer extends Peer { } private sendWebrtcScreenSharingSignal(data: unknown) { - console.log("sendWebrtcScreenSharingSignal", data); + //console.log("sendWebrtcScreenSharingSignal", data); try { this.connection.sendWebrtcScreenSharingSignal(data, this.userId); }catch (e) { @@ -82,8 +82,8 @@ export class ScreenSharingPeer extends Peer { * Sends received stream to screen. */ private stream(stream?: MediaStream) { - console.log(`ScreenSharingPeer::stream => ${this.userId}`, stream); - console.log(`stream => ${this.userId} => `, stream); + //console.log(`ScreenSharingPeer::stream => ${this.userId}`, stream); + //console.log(`stream => ${this.userId} => `, stream); if(!stream){ mediaManager.removeActiveScreenSharingVideo(this.userId); this.isReceivingStream = false; diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 3acd65c5..f388b2ec 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -4,7 +4,12 @@ import { WebRtcSignalReceivedMessageInterface, WebRtcStartMessageInterface } from "../Connection"; -import { mediaManager } from "./MediaManager"; +import { + mediaManager, + StartScreenSharingCallback, + StopScreenSharingCallback, + UpdatedLocalStreamCallback +} from "./MediaManager"; import * as SimplePeerNamespace from "simple-peer"; import {ScreenSharingPeer} from "./ScreenSharingPeer"; import {VideoPeer} from "./VideoPeer"; @@ -32,9 +37,9 @@ export class SimplePeer { private PeerScreenSharingConnectionArray: Map = new Map(); private PeerConnectionArray: Map = new Map(); - private readonly sendLocalVideoStreamCallback: (media: MediaStream) => void; - private readonly sendLocalScreenSharingStreamCallback: (media: MediaStream) => void; - private readonly stopLocalScreenSharingStreamCallback: (media: MediaStream) => void; + private readonly sendLocalVideoStreamCallback: UpdatedLocalStreamCallback; + private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback; + private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback; private readonly peerConnectionListeners: Array = new Array(); constructor(Connection: Connection, WebRtcRoomId: string = "test-webrtc") { @@ -326,9 +331,9 @@ export class SimplePeer { } public sendLocalVideoStream(){ - this.Users.forEach((user: UserSimplePeerInterface) => { + for (const user of this.Users) { this.pushVideoToRemoteUser(user.userId); - }) + } } /** diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index ec7f2576..3ffe33e7 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -85,7 +85,7 @@ export class VideoPeer extends Peer { * Sends received stream to screen. */ private stream(stream?: MediaStream) { - console.log(`VideoPeer::stream => ${this.userId}`, stream); + //console.log(`VideoPeer::stream => ${this.userId}`, stream); if(!stream){ mediaManager.disabledVideoByUserId(this.userId); mediaManager.disabledMicrophoneByUserId(this.userId); From ed116cf2a300c0c2f046d936b14a58b9b3c4ccc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 28 Aug 2020 13:52:25 +0200 Subject: [PATCH 48/51] Switching on our very own turn server --- deeployer.libsonnet | 5 ++++- docker-compose.yaml | 3 +++ front/src/Enum/EnvironmentVariable.ts | 6 ++++++ front/src/WebRtc/ScreenSharingPeer.ts | 7 ++++--- front/src/WebRtc/VideoPeer.ts | 7 ++++--- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 528342ed..620e9484 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -26,7 +26,10 @@ "ports": [80], "env": { "API_URL": "api."+url, - "JITSI_URL": "meet.jit.si" + "JITSI_URL": "meet.jit.si", + "TURN_SERVER": "coturn.workadventu.re:443", + "TURN_USER": "workadventure", + "TURN_PASSWORD": "WorkAdventure123" } }, "website": { diff --git a/docker-compose.yaml b/docker-compose.yaml index d944e0f8..e731bbed 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -27,6 +27,9 @@ services: NODE_ENV: development API_URL: api.workadventure.localhost STARTUP_COMMAND_1: yarn install + TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443" + TURN_USER: workadventure + TURN_PASSWORD: WorkAdventure123 command: yarn run start volumes: - ./front:/usr/src/app diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 97a3f91b..59c8b50f 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -1,5 +1,8 @@ const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true"; const API_URL = (typeof(window) !== 'undefined' ? window.location.protocol : 'http:') + '//' + (process.env.API_URL || "api.workadventure.localhost"); +const TURN_SERVER: string = process.env.TURN_SERVER || "turn:numb.viagenie.ca"; +const TURN_USER: string = process.env.TURN_USER || 'g.parant@thecodingmachine.com'; +const TURN_PASSWORD: string = process.env.TURN_PASSWORD || 'itcugcOHxle9Acqi$'; const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL; const RESOLUTION = 3; const ZOOM_LEVEL = 1/*3/4*/; @@ -13,5 +16,8 @@ export { ZOOM_LEVEL, POSITION_DELAY, MAX_EXTRAPOLATION_TIME, + TURN_SERVER, + TURN_USER, + TURN_PASSWORD, JITSI_URL } diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index 3f43a70f..3774c612 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -1,6 +1,7 @@ import * as SimplePeerNamespace from "simple-peer"; import {mediaManager} from "./MediaManager"; import {Connection} from "../Connection"; +import {TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable"; const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); @@ -23,9 +24,9 @@ export class ScreenSharingPeer extends Peer { urls: 'stun:stun.l.google.com:19302' }, { - urls: 'turn:numb.viagenie.ca', - username: 'g.parant@thecodingmachine.com', - credential: 'itcugcOHxle9Acqi$' + urls: TURN_SERVER, + username: TURN_USER, + credential: TURN_PASSWORD }, ] } diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index 3ffe33e7..0124eee5 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -1,6 +1,7 @@ import * as SimplePeerNamespace from "simple-peer"; import {mediaManager} from "./MediaManager"; import {Connection} from "../Connection"; +import {TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable"; const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); @@ -18,9 +19,9 @@ export class VideoPeer extends Peer { urls: 'stun:stun.l.google.com:19302' }, { - urls: 'turn:numb.viagenie.ca', - username: 'g.parant@thecodingmachine.com', - credential: 'itcugcOHxle9Acqi$' + urls: TURN_SERVER, + username: TURN_USER, + credential: TURN_PASSWORD }, ] } From 3875c0afe83dbb1f3ed323c9280a0f4a984a0168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 28 Aug 2020 16:18:26 +0200 Subject: [PATCH 49/51] Fixing environment variable passing in Webpack --- front/webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/webpack.config.js b/front/webpack.config.js index 7ffbcfcc..3083fd25 100644 --- a/front/webpack.config.js +++ b/front/webpack.config.js @@ -42,7 +42,7 @@ module.exports = { new webpack.ProvidePlugin({ Phaser: 'phaser' }), - new webpack.EnvironmentPlugin(['API_URL', 'DEBUG_MODE', 'JITSI_URL']) + new webpack.EnvironmentPlugin(['API_URL', 'DEBUG_MODE', 'TURN_SERVER', 'TURN_USER', 'TURN_PASSWORD', 'JITSI_URL']) ], }; From 8655aef6298d34f1895f6555b063ceabd0120051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 28 Aug 2020 16:29:21 +0200 Subject: [PATCH 50/51] Fixing URL passing in WebRtc setup --- front/src/WebRtc/ScreenSharingPeer.ts | 2 +- front/src/WebRtc/VideoPeer.ts | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index 3774c612..8857274e 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -24,7 +24,7 @@ export class ScreenSharingPeer extends Peer { urls: 'stun:stun.l.google.com:19302' }, { - urls: TURN_SERVER, + urls: TURN_SERVER.split(','), username: TURN_USER, credential: TURN_PASSWORD }, diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index 0124eee5..33422433 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -19,7 +19,7 @@ export class VideoPeer extends Peer { urls: 'stun:stun.l.google.com:19302' }, { - urls: TURN_SERVER, + urls: TURN_SERVER.split(','), username: TURN_USER, credential: TURN_PASSWORD }, @@ -27,6 +27,23 @@ export class VideoPeer extends Peer { } }); + console.log('PEER SETUP ', { + initiator: initiator ? initiator : false, + reconnectTimer: 10000, + config: { + iceServers: [ + { + urls: 'stun:stun.l.google.com:19302' + }, + { + urls: TURN_SERVER, + username: TURN_USER, + credential: TURN_PASSWORD + }, + ] + } + }) + //start listen signal for the peer connection this.on('signal', (data: unknown) => { this.sendWebrtcSignal(data); From b6590e21dd310f48fc840d99b15cecbf5addeca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 28 Aug 2020 17:30:33 +0200 Subject: [PATCH 51/51] Fixing coturn URLS --- deeployer.libsonnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 620e9484..d6cd232b 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -27,7 +27,7 @@ "env": { "API_URL": "api."+url, "JITSI_URL": "meet.jit.si", - "TURN_SERVER": "coturn.workadventu.re:443", + "TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443", "TURN_USER": "workadventure", "TURN_PASSWORD": "WorkAdventure123" }