From 27634a61ee795d74cc424e3874c5d294aab88ec9 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 27 Nov 2020 16:24:07 +0100 Subject: [PATCH] Create menu to set your quality video and game --- front/dist/resources/logos/monitor-yellow.svg | 1 + front/dist/resources/style/style.css | 39 +++ .../ConsoleGlobalMessageManager.ts | 243 +++++++++++++++--- front/src/Phaser/Game/GameScene.ts | 4 +- front/src/WebRtc/MediaManager.ts | 30 ++- front/src/index.ts | 16 +- 6 files changed, 281 insertions(+), 52 deletions(-) create mode 100644 front/dist/resources/logos/monitor-yellow.svg diff --git a/front/dist/resources/logos/monitor-yellow.svg b/front/dist/resources/logos/monitor-yellow.svg new file mode 100644 index 00000000..f26d30c1 --- /dev/null +++ b/front/dist/resources/logos/monitor-yellow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 10e16cfa..0fa951bc 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -74,6 +74,10 @@ body .message-info.warning{ height: 100%; } +.video-container video:focus{ + outline: none; +} + .video-container#div-myCamVideo{ border: none; } @@ -455,6 +459,8 @@ body { flex-grow: 5; } +/*CONSOLE*/ + .message-container, .main-console{ position: absolute; @@ -467,6 +473,7 @@ body { background: #333333; z-index: 200; transition: all 0.1s ease-out; + border-radius: 0 0 15px 15px; } .message-container{ @@ -499,6 +506,16 @@ body { text-align: center; } +.main-console div.message, +.main-console div.setting{ + display: none; +} + +.main-console div.message.active, +.main-console div.setting.active{ + display: block; +} + .message-container div.clear{ width: 100px; left: calc(50% - 50px); @@ -648,6 +665,28 @@ body { margin-left: 10px; } +/* VIDEO QUALITY */ +.main-console div.setting h1{ + color: white; +} +.main-console div.setting select{ + background: black; + color: white; + min-width: 280px; + text-align: center; + min-height: 40px; + padding: 10px; + border-radius: 15px; +} +.main-console div.setting select:focus{ + border: solid 1px white; + outline: none; +} +.main-console div.setting.active section{ + display: block; +} + + /*REPORT input*/ div.modal-report-user{ position: absolute; diff --git a/front/src/Administration/ConsoleGlobalMessageManager.ts b/front/src/Administration/ConsoleGlobalMessageManager.ts index a79ecf75..03929a46 100644 --- a/front/src/Administration/ConsoleGlobalMessageManager.ts +++ b/front/src/Administration/ConsoleGlobalMessageManager.ts @@ -3,11 +3,15 @@ import {UserInputManager} from "../Phaser/UserInput/UserInputManager"; import {RoomConnection} from "../Connexion/RoomConnection"; import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels"; import {ADMIN_URL} from "../Enum/EnvironmentVariable"; +import {mediaManager} from "../WebRtc/MediaManager"; export const CLASS_CONSOLE_MESSAGE = 'main-console'; export const INPUT_CONSOLE_MESSAGE = 'input-send-text'; export const UPLOAD_CONSOLE_MESSAGE = 'input-upload-music'; export const INPUT_TYPE_CONSOLE = 'input-type'; +export const GAME_QUALITY_SELECT = 'select-game-quality'; +export const VIDEO_QUALITY_SELECT = 'select-video-quality'; +export const VIDEO_QUALITY_CONSOLE = 'video-quality'; export const AUDIO_TYPE = 'audio'; export const MESSAGE_TYPE = 'message'; @@ -19,19 +23,30 @@ interface EventTargetFiles extends EventTarget { export class ConsoleGlobalMessageManager { private readonly divMainConsole: HTMLDivElement; + private readonly divMessageConsole: HTMLDivElement; + private readonly divSettingConsole: HTMLDivElement; private readonly buttonMainConsole: HTMLDivElement; private readonly buttonSendMainConsole: HTMLImageElement; + private readonly buttonAdminMainConsole: HTMLImageElement; private readonly buttonSettingsMainConsole: HTMLImageElement; private activeConsole: boolean = false; + private activeMessage: boolean = false; + private activeSetting: boolean = false; private userInputManager!: UserInputManager; private static cssLoaded: boolean = false; - constructor(private Connection: RoomConnection, userInputManager : UserInputManager) { + constructor(private Connection: RoomConnection, userInputManager : UserInputManager, private isAdmin: Boolean) { this.buttonMainConsole = document.createElement('div'); this.buttonMainConsole.classList.add('console'); this.divMainConsole = document.createElement('div'); + this.divMainConsole.className = CLASS_CONSOLE_MESSAGE; + this.divMessageConsole = document.createElement('div'); + this.divMessageConsole.className = 'message'; + this.divSettingConsole = document.createElement('div'); + this.divSettingConsole.className = 'setting'; this.buttonSendMainConsole = document.createElement('img'); this.buttonSettingsMainConsole = document.createElement('img'); + this.buttonAdminMainConsole = document.createElement('img'); this.userInputManager = userInputManager; this.initialise(); } @@ -78,35 +93,48 @@ export class ConsoleGlobalMessageManager { }); menu.appendChild(textMessage); menu.appendChild(textAudio); - this.divMainConsole.appendChild(menu); + this.divMessageConsole.appendChild(menu); this.buttonSendMainConsole.src = 'resources/logos/send-yellow.svg'; this.buttonSendMainConsole.addEventListener('click', () => { - if(this.activeConsole){ - this.disabled(); + if(this.activeMessage){ + this.disabledMessageConsole(); }else{ - this.buttonSendMainConsole.classList.add('active'); - this.active(); + this.activeMessageConsole(); } }); - this.buttonMainConsole.appendChild(this.buttonSendMainConsole); - this.buttonSettingsMainConsole.src = 'resources/logos/setting-yellow.svg'; - this.buttonSettingsMainConsole.addEventListener('click', () => { + this.buttonAdminMainConsole.src = 'resources/logos/setting-yellow.svg'; + this.buttonAdminMainConsole.addEventListener('click', () => { window.open(ADMIN_URL, '_blank'); }); - this.buttonMainConsole.appendChild(this.buttonSettingsMainConsole); + + this.buttonSettingsMainConsole.src = 'resources/logos/monitor-yellow.svg'; + this.buttonSettingsMainConsole.addEventListener('click', () => { + if(this.activeSetting){ + this.disabledSettingConsole(); + }else{ + this.activeSettingConsole(); + } + }); /*const buttonText = document.createElement('p'); buttonText.innerText = 'Console'; this.buttonMainConsole.appendChild(buttonText);*/ + this.divMessageConsole.appendChild(typeConsole); + + if(this.isAdmin) { + this.buttonMainConsole.appendChild(this.buttonSendMainConsole); + this.buttonMainConsole.appendChild(this.buttonAdminMainConsole); + this.createTextMessagePart(); + this.createUploadAudioPart(); + } + this.buttonMainConsole.appendChild(this.buttonSettingsMainConsole); + this.createSettings(); - this.divMainConsole.className = CLASS_CONSOLE_MESSAGE; this.divMainConsole.appendChild(this.buttonMainConsole); - this.divMainConsole.appendChild(typeConsole); - - this.createTextMessagePart(); - this.createUploadAudioPart(); + this.divMainConsole.appendChild(this.divMessageConsole); + this.divMainConsole.appendChild(this.divSettingConsole); const mainSectionDiv = HtmlUtils.getElementByIdOrFail('main-container'); mainSectionDiv.appendChild(this.divMainConsole); @@ -120,7 +148,7 @@ export class ConsoleGlobalMessageManager { buttonSend.classList.add('btn'); buttonSend.addEventListener('click', (event: MouseEvent) => { this.sendMessage(); - this.disabled(); + this.disabledMessageConsole(); }); const buttonDiv = document.createElement('div'); buttonDiv.classList.add('btn-action'); @@ -131,7 +159,7 @@ export class ConsoleGlobalMessageManager { section.classList.add('active'); section.appendChild(div); section.appendChild(buttonDiv); - this.divMainConsole.appendChild(section); + this.divMessageConsole.appendChild(section); (async () => { // Start loading CSS @@ -173,27 +201,6 @@ export class ConsoleGlobalMessageManager { })(); } - private static loadCss(): Promise { - return new Promise((resolve, reject) => { - if (ConsoleGlobalMessageManager.cssLoaded) { - resolve(); - return; - } - const fileref = document.createElement("link") - fileref.setAttribute("rel", "stylesheet") - fileref.setAttribute("type", "text/css") - fileref.setAttribute("href", "https://cdn.quilljs.com/1.3.7/quill.snow.css"); - document.getElementsByTagName("head")[0].appendChild(fileref); - ConsoleGlobalMessageManager.cssLoaded = true; - fileref.onload = () => { - resolve(); - } - fileref.onerror = () => { - reject(); - } - }); - } - createUploadAudioPart(){ const div = document.createElement('div'); div.classList.add('upload'); @@ -243,7 +250,7 @@ export class ConsoleGlobalMessageManager { buttonSend.classList.add('btn'); buttonSend.addEventListener('click', (event: MouseEvent) => { this.sendMessage(); - this.disabled(); + this.disabledMessageConsole(); }); const buttonDiv = document.createElement('div'); buttonDiv.classList.add('btn-action'); @@ -253,7 +260,114 @@ export class ConsoleGlobalMessageManager { section.id = this.getSectionId(UPLOAD_CONSOLE_MESSAGE); section.appendChild(div); section.appendChild(buttonDiv); - this.divMainConsole.appendChild(section); + this.divMessageConsole.appendChild(section); + } + + createSettings(){ + const labelGame = document.createElement('h1'); + labelGame.innerText = "Game quality"; + + const selectGame = document.createElement('select'); + selectGame.id = VIDEO_QUALITY_SELECT; + + const option1 : HTMLOptionElement = document.createElement('option'); + option1.value = '120'; + option1.innerText = 'High video quality (120 fps)'; + selectGame.appendChild(option1); + + const option2 : HTMLOptionElement = document.createElement('option'); + option2.value = '60'; + option2.innerText = 'Medium video quality (60 fps, recommended)'; + selectGame.appendChild(option2); + + const option3 : HTMLOptionElement = document.createElement('option'); + option3.value = '40'; + option3.innerText = 'Minimum video quality (40 fps)'; + selectGame.appendChild(option3); + + const option4 : HTMLOptionElement = document.createElement('option'); + option4.value = '20'; + option4.innerText = 'Small video quality (20 fps)'; + selectGame.appendChild(option4); + + const labelVideo = document.createElement('h1'); + labelVideo.innerText = "Video quality"; + + const selectVideo = document.createElement('select'); + selectVideo.id = GAME_QUALITY_SELECT; + + const optionVideo1 : HTMLOptionElement = document.createElement('option'); + optionVideo1.value = '30'; + optionVideo1.innerText = 'High video quality (30 fps)'; + selectVideo.appendChild(optionVideo1); + + const optionVideo2 : HTMLOptionElement = document.createElement('option'); + optionVideo2.value = '20'; + optionVideo2.innerText = 'Medium video quality (20 fps, recommended)'; + selectVideo.appendChild(optionVideo2); + + const optionVideo3 : HTMLOptionElement = document.createElement('option'); + optionVideo3.value = '10'; + optionVideo3.innerText = 'Minimum video quality (10 fps)'; + selectVideo.appendChild(optionVideo3); + + const optionVideo4 : HTMLOptionElement = document.createElement('option'); + optionVideo4.value = '5'; + optionVideo4.innerText = 'Small video quality (5 fps)'; + selectVideo.appendChild(optionVideo4); + + selectGame.value = '60'; + const localQualityGame = localStorage.getItem(GAME_QUALITY_SELECT); + if(localQualityGame){ + selectGame.value = localQualityGame; + } + + selectVideo.value = '30'; + const localQualityCam = localStorage.getItem(VIDEO_QUALITY_SELECT); + if(localQualityCam){ + selectVideo.value = localQualityCam; + } + + const divButtonAction = document.createElement('div'); + divButtonAction.className = 'btn-action'; + const buttonSave = document.createElement('button'); + buttonSave.innerText = 'Save'; + buttonSave.classList.add('btn'); + buttonSave.addEventListener('click', () => { + this.saveSetting(selectGame.value, selectVideo.value); + this.disabledSettingConsole(); + }); + divButtonAction.appendChild(buttonSave); + + const section = document.createElement('section'); + section.id = this.getSectionId(VIDEO_QUALITY_CONSOLE); + section.appendChild(labelGame); + section.appendChild(selectGame); + section.appendChild(labelVideo); + section.appendChild(selectVideo); + section.appendChild(divButtonAction); + this.divSettingConsole.appendChild(section); + } + + private static loadCss(): Promise { + return new Promise((resolve, reject) => { + if (ConsoleGlobalMessageManager.cssLoaded) { + resolve(); + return; + } + const fileref = document.createElement("link") + fileref.setAttribute("rel", "stylesheet") + fileref.setAttribute("type", "text/css") + fileref.setAttribute("href", "https://cdn.quilljs.com/1.3.7/quill.snow.css"); + document.getElementsByTagName("head")[0].appendChild(fileref); + ConsoleGlobalMessageManager.cssLoaded = true; + fileref.onload = () => { + resolve(); + } + fileref.onerror = () => { + reject(); + } + }); } sendMessage(){ @@ -307,20 +421,65 @@ export class ConsoleGlobalMessageManager { this.Connection.emitGlobalMessage(GlobalMessage); } + private saveSetting(valueGame: string, valueVideo: string){ + const previousGameValue = localStorage.getItem(GAME_QUALITY_SELECT); + if(!previousGameValue || previousGameValue !== valueGame) { + localStorage.setItem(GAME_QUALITY_SELECT, valueGame); + window.location.reload(); + } + const previousVideoValue = localStorage.getItem(VIDEO_QUALITY_SELECT); + if(!previousVideoValue || previousVideoValue !== valueVideo) { + localStorage.setItem(VIDEO_QUALITY_SELECT, valueVideo); + mediaManager.updateCameraQuality(parseInt(valueVideo)); + } + } + active(){ this.userInputManager.clearAllInputKeyboard(); - this.activeConsole = true; this.divMainConsole.style.top = '0'; - this.buttonSendMainConsole.classList.add('active'); + this.activeConsole = true; } disabled(){ this.userInputManager.initKeyBoardEvent(); this.activeConsole = false; this.divMainConsole.style.top = '-80%'; + } + + activeMessageConsole(){ + this.activeMessage = true; + if(this.activeSetting){ + this.disabledSettingConsole(); + } + this.active(); + this.divMessageConsole.classList.add('active'); + this.buttonSendMainConsole.classList.add('active'); + } + + disabledMessageConsole(){ + this.activeMessage = false; + this.disabled(); + this.divMessageConsole.classList.remove('active'); this.buttonSendMainConsole.classList.remove('active'); } + activeSettingConsole(){ + this.activeSetting = true; + if(this.activeMessage){ + this.disabledMessageConsole(); + } + this.active(); + this.divSettingConsole.classList.add('active'); + this.buttonSettingsMainConsole.classList.add('active'); + } + + disabledSettingConsole(){ + this.activeSetting = false; + this.disabled(); + this.divSettingConsole.classList.remove('active'); + this.buttonSettingsMainConsole.classList.remove('active'); + } + private getSectionId(id: string) : string { return `section-${id}`; } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 45fb6a06..1435513c 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -462,9 +462,7 @@ export class GameScene extends ResizableScene implements CenterListener { this.initUsersPosition(roomJoinedMessage.users); this.connectionAnswerPromiseResolve(roomJoinedMessage); // Analyze tags to find if we are admin. If yes, show console. - if (this.connection.hasTag('admin')) { - this.ConsoleGlobalMessageManager = new ConsoleGlobalMessageManager(this.connection, this.userInputManager); - } + this.ConsoleGlobalMessageManager = new ConsoleGlobalMessageManager(this.connection, this.userInputManager, this.connection.hasTag('admin')); }); connection.onUserJoins((message: MessageUserJoined) => { diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 943e13c2..c7aa82d0 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -2,12 +2,21 @@ import {DivImportance, layoutManager} from "./LayoutManager"; import {HtmlUtils} from "./HtmlUtils"; import {DiscussionManager, SendMessageCallback} from "./DiscussionManager"; import {UserInputManager} from "../Phaser/UserInput/UserInputManager"; +import {VIDEO_QUALITY_SELECT} from "../Administration/ConsoleGlobalMessageManager"; declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any -const videoConstraint: boolean|MediaTrackConstraints = { - width: { ideal: 1280 }, - height: { ideal: 720 }, - facingMode: "user" +const localValueVideo = localStorage.getItem(VIDEO_QUALITY_SELECT); +let valueVideo = 20; +if(localValueVideo){ + valueVideo = parseInt(localValueVideo); +} +let videoConstraint: boolean|MediaTrackConstraints = { + width: { min: 640, ideal: 1280, max: 1920 }, + height: { min: 400, ideal: 720 }, + frameRate: {exact: valueVideo, ideal: valueVideo}, + facingMode: "user", + resizeMode: 'crop-and-scale', + aspectRatio: 1.777777778 }; export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void; @@ -191,6 +200,17 @@ export class MediaManager { buttonCloseFrame.addEventListener('click', functionTrigger); } + public updateCameraQuality(value: number) { + this.enableCameraStyle(); + const newVideoConstraint = JSON.parse(JSON.stringify(videoConstraint)); + newVideoConstraint.frameRate = {exact: value, ideal: value}; + videoConstraint = newVideoConstraint; + this.constraintsMedia.video = videoConstraint; + this.getCamera().then((stream: MediaStream) => { + this.triggerUpdatedLocalStreamCallbacks(stream); + }); + } + public enableCamera() { this.enableCameraStyle(); this.constraintsMedia.video = videoConstraint; @@ -564,7 +584,7 @@ export class MediaManager { } isError(userId: string): void { - console.log("isError", `div-${userId}`); + console.info("isError", `div-${userId}`); const element = document.getElementById(`div-${userId}`); if(!element){ return; diff --git a/front/src/index.ts b/front/src/index.ts index 59b15718..640b04f9 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -13,6 +13,7 @@ import {CustomizeScene} from "./Phaser/Login/CustomizeScene"; import {ResizableScene} from "./Phaser/Login/ResizableScene"; import {EntryScene} from "./Phaser/Login/EntryScene"; import {coWebsiteManager} from "./WebRtc/CoWebsiteManager"; +import {GAME_QUALITY_SELECT} from "./Administration/ConsoleGlobalMessageManager"; // Load Jitsi if the environment variable is set. if (JITSI_URL) { @@ -23,15 +24,24 @@ if (JITSI_URL) { const {width, height} = coWebsiteManager.getGameSize(); +let valueGameQuality : number = 60 +const localGameQuality = localStorage.getItem(GAME_QUALITY_SELECT); +if(localGameQuality){ + try { + valueGameQuality = parseInt(localGameQuality); + }catch (err){ + console.error(err); + } +} const fps : Phaser.Types.Core.FPSConfig = { /** * The minimum acceptable rendering rate, in frames per second. */ - min: 40, + min: valueGameQuality, /** * The optimum rendering rate, in frames per second. */ - target: 40, + target: valueGameQuality, /** * Use setTimeout instead of requestAnimationFrame to run the game loop. */ @@ -49,6 +59,7 @@ const fps : Phaser.Types.Core.FPSConfig = { */ smoothStep: false } + const config: GameConfig = { type: Phaser.AUTO, title: "WorkAdventure", @@ -88,6 +99,7 @@ window.addEventListener('resize', function (event) { } } }); + coWebsiteManager.onStateChange(() => { const {width, height} = coWebsiteManager.getGameSize(); game.scale.resize(width / RESOLUTION, height / RESOLUTION);