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] 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",