diff --git a/contrib/docker/.env.prod.template b/contrib/docker/.env.prod.template index 5e9adc87..a6317078 100644 --- a/contrib/docker/.env.prod.template +++ b/contrib/docker/.env.prod.template @@ -13,7 +13,7 @@ DOMAIN=workadventure.localhost # Subdomains # MUST match the DOMAIN variable above -FRONT_HOST=front.workadventure.localhost +FRONT_HOST=play.workadventure.localhost PUSHER_HOST=pusher.workadventure.localhost BACK_HOST=api.workadventure.localhost MAPS_HOST=maps.workadventure.localhost diff --git a/contrib/docker/docker-compose.prod.yaml b/contrib/docker/docker-compose.prod.yaml index 80ed192b..eb21ab3d 100644 --- a/contrib/docker/docker-compose.prod.yaml +++ b/contrib/docker/docker-compose.prod.yaml @@ -27,10 +27,7 @@ services: front: - build: - context: ../.. - dockerfile: front/Dockerfile - #image: thecodingmachine/workadventure-front:${VERSION} + image: thecodingmachine/workadventure-front:${VERSION} environment: - DEBUG_MODE - JITSI_URL @@ -60,10 +57,7 @@ services: restart: ${RESTART_POLICY} pusher: - build: - context: ../.. - dockerfile: pusher/Dockerfile - #image: thecodingmachine/workadventure-pusher:${VERSION} + image: thecodingmachine/workadventure-pusher:${VERSION} command: yarn run runprod environment: - SECRET_JITSI_KEY @@ -77,7 +71,7 @@ services: - "traefik.http.routers.pusher.rule=Host(`${PUSHER_HOST}`)" - "traefik.http.routers.pusher.entryPoints=web" - "traefik.http.services.pusher.loadbalancer.server.port=8080" - - "traefik.http.routers.pusher-ssl.rule=Host(${PUSHER_HOST}`)" + - "traefik.http.routers.pusher-ssl.rule=Host(`${PUSHER_HOST}`)" - "traefik.http.routers.pusher-ssl.entryPoints=websecure" - "traefik.http.routers.pusher-ssl.service=pusher" - "traefik.http.routers.pusher-ssl.tls=true" @@ -85,10 +79,7 @@ services: restart: ${RESTART_POLICY} back: - build: - context: ../.. - dockerfile: back/Dockerfile - #image: thecodingmachine/workadventure-back:${VERSION} + image: thecodingmachine/workadventure-back:${VERSION} command: yarn run runprod environment: - SECRET_JITSI_KEY @@ -119,7 +110,7 @@ services: image: matthiasluedtke/iconserver:v3.13.0 labels: - "traefik.http.routers.icon.rule=Host(`${ICON_HOST}`)" - - "traefik.http.routers.icon.entryPoints=web,traefik" + - "traefik.http.routers.icon.entryPoints=web" - "traefik.http.services.icon.loadbalancer.server.port=8080" - "traefik.http.routers.icon-ssl.rule=Host(`${ICON_HOST}`)" - "traefik.http.routers.icon-ssl.entryPoints=websecure" diff --git a/front/public/resources/icons/icon_talking.png b/front/public/resources/icons/icon_talking.png new file mode 100644 index 00000000..9d566f4e Binary files /dev/null and b/front/public/resources/icons/icon_talking.png differ diff --git a/front/src/Components/EmbedScreens/CamerasContainer.svelte b/front/src/Components/EmbedScreens/CamerasContainer.svelte index 03718fdc..3783021a 100644 --- a/front/src/Components/EmbedScreens/CamerasContainer.svelte +++ b/front/src/Components/EmbedScreens/CamerasContainer.svelte @@ -3,7 +3,7 @@ import { streamableCollectionStore } from "../../Stores/StreamableCollectionStore"; import MediaBox from "../Video/MediaBox.svelte"; - export let highlightedEmbedScreen: EmbedScreen | null; + export let highlightedEmbedScreen: EmbedScreen | undefined; export let full = false; $: clickable = !full; diff --git a/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte b/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte index e3beeef3..cf938ad1 100644 --- a/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte +++ b/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte @@ -73,9 +73,9 @@ $mainCoWebsite !== undefined && $mainCoWebsite.getId() === coWebsite.getId(); isHighlight = - $highlightedEmbedScreen !== null && - $highlightedEmbedScreen.type === "cowebsite" && - $highlightedEmbedScreen.embed.getId() === coWebsite.getId(); + $highlightedEmbedScreen !== undefined && + $highlightedEmbedScreen?.type === "cowebsite" && + $highlightedEmbedScreen?.embed.getId() === coWebsite.getId(); } diff --git a/front/src/Components/EnableCamera/EnableCameraScene.svelte b/front/src/Components/EnableCamera/EnableCameraScene.svelte index 8a397541..6ca909dd 100644 --- a/front/src/Components/EnableCamera/EnableCameraScene.svelte +++ b/front/src/Components/EnableCamera/EnableCameraScene.svelte @@ -5,6 +5,7 @@ audioConstraintStore, cameraListStore, localStreamStore, + localVolumeStore, microphoneListStore, videoConstraintStore, } from "../../Stores/MediaStore"; @@ -38,7 +39,7 @@ let stream: MediaStream | null; - const unsubscribe = localStreamStore.subscribe((value) => { + const unsubscribeLocalStreamStore = localStreamStore.subscribe((value) => { if (value.type === "success") { stream = value.stream; @@ -59,7 +60,9 @@ } }); - onDestroy(unsubscribe); + onDestroy(() => { + unsubscribeLocalStreamStore(); + }); function normalizeDeviceName(label: string): string { // remove IDs (that can appear in Chrome, like: "HD Pro Webcam (4df7:4eda)" @@ -86,7 +89,7 @@ {/if} - +
{#if $cameraListStore.length > 1} diff --git a/front/src/Components/EnableCamera/HorizontalSoundMeterWidget.svelte b/front/src/Components/EnableCamera/HorizontalSoundMeterWidget.svelte index 58bbb138..df7a1de6 100644 --- a/front/src/Components/EnableCamera/HorizontalSoundMeterWidget.svelte +++ b/front/src/Components/EnableCamera/HorizontalSoundMeterWidget.svelte @@ -1,50 +1,8 @@ -
+
{#each [...Array(NB_BARS).keys()] as i (i)}
{/each} diff --git a/front/src/Components/MyCamera.svelte b/front/src/Components/MyCamera.svelte index b0b5eccd..7f97ff22 100644 --- a/front/src/Components/MyCamera.svelte +++ b/front/src/Components/MyCamera.svelte @@ -1,5 +1,5 @@
diff --git a/front/src/Components/Video/VideoMediaBox.svelte b/front/src/Components/Video/VideoMediaBox.svelte index e5199a37..4652449b 100644 --- a/front/src/Components/Video/VideoMediaBox.svelte +++ b/front/src/Components/Video/VideoMediaBox.svelte @@ -18,6 +18,7 @@ export let peer: VideoPeer; let streamStore = peer.streamStore; + let volumeStore = peer.volumeStore; let name = peer.userName; let statusStore = peer.statusStore; let constraintStore = peer.constraintsStore; @@ -93,7 +94,7 @@ /> {#if $constraintStore && $constraintStore.audio !== false} - + {/if}
diff --git a/front/src/Phaser/Components/SoundMeter.ts b/front/src/Phaser/Components/SoundMeter.ts index 1d97be50..6e12912f 100644 --- a/front/src/Phaser/Components/SoundMeter.ts +++ b/front/src/Phaser/Components/SoundMeter.ts @@ -1,4 +1,4 @@ -import type { IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from "standardized-audio-context"; +import { AudioContext, IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from "standardized-audio-context"; /** * Class to measure the sound volume of a media stream @@ -6,41 +6,15 @@ import type { IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from " export class SoundMeter { private instant: number; private clip: number; - //private script: ScriptProcessorNode; private analyser: IAnalyserNode | undefined; private dataArray: Uint8Array | undefined; private context: IAudioContext | undefined; private source: IMediaStreamAudioSourceNode | undefined; - constructor() { + constructor(mediaStream: MediaStream) { this.instant = 0.0; this.clip = 0.0; - //this.script = context.createScriptProcessor(2048, 1, 1); - } - - private init(context: IAudioContext) { - this.context = context; - this.analyser = this.context.createAnalyser(); - - this.analyser.fftSize = 2048; - const bufferLength = this.analyser.fftSize; - this.dataArray = new Uint8Array(bufferLength); - } - - public connectToSource(stream: MediaStream, context: IAudioContext): void { - if (this.source !== undefined) { - this.stop(); - } - - this.init(context); - - this.source = this.context?.createMediaStreamSource(stream); - if (this.analyser !== undefined) { - this.source?.connect(this.analyser); - } - //analyser.connect(distortion); - //distortion.connect(this.context.destination); - //this.analyser.connect(this.context.destination); + this.connectToSource(mediaStream, new AudioContext()); } public getVolume(): number { @@ -78,4 +52,29 @@ export class SoundMeter { this.dataArray = undefined; this.source = undefined; } + + private init(context: IAudioContext) { + this.context = context; + this.analyser = this.context.createAnalyser(); + + this.analyser.fftSize = 2048; + const bufferLength = this.analyser.fftSize; + this.dataArray = new Uint8Array(bufferLength); + } + + private connectToSource(stream: MediaStream, context: IAudioContext): void { + if (this.source !== undefined) { + this.stop(); + } + + this.init(context); + + this.source = this.context?.createMediaStreamSource(stream); + if (this.analyser !== undefined) { + this.source?.connect(this.analyser); + } + //analyser.connect(distortion); + //distortion.connect(this.context.destination); + //this.analyser.connect(this.context.destination); + } } diff --git a/front/src/Phaser/Components/TalkIcon.ts b/front/src/Phaser/Components/TalkIcon.ts new file mode 100644 index 00000000..12b4ebe3 --- /dev/null +++ b/front/src/Phaser/Components/TalkIcon.ts @@ -0,0 +1,58 @@ +import { Easing } from "../../types"; + +export class TalkIcon extends Phaser.GameObjects.Image { + private shown: boolean; + private showAnimationTween?: Phaser.Tweens.Tween; + + private defaultPosition: { x: number; y: number }; + private defaultScale: number; + + constructor(scene: Phaser.Scene, x: number, y: number) { + super(scene, x, y, "iconTalk"); + + this.defaultPosition = { x, y }; + this.defaultScale = 0.3; + + this.shown = false; + this.setAlpha(0); + this.setScale(this.defaultScale); + + this.scene.add.existing(this); + } + + public show(show: boolean = true, forceClose: boolean = false): void { + if (this.shown === show && !forceClose) { + return; + } + this.showAnimation(show, forceClose); + } + + private showAnimation(show: boolean = true, forceClose: boolean = false) { + if (forceClose && !show) { + this.showAnimationTween?.stop(); + } else if (this.showAnimationTween?.isPlaying()) { + return; + } + this.shown = show; + if (show) { + this.y += 50; + this.scale = 0.05; + this.alpha = 0; + } + this.showAnimationTween = this.scene.tweens.add({ + targets: [this], + duration: 350, + alpha: show ? 1 : 0, + y: this.defaultPosition.y, + scale: this.defaultScale, + ease: Easing.BackEaseOut, + onComplete: () => { + this.showAnimationTween = undefined; + }, + }); + } + + public isShown(): boolean { + return this.shown; + } +} diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index b411eee7..00067897 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -17,6 +17,7 @@ import { Unsubscriber, Writable, writable } from "svelte/store"; import { createColorStore } from "../../Stores/OutlineColorStore"; import type { OutlineableInterface } from "../Game/OutlineableInterface"; import type CancelablePromise from "cancelable-promise"; +import { TalkIcon } from "../Components/TalkIcon"; const playerNameY = -25; @@ -33,6 +34,7 @@ const interactiveRadius = 35; export abstract class Character extends Container implements OutlineableInterface { private bubble: SpeechBubble | null = null; private readonly playerNameText: Text; + private readonly talkIcon: TalkIcon; public playerName: string; public sprites: Map; protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down; @@ -102,6 +104,17 @@ export abstract class Character extends Container implements OutlineableInterfac fontSize: 35, }, }); + + this.talkIcon = new TalkIcon(scene, 0, -45); + this.add(this.talkIcon); + + if (isClickable) { + this.setInteractive({ + hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius), + hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method + useHandCursor: true, + }); + } this.playerNameText.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX); this.add(this.playerNameText); @@ -200,6 +213,10 @@ export abstract class Character extends Container implements OutlineableInterfac }); } + public showTalkIcon(show: boolean = true, forceClose: boolean = false): void { + this.talkIcon.show(show, forceClose); + } + public addCompanion(name: string, texturePromise?: CancelablePromise): void { if (typeof texturePromise !== "undefined") { this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise); diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 767527a7..5ecbf649 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -133,6 +133,18 @@ export class GameMap { return grid; } + public getWalkingCostGrid(): number[][] { + const grid: number[][] = []; + for (let y = 0; y < this.map.height; y += 1) { + const row: number[] = []; + for (let x = 0; x < this.map.width; x += 1) { + row.push(this.getWalkingCostAt(x, y)); + } + grid.push(row); + } + return grid; + } + public getTileDimensions(): { width: number; height: number } { return { width: this.map.tilewidth, height: this.map.tileheight }; } @@ -345,6 +357,32 @@ export class GameMap { return false; } + private getWalkingCostAt(x: number, y: number): number { + const bigCost = 100; + for (const layer of this.phaserLayers) { + if (!layer.visible) { + continue; + } + const tile = layer.getTileAt(x, y); + if (!tile) { + continue; + } + if ( + tile && + (tile.properties[GameMapProperties.EXIT_URL] || tile.properties[GameMapProperties.EXIT_SCENE_URL]) + ) { + return bigCost; + } + for (const property of layer.layer.properties) { + //@ts-ignore + if (property.name && property.name === "exitUrl") { + return bigCost; + } + } + } + return 0; + } + private triggerAllProperties(): void { const newProps = this.getProperties(this.key ?? 0); const oldProps = this.lastProperties; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 5c8826bc..6aaa34d3 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -91,6 +91,7 @@ import { MapStore } from "../../Stores/Utils/MapStore"; import { followUsersColorStore } from "../../Stores/FollowStore"; import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler"; import { locale } from "../../i18n/i18n-svelte"; +import { localVolumeStore } from "../../Stores/MediaStore"; import { StringUtils } from "../../Utils/StringUtils"; import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore"; import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite"; @@ -172,6 +173,9 @@ export class GameScene extends DirtyScene { private peerStoreUnsubscribe!: Unsubscriber; private emoteUnsubscribe!: Unsubscriber; private emoteMenuUnsubscribe!: Unsubscriber; + + private volumeStoreUnsubscribers: Map = new Map(); + private localVolumeStoreUnsubscriber: Unsubscriber | undefined; private followUsersColorStoreUnsubscribe!: Unsubscriber; private biggestAvailableAreaStoreUnsubscribe!: () => void; @@ -247,6 +251,7 @@ export class GameScene extends DirtyScene { loadCustomTexture(this.load, texture).catch((e) => console.error(e)); } } + this.load.image("iconTalk", "/resources/icons/icon_talking.png"); if (touchScreenManager.supportTouchScreen) { this.load.image(joystickBaseKey, joystickBaseImg); @@ -566,6 +571,7 @@ export class GameScene extends DirtyScene { this.pathfindingManager = new PathfindingManager( this, this.gameMap.getCollisionGrid(), + this.gameMap.getWalkingCostGrid(), this.gameMap.getTileDimensions() ); @@ -581,12 +587,6 @@ export class GameScene extends DirtyScene { waScaleManager ); - this.pathfindingManager = new PathfindingManager( - this, - this.gameMap.getCollisionGrid(), - this.gameMap.getTileDimensions() - ); - this.activatablesManager = new ActivatablesManager(this.CurrentPlayer); biggestAvailableAreaStore.recompute(); @@ -640,14 +640,45 @@ export class GameScene extends DirtyScene { this.connect(); } + const talkIconVolumeTreshold = 10; let oldPeerNumber = 0; this.peerStoreUnsubscribe = peerStore.subscribe((peers) => { + this.volumeStoreUnsubscribers.forEach((unsubscribe) => unsubscribe()); + this.volumeStoreUnsubscribers.clear(); + + for (const [key, videoStream] of peers) { + this.volumeStoreUnsubscribers.set( + key, + videoStream.volumeStore.subscribe((volume) => { + if (volume) { + this.MapPlayersByKey.get(key)?.showTalkIcon(volume > talkIconVolumeTreshold); + } + }) + ); + } + const newPeerNumber = peers.size; if (newPeerNumber > oldPeerNumber) { this.playSound("audio-webrtc-in"); } else if (newPeerNumber < oldPeerNumber) { this.playSound("audio-webrtc-out"); } + if (newPeerNumber > 0) { + if (!this.localVolumeStoreUnsubscriber) { + this.localVolumeStoreUnsubscriber = localVolumeStore.subscribe((volume) => { + if (volume) { + this.CurrentPlayer.showTalkIcon(volume > talkIconVolumeTreshold); + } + }); + } + } else { + this.CurrentPlayer.showTalkIcon(false, true); + this.MapPlayersByKey.forEach((remotePlayer) => remotePlayer.showTalkIcon(false, true)); + if (this.localVolumeStoreUnsubscriber) { + this.localVolumeStoreUnsubscriber(); + this.localVolumeStoreUnsubscriber = undefined; + } + } oldPeerNumber = newPeerNumber; }); @@ -1404,7 +1435,7 @@ ${escapedMessage} phaserLayer.setCollisionByProperty({ collides: true }, visible); } else { const phaserLayers = this.gameMap.findPhaserLayers(layerName + "/"); - if (phaserLayers === []) { + if (phaserLayers.length === 0) { console.warn( 'Could not find layer with name that contains "' + layerName + @@ -1417,7 +1448,7 @@ ${escapedMessage} phaserLayers[i].setCollisionByProperty({ collides: true }, visible); } } - this.pathfindingManager.setCollisionGrid(this.gameMap.getCollisionGrid()); + this.pathfindingManager.setCollisionGrid(this.gameMap.getCollisionGrid(), this.gameMap.getWalkingCostGrid()); this.markDirty(); } diff --git a/front/src/Stores/EmbedScreensStore.ts b/front/src/Stores/EmbedScreensStore.ts index 724733b3..172ec45b 100644 --- a/front/src/Stores/EmbedScreensStore.ts +++ b/front/src/Stores/EmbedScreensStore.ts @@ -15,7 +15,7 @@ export type EmbedScreen = }; function createHighlightedEmbedScreenStore() { - const { subscribe, set, update } = writable(null); + const { subscribe, set, update } = writable(undefined); return { subscribe, @@ -23,7 +23,7 @@ function createHighlightedEmbedScreenStore() { set(embedScreen); }, removeHighlight: () => { - set(null); + set(undefined); }, toggleHighlight: (embedScreen: EmbedScreen) => { update((currentEmbedScreen) => @@ -36,7 +36,7 @@ function createHighlightedEmbedScreenStore() { currentEmbedScreen.type === "streamable" && embedScreen.embed.uniqueId !== currentEmbedScreen.embed.uniqueId) ? embedScreen - : null + : undefined ); }, }; diff --git a/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts index f1b41430..9494eb7e 100644 --- a/front/src/Stores/MediaStore.ts +++ b/front/src/Stores/MediaStore.ts @@ -10,6 +10,8 @@ import { myCameraVisibilityStore } from "./MyCameraStoreVisibility"; import { peerStore } from "./PeerStore"; import { privacyShutdownStore } from "./PrivacyShutdownStore"; import { MediaStreamConstraintsError } from "./Errors/MediaStreamConstraintsError"; +import { SoundMeter } from "../Phaser/Components/SoundMeter"; +import { AudioContext } from "standardized-audio-context"; /** * A store that contains the camera state requested by the user (on or off). @@ -541,6 +543,41 @@ export const obtainedMediaConstraintStore = derived(undefined, (set) => { + let timeout: ReturnType; + const unsubscribe = localStreamStore.subscribe((localStreamStoreValue) => { + clearInterval(timeout); + if (localStreamStoreValue.type === "error") { + set(undefined); + return; + } + const mediaStream = localStreamStoreValue.stream; + + if (mediaStream === null || mediaStream.getAudioTracks().length <= 0) { + set(undefined); + return; + } + const soundMeter = new SoundMeter(mediaStream); + let error = false; + + timeout = setInterval(() => { + try { + set(soundMeter.getVolume()); + } catch (err) { + if (!error) { + console.error(err); + error = true; + } + } + }, 100); + }); + + return () => { + unsubscribe(); + clearInterval(timeout); + }; +}); + /** * Device list */ diff --git a/front/src/Utils/PathfindingManager.ts b/front/src/Utils/PathfindingManager.ts index c5057ed8..a129dae8 100644 --- a/front/src/Utils/PathfindingManager.ts +++ b/front/src/Utils/PathfindingManager.ts @@ -8,7 +8,12 @@ export class PathfindingManager { private grid: number[][]; private tileDimensions: { width: number; height: number }; - constructor(scene: Phaser.Scene, collisionsGrid: number[][], tileDimensions: { width: number; height: number }) { + constructor( + scene: Phaser.Scene, + collisionsGrid: number[][], + walkingCostGrid: number[][], + tileDimensions: { width: number; height: number } + ) { this.scene = scene; this.easyStar = new EasyStar.js(); @@ -17,10 +22,12 @@ export class PathfindingManager { this.grid = collisionsGrid; this.tileDimensions = tileDimensions; this.setEasyStarGrid(collisionsGrid); + this.setWalkingCostGrid(walkingCostGrid); } - public setCollisionGrid(collisionGrid: number[][]): void { + public setCollisionGrid(collisionGrid: number[][], walkingCostGrid: number[][]): void { this.setEasyStarGrid(collisionGrid); + this.setWalkingCostGrid(walkingCostGrid); } public async findPath( @@ -115,6 +122,14 @@ export class PathfindingManager { this.easyStar.setAcceptableTiles([0]); // zeroes are walkable } + private setWalkingCostGrid(grid: number[][]): void { + for (let y = 0; y < grid.length; y += 1) { + for (let x = 0; x < grid[y].length; x += 1) { + this.easyStar.setAdditionalPointCost(x, y, grid[y][x]); + } + } + } + private logGridToTheConsole(grid: number[][]): void { let rowNumber = 0; for (const row of grid) { diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index a5c57ed6..a33432dc 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -159,9 +159,17 @@ class CoWebsiteManager { }); buttonSwipe.addEventListener("click", () => { + const mainCoWebsite = this.getMainCoWebsite(); const highlightedEmbed = get(highlightedEmbedScreen); if (highlightedEmbed?.type === "cowebsite") { this.goToMain(highlightedEmbed.embed); + + if (mainCoWebsite) { + highlightedEmbedScreen.toggleHighlight({ + type: "cowebsite", + embed: mainCoWebsite, + }); + } } }); } @@ -553,6 +561,13 @@ class CoWebsiteManager { coWebsites.remove(coWebsite); coWebsites.add(coWebsite, 0); + if (mainCoWebsite) { + const iframe = mainCoWebsite.getIframe(); + if (iframe) { + iframe.style.display = "block"; + } + } + if ( isMediaBreakpointDown("lg") && get(embedScreenLayout) === LayoutMode.Presentation && @@ -596,12 +611,20 @@ class CoWebsiteManager { .load() .then(() => { const mainCoWebsite = this.getMainCoWebsite(); - if (mainCoWebsite && mainCoWebsite.getId() === coWebsite.getId()) { - this.openMain(); + const highlightedEmbed = get(highlightedEmbedScreen); + if (mainCoWebsite) { + if (mainCoWebsite.getId() === coWebsite.getId()) { + this.openMain(); - setTimeout(() => { - this.fire(); - }, animationTime); + setTimeout(() => { + this.fire(); + }, animationTime); + } else if (!highlightedEmbed) { + highlightedEmbedScreen.toggleHighlight({ + type: "cowebsite", + embed: coWebsite, + }); + } } this.resizeAllIframes(); }) diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index 29c5824e..a1ffa14c 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -9,6 +9,9 @@ import { playersStore } from "../Stores/PlayersStore"; import { chatMessagesStore, newChatMessageSubject } from "../Stores/ChatStore"; import { getIceServersConfig } from "../Components/Video/utils"; import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils"; +import { SoundMeter } from "../Phaser/Components/SoundMeter"; +import { AudioContext } from "standardized-audio-context"; +import { Console } from "console"; import Peer from "simple-peer/simplepeer.min.js"; import { Buffer } from "buffer"; @@ -32,6 +35,7 @@ export class VideoPeer extends Peer { private onBlockSubscribe: Subscription; private onUnBlockSubscribe: Subscription; public readonly streamStore: Readable; + public readonly volumeStore: Readable; public readonly statusStore: Readable; public readonly constraintsStore: Readable; private newMessageSubscribtion: Subscription | undefined; @@ -68,6 +72,34 @@ export class VideoPeer extends Peer { }; }); + this.volumeStore = readable(undefined, (set) => { + let timeout: ReturnType; + const unsubscribe = this.streamStore.subscribe((mediaStream) => { + if (mediaStream === null || mediaStream.getAudioTracks().length <= 0) { + set(undefined); + return; + } + const soundMeter = new SoundMeter(mediaStream); + let error = false; + + timeout = setInterval(() => { + try { + set(soundMeter.getVolume()); + } catch (err) { + if (!error) { + console.error(err); + error = true; + } + } + }, 100); + }); + + return () => { + unsubscribe(); + clearInterval(timeout); + }; + }); + this.constraintsStore = readable(null, (set) => { const onData = (chunk: Buffer) => { const message = JSON.parse(chunk.toString("utf8")); diff --git a/maps/tests/PathfinderAvoidExits/map1.json b/maps/tests/PathfinderAvoidExits/map1.json new file mode 100644 index 00000000..51361ced --- /dev/null +++ b/maps/tests/PathfinderAvoidExits/map1.json @@ -0,0 +1,177 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":10, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":10, + "id":2, + "name":"start", + "opacity":0.9, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17], + "height":10, + "id":7, + "name":"walls", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "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, 34, 34, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":8, + "name":"exit", + "opacity":1, + "properties":[ + { + "name":"exitUrl", + "type":"string", + "value":"map2.json" + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":10, + "id":9, + "name":"from_exit2", + "opacity":1, + "properties":[ + { + "name":"startLayer", + "type":"bool", + "value":true + }], + "type":"tilelayer", + "visible":true, + "width":10, + "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, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":10, + "name":"funritures", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":58.0929629319722, + "id":2, + "name":"", + "rotation":0, + "text": + { + "text":"DO NOT fall into the water or you will drown! Use right-click to move", + "wrap":true + }, + "type":"", + "visible":true, + "width":207.776169358213, + "x":78.9920090369007, + "y":34.4126432934483 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":11, + "nextobjectid":3, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.7.2", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"..\/tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tiles":[ + { + "id":16, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":17, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":18, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":19, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.6", + "width":10 +} \ No newline at end of file diff --git a/maps/tests/PathfinderAvoidExits/map2.json b/maps/tests/PathfinderAvoidExits/map2.json new file mode 100644 index 00000000..449de40f --- /dev/null +++ b/maps/tests/PathfinderAvoidExits/map2.json @@ -0,0 +1,184 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":10, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "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, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17], + "height":10, + "id":7, + "name":"walls", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "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, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":10, + "id":8, + "name":"exit", + "opacity":1, + "properties":[ + { + "name":"exitUrl", + "type":"string", + "value":"map1.json#from_exit2" + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":19, + "id":2, + "name":"", + "rotation":0, + "text": + { + "text":"YOU ", + "wrap":true + }, + "type":"", + "visible":true, + "width":33.1966362647477, + "x":143.254413856581, + "y":65.4728056229604 + }, + { + "height":39.3497615262321, + "id":3, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"MS Shell Dlg 2", + "pixelsize":32, + "text":"ARE", + "wrap":true + }, + "type":"", + "visible":true, + "width":81.3934036147603, + "x":130.134191004937, + "y":102.423688394277 + }, + { + "height":124.497486386076, + "id":4, + "name":"", + "rotation":0, + "text": + { + "color":"#ff0000", + "fontfamily":"MS Shell Dlg 2", + "pixelsize":96, + "text":"DEAD", + "wrap":true + }, + "type":"", + "visible":true, + "width":246.869092410677, + "x":41.7733861852564, + "y":157.582233294285 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":9, + "nextobjectid":5, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.7.2", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"..\/tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tiles":[ + { + "id":16, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":17, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":18, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":19, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.6", + "width":10 +} \ No newline at end of file diff --git a/maps/tests/index.html b/maps/tests/index.html index e8bf4369..3b3c700a 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -88,6 +88,14 @@ Test exits + + + Success Failure Pending + + + Test Pathfinder Avoid Exits + + Success Failure Pending diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index fe80eafa..89d3adf3 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -79,6 +79,7 @@ export class AuthenticateController extends BaseController { if (!code && !nonce) { res.writeStatus("200"); this.addCorsHeaders(res); + res.writeHeader("Content-Type", "application/json"); return res.end(JSON.stringify({ ...resUserData, authToken: token })); } console.error("Token cannot to be check on OpenId provider"); @@ -91,6 +92,7 @@ export class AuthenticateController extends BaseController { const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken); res.writeStatus("200"); this.addCorsHeaders(res); + res.writeHeader("Content-Type", "application/json"); return res.end(JSON.stringify({ ...resCheckTokenAuth, ...resUserData, authToken: token })); } catch (err) { console.info("User was not connected", err); @@ -121,6 +123,7 @@ export class AuthenticateController extends BaseController { res.writeStatus("200"); this.addCorsHeaders(res); + res.writeHeader("Content-Type", "application/json"); return res.end(JSON.stringify({ ...data, authToken })); } catch (e) { console.error("openIDCallback => ERROR", e); @@ -183,6 +186,7 @@ export class AuthenticateController extends BaseController { const authToken = jwtTokenManager.createAuthToken(email || userUuid); res.writeStatus("200 OK"); this.addCorsHeaders(res); + res.writeHeader("Content-Type", "application/json"); res.end( JSON.stringify({ authToken, @@ -222,6 +226,7 @@ export class AuthenticateController extends BaseController { const authToken = jwtTokenManager.createAuthToken(userUuid); res.writeStatus("200 OK"); this.addCorsHeaders(res); + res.writeHeader("Content-Type", "application/json"); res.end( JSON.stringify({ authToken, diff --git a/pusher/src/Controller/MapController.ts b/pusher/src/Controller/MapController.ts index 00936b44..eae205f9 100644 --- a/pusher/src/Controller/MapController.ts +++ b/pusher/src/Controller/MapController.ts @@ -47,6 +47,7 @@ export class MapController extends BaseController { if (!match) { res.writeStatus("404 Not Found"); this.addCorsHeaders(res); + res.writeHeader("Content-Type", "application/json"); res.end(JSON.stringify({})); return; } @@ -55,6 +56,7 @@ export class MapController extends BaseController { res.writeStatus("200 OK"); this.addCorsHeaders(res); + res.writeHeader("Content-Type", "application/json"); res.end( JSON.stringify({ mapUrl, @@ -106,6 +108,7 @@ export class MapController extends BaseController { res.writeStatus("200 OK"); this.addCorsHeaders(res); + res.writeHeader("Content-Type", "application/json"); res.end(JSON.stringify(mapDetails)); } catch (e) { this.errorToResponse(e, res);