Merge pull request #238 from moufmouf/layoutmanager
Adding LayoutManager to position videos according to chosen layout / importance
This commit is contained in:
commit
e1989c1c21
52
front/dist/index.html
vendored
52
front/dist/index.html
vendored
@ -39,7 +39,53 @@
|
|||||||
<title>WorkAdventure</title>
|
<title>WorkAdventure</title>
|
||||||
</head>
|
</head>
|
||||||
<body id="body" style="margin: 0">
|
<body id="body" style="margin: 0">
|
||||||
<div id="webRtc" class="webrtc">
|
<div class="main-container">
|
||||||
|
<div id="game" class="game" style="/*background: red;*/">
|
||||||
|
<div id="game-overlay" class="game-overlay" style="/*background: violet*/;">
|
||||||
|
<div id="main-section" class="main-section">
|
||||||
|
<!--<div style="background: lightpink;">a</div>
|
||||||
|
<div style="background: lightpink;">a</div> -->
|
||||||
|
</div>
|
||||||
|
<aside id="sidebar" class="sidebar">
|
||||||
|
<!--<div style="background: lightgreen;">a</div>
|
||||||
|
<div style="background: green;">b</div>
|
||||||
|
<div style="background: darkgreen;">c</div>
|
||||||
|
<div style="background: darkgreen;">d</div>-->
|
||||||
|
</aside>
|
||||||
|
<div id="chat-mode" class="chat-mode three-col" style="display: none;">
|
||||||
|
<!--<div style="background: lightgreen;">a</div>
|
||||||
|
<div style="background: green;">b</div>
|
||||||
|
<div style="background: darkgreen;">c</div>
|
||||||
|
<div style="background: darkolivegreen;">d</div>
|
||||||
|
<div style="background: darkolivegreen;">d</div>
|
||||||
|
<div style="background: darkgreen;">c</div>
|
||||||
|
<div style="background: green;">b</div>
|
||||||
|
<div style="background: lightgreen;">last elem for game</div>-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="activeCam" class="activeCam">
|
||||||
|
<div id="div-myCamVideo" class="video-container">
|
||||||
|
<video id="myCamVideo" autoplay muted></video>
|
||||||
|
</div>
|
||||||
|
<div class="btn-cam-action">
|
||||||
|
<div class="btn-micro">
|
||||||
|
<img id="microphone" src="resources/logos/microphone.svg">
|
||||||
|
<img id="microphone-close" src="resources/logos/microphone-close.svg">
|
||||||
|
</div>
|
||||||
|
<div class="btn-video">
|
||||||
|
<img id="cinema" src="resources/logos/cinema.svg">
|
||||||
|
<img id="cinema-close" src="resources/logos/cinema-close.svg">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="cowebsite" class="cowebsite"></div>
|
||||||
|
</div>
|
||||||
|
<!--
|
||||||
|
<div id="webRtc" class="webrtc">
|
||||||
<div id="activeCam" class="activeCam">
|
<div id="activeCam" class="activeCam">
|
||||||
<div id="div-myCamVideo" class="video-container">
|
<div id="div-myCamVideo" class="video-container">
|
||||||
<video id="myCamVideo" autoplay muted></video>
|
<video id="myCamVideo" autoplay muted></video>
|
||||||
@ -54,11 +100,9 @@
|
|||||||
<img id="cinema" src="resources/logos/cinema.svg">
|
<img id="cinema" src="resources/logos/cinema.svg">
|
||||||
<img id="cinema-close" src="resources/logos/cinema-close.svg">
|
<img id="cinema-close" src="resources/logos/cinema-close.svg">
|
||||||
</div>
|
</div>
|
||||||
<!--<div class="btn-call">
|
|
||||||
<img src="resources/logos/phone.svg">
|
|
||||||
</div>-->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
-->
|
||||||
<div id="webRtcSetup" class="webrtcsetup">
|
<div id="webRtcSetup" class="webrtcsetup">
|
||||||
<img id="webRtcSetupNoVideo" class="background-img" src="resources/logos/cinema-close.svg">
|
<img id="webRtcSetupNoVideo" class="background-img" src="resources/logos/cinema-close.svg">
|
||||||
<video id="myCamVideoSetup" autoplay muted></video>
|
<video id="myCamVideoSetup" autoplay muted></video>
|
||||||
|
BIN
front/dist/resources/objects/layout_modes.png
vendored
Normal file
BIN
front/dist/resources/objects/layout_modes.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 297 B |
226
front/dist/resources/style/style.css
vendored
226
front/dist/resources/style/style.css
vendored
@ -23,34 +23,12 @@ body .message-info.info{
|
|||||||
body .message-info.warning{
|
body .message-info.warning{
|
||||||
background: #ffa500d6;
|
background: #ffa500d6;
|
||||||
}
|
}
|
||||||
video{
|
.video-container{
|
||||||
-webkit-transform: scaleX(-1);
|
position: relative;
|
||||||
transform: scaleX(-1);
|
|
||||||
}
|
|
||||||
.webrtc{
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
right: 0px;
|
|
||||||
height: 100%;
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
.webrtc.active{
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.webrtc, .activeCam{}
|
|
||||||
.activeCam .video-container{
|
|
||||||
position: absolute;
|
|
||||||
height: 25%;
|
|
||||||
top: 10px;
|
|
||||||
margin: 5px;
|
|
||||||
right: -100px;
|
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
border-color: black;
|
background-color: #00000099;
|
||||||
border-style: solid;
|
|
||||||
border-width: 0.2px;
|
|
||||||
}
|
}
|
||||||
.activeCam .video-container i{
|
.video-container i{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 65px;
|
height: 65px;
|
||||||
@ -63,10 +41,10 @@ video{
|
|||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.activeCam .video-container img.active{
|
.video-container img.active{
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.activeCam .video-container img{
|
.video-container img{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: none;
|
display: none;
|
||||||
width: 15px;
|
width: 15px;
|
||||||
@ -78,36 +56,29 @@ video{
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
.activeCam .video-container video{
|
.video-container video{
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.webrtc:hover .activeCam .video-container{
|
.video-container#div-myCamVideo{
|
||||||
right: 10px;
|
|
||||||
}
|
|
||||||
.activeCam .video-container#div-myCamVideo{
|
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
.activeCam .video-container video#myCamVideo{
|
|
||||||
width: 200px;
|
#div-myCamVideo {
|
||||||
height: 113px;
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*CSS size for 2 - 3 elements*/
|
video#myCamVideo{
|
||||||
.activeCam .video-container:nth-child(1){
|
width: 15vw;
|
||||||
/*this is for camera of user*/
|
-webkit-transform: scaleX(-1);
|
||||||
top: 75%;
|
transform: scaleX(-1);
|
||||||
}
|
/*width: 200px;*/
|
||||||
.activeCam .video-container:nth-child(2){
|
/*height: 113px;*/
|
||||||
top: 0%;
|
|
||||||
}
|
|
||||||
.activeCam .video-container:nth-child(3){
|
|
||||||
top: 25%;
|
|
||||||
}
|
|
||||||
.activeCam .video-container:nth-child(4) {
|
|
||||||
top: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*btn animation*/
|
/*btn animation*/
|
||||||
.btn-cam-action div{
|
.btn-cam-action div{
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -118,11 +89,11 @@ video{
|
|||||||
background: #666;
|
background: #666;
|
||||||
box-shadow: 2px 2px 24px #444;
|
box-shadow: 2px 2px 24px #444;
|
||||||
border-radius: 48px;
|
border-radius: 48px;
|
||||||
transform: translateY(12vw);
|
transform: translateY(12vh);
|
||||||
transition-timing-function: ease-in-out;
|
transition-timing-function: ease-in-out;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
}
|
}
|
||||||
.webrtc:hover .btn-cam-action div{
|
#activeCam:hover .btn-cam-action div{
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
.btn-cam-action div:hover{
|
.btn-cam-action div:hover{
|
||||||
@ -237,3 +208,156 @@ video{
|
|||||||
.webrtcsetup.active{
|
.webrtcsetup.active{
|
||||||
display: block;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar > div {
|
||||||
|
max-height: 21%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-aspect-ratio: 1/1) {
|
||||||
|
.main-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-overlay {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar > div {
|
||||||
|
max-width: 21%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
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%;
|
||||||
|
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
padding: 1%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-mode div {
|
||||||
|
margin: 1%;
|
||||||
|
max-height: 96%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
@ -1,34 +1,33 @@
|
|||||||
import {GameManager, gameManager, HasMovedEvent} from "./GameManager";
|
import {GameManager, gameManager, HasMovedEvent} from "./GameManager";
|
||||||
import {
|
import {
|
||||||
Connection,
|
Connection,
|
||||||
GroupCreatedUpdatedMessageInterface, MessageUserJoined,
|
GroupCreatedUpdatedMessageInterface,
|
||||||
|
MessageUserJoined,
|
||||||
MessageUserMovedInterface,
|
MessageUserMovedInterface,
|
||||||
MessageUserPositionInterface, PointInterface, PositionInterface
|
MessageUserPositionInterface,
|
||||||
|
PointInterface,
|
||||||
|
PositionInterface
|
||||||
} from "../../Connection";
|
} from "../../Connection";
|
||||||
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player";
|
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player";
|
||||||
import { DEBUG_MODE, ZOOM_LEVEL, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
|
import {DEBUG_MODE, POSITION_DELAY, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable";
|
||||||
import {
|
import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledTileSet} from "../Map/ITiledMap";
|
||||||
ITiledMap,
|
|
||||||
ITiledMapLayer,
|
|
||||||
ITiledMapLayerProperty,
|
|
||||||
ITiledTileSet
|
|
||||||
} from "../Map/ITiledMap";
|
|
||||||
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character";
|
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 {AddPlayerInterface} from "./AddPlayerInterface";
|
||||||
import {PlayerAnimationNames} from "../Player/Animation";
|
import {PlayerAnimationNames} from "../Player/Animation";
|
||||||
import {PlayerMovement} from "./PlayerMovement";
|
import {PlayerMovement} from "./PlayerMovement";
|
||||||
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator";
|
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator";
|
||||||
import {RemotePlayer} from "../Entity/RemotePlayer";
|
import {RemotePlayer} from "../Entity/RemotePlayer";
|
||||||
import GameObject = Phaser.GameObjects.GameObject;
|
import {Queue} from 'queue-typescript';
|
||||||
import { Queue } from 'queue-typescript';
|
import {SimplePeer, UserSimplePeer} from "../../WebRtc/SimplePeer";
|
||||||
import {SimplePeer} from "../../WebRtc/SimplePeer";
|
|
||||||
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
|
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
|
||||||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
|
||||||
import {FourOFourSceneName} from "../Reconnecting/FourOFourScene";
|
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 {
|
export enum Textures {
|
||||||
@ -107,6 +106,9 @@ export class GameScene extends Phaser.Scene {
|
|||||||
|
|
||||||
private PositionNextScene: Array<Array<{ key: string, hash: string }>> = new Array<Array<{ key: string, hash: string }>>();
|
private PositionNextScene: Array<Array<{ key: string, hash: string }>> = new Array<Array<{ key: string, hash: string }>>();
|
||||||
private startLayerName: string|undefined;
|
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 {
|
static createFromUrl(mapUrlFile: string, instance: string, key: string|null = null): GameScene {
|
||||||
const mapKey = GameScene.getMapKeyByUrl(mapUrlFile);
|
const mapKey = GameScene.getMapKeyByUrl(mapUrlFile);
|
||||||
@ -159,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);
|
loadAllLayers(this.load);
|
||||||
|
|
||||||
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||||
@ -214,10 +222,24 @@ export class GameScene extends Phaser.Scene {
|
|||||||
|
|
||||||
this.scene.stop(this.scene.key);
|
this.scene.stop(this.scene.key);
|
||||||
this.scene.remove(this.scene.key);
|
this.scene.remove(this.scene.key);
|
||||||
|
window.removeEventListener('resize', this.repositionCallback);
|
||||||
})
|
})
|
||||||
|
|
||||||
// When connection is performed, let's connect SimplePeer
|
// When connection is performed, let's connect SimplePeer
|
||||||
this.simplePeer = new SimplePeer(this.connection);
|
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.wake();
|
||||||
this.scene.sleep(ReconnectingSceneName);
|
this.scene.sleep(ReconnectingSceneName);
|
||||||
@ -364,6 +386,41 @@ export class GameScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 UserInputManager class for input
|
||||||
|
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 {
|
private getExitSceneUrl(layer: ITiledMapLayer): string|undefined {
|
||||||
@ -625,6 +682,7 @@ export class GameScene extends Phaser.Scene {
|
|||||||
this.simplePeer.unregister();
|
this.simplePeer.unregister();
|
||||||
this.scene.stop();
|
this.scene.stop();
|
||||||
this.scene.remove(this.scene.key);
|
this.scene.remove(this.scene.key);
|
||||||
|
window.removeEventListener('resize', this.repositionCallback);
|
||||||
this.scene.start(nextSceneKey.key, {
|
this.scene.start(nextSceneKey.key, {
|
||||||
startLayerName: nextSceneKey.hash
|
startLayerName: nextSceneKey.hash
|
||||||
});
|
});
|
||||||
@ -812,4 +870,9 @@ export class GameScene extends Phaser.Scene {
|
|||||||
const endPos = mapUrlStart.indexOf(".json");
|
const endPos = mapUrlStart.indexOf(".json");
|
||||||
return mapUrlStart.substring(startPos, endPos);
|
return mapUrlStart.substring(startPos, endPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private reposition(): void {
|
||||||
|
this.presentationModeSprite.setY(this.game.renderer.height - 2);
|
||||||
|
this.chatModeSprite.setY(this.game.renderer.height - 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
56
front/src/WebRtc/CoWebsiteManager.ts
Normal file
56
front/src/WebRtc/CoWebsiteManager.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import {HtmlUtils} from "./HtmlUtils";
|
||||||
|
|
||||||
|
export type CoWebsiteStateChangedCallback = () => void;
|
||||||
|
|
||||||
|
export class CoWebsiteManager {
|
||||||
|
|
||||||
|
private static observers = new Array<CoWebsiteStateChangedCallback>();
|
||||||
|
|
||||||
|
public static loadCoWebsite(url: string): void {
|
||||||
|
const cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>("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<HTMLDivElement>("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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
front/src/WebRtc/HtmlUtils.ts
Normal file
10
front/src/WebRtc/HtmlUtils.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export class HtmlUtils {
|
||||||
|
public static getElementByIdOrFail<T extends HTMLElement>(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;
|
||||||
|
}
|
||||||
|
}
|
135
front/src/WebRtc/LayoutManager.ts
Normal file
135
front/src/WebRtc/LayoutManager.ts
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
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.
|
||||||
|
*/
|
||||||
|
class LayoutManager {
|
||||||
|
private mode: LayoutMode = LayoutMode.Presentation;
|
||||||
|
|
||||||
|
private importantDivs: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
|
||||||
|
private normalDivs: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
|
||||||
|
|
||||||
|
public add(importance: DivImportance, userId: string, html: string): void {
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
this.adjustVideoChatClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
private positionDiv(elem: HTMLDivElement, importance: DivImportance): void {
|
||||||
|
if (this.mode === LayoutMode.VideoChat) {
|
||||||
|
const chatModeDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('chat-mode');
|
||||||
|
chatModeDiv.appendChild(elem);
|
||||||
|
} else {
|
||||||
|
if (importance === DivImportance.Important) {
|
||||||
|
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-section');
|
||||||
|
mainSectionDiv.appendChild(elem);
|
||||||
|
} else if (importance === DivImportance.Normal) {
|
||||||
|
const sideBarDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('sidebar');
|
||||||
|
sideBarDiv.appendChild(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
this.importantDivs.delete(userId);
|
||||||
|
this.adjustVideoChatClass();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
div = this.normalDivs.get(userId);
|
||||||
|
if (div !== undefined) {
|
||||||
|
div.remove();
|
||||||
|
this.normalDivs.delete(userId);
|
||||||
|
this.adjustVideoChatClass();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Cannot remove userID '+userId+'. Already removed?');
|
||||||
|
//throw new Error('Could not find user ID "'+userId+'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
private adjustVideoChatClass(): void {
|
||||||
|
const chatModeDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public switchLayoutMode(layoutMode: LayoutMode) {
|
||||||
|
this.mode = layoutMode;
|
||||||
|
|
||||||
|
if (layoutMode === LayoutMode.Presentation) {
|
||||||
|
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('sidebar').style.display = 'flex';
|
||||||
|
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-section').style.display = 'flex';
|
||||||
|
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('chat-mode').style.display = 'none';
|
||||||
|
} else {
|
||||||
|
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('sidebar').style.display = 'none';
|
||||||
|
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-section').style.display = 'none';
|
||||||
|
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('chat-mode').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const div of this.importantDivs.values()) {
|
||||||
|
this.positionDiv(div, DivImportance.Important);
|
||||||
|
}
|
||||||
|
for (const div of this.normalDivs.values()) {
|
||||||
|
this.positionDiv(div, DivImportance.Normal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLayoutMode(): LayoutMode {
|
||||||
|
return this.mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const layoutManager = new LayoutManager();
|
||||||
|
|
||||||
|
export { layoutManager };
|
@ -1,3 +1,5 @@
|
|||||||
|
import {DivImportance, layoutManager} from "./LayoutManager";
|
||||||
|
|
||||||
const videoConstraint: boolean|MediaTrackConstraints = {
|
const videoConstraint: boolean|MediaTrackConstraints = {
|
||||||
width: { ideal: 1280 },
|
width: { ideal: 1280 },
|
||||||
height: { ideal: 720 },
|
height: { ideal: 720 },
|
||||||
@ -73,8 +75,8 @@ export class MediaManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activeVisio(){
|
activeVisio(){
|
||||||
const webRtc = this.getElementByIdOrFail('webRtc');
|
const gameOverlay = this.getElementByIdOrFail('game-overlay');
|
||||||
webRtc.classList.add('active');
|
gameOverlay.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
enabledCamera() {
|
enabledCamera() {
|
||||||
@ -184,18 +186,22 @@ export class MediaManager {
|
|||||||
*/
|
*/
|
||||||
addActiveVideo(userId : string, userName: string = ""){
|
addActiveVideo(userId : string, userName: string = ""){
|
||||||
this.webrtcInAudio.play();
|
this.webrtcInAudio.play();
|
||||||
const elementRemoteVideo = this.getElementByIdOrFail("activeCam");
|
|
||||||
userName = userName.toUpperCase();
|
userName = userName.toUpperCase();
|
||||||
const color = this.getColorByString(userName);
|
const color = this.getColorByString(userName);
|
||||||
elementRemoteVideo.insertAdjacentHTML('beforeend', `
|
|
||||||
<div id="div-${userId}" class="video-container" style="border-color: ${color};">
|
const html = `
|
||||||
|
<div id="div-${userId}" class="video-container">
|
||||||
<div class="connecting-spinner"></div>
|
<div class="connecting-spinner"></div>
|
||||||
<div class="rtc-error" style="display: none"></div>
|
<div class="rtc-error" style="display: none"></div>
|
||||||
<i style="background-color: ${color};">${userName}</i>
|
<i id="name-${userId}" style="background-color: ${color};">${userName}</i>
|
||||||
<img id="microphone-${userId}" src="resources/logos/microphone-close.svg">
|
<img id="microphone-${userId}" src="resources/logos/microphone-close.svg">
|
||||||
<video id="${userId}" autoplay></video>
|
<video id="${userId}" autoplay></video>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`;
|
||||||
|
|
||||||
|
layoutManager.add(DivImportance.Normal, userId, html);
|
||||||
|
|
||||||
this.remoteVideo.set(userId, this.getElementByIdOrFail<HTMLVideoElement>(userId));
|
this.remoteVideo.set(userId, this.getElementByIdOrFail<HTMLVideoElement>(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,11 +238,10 @@ export class MediaManager {
|
|||||||
if (element) {
|
if (element) {
|
||||||
element.style.opacity = "0";
|
element.style.opacity = "0";
|
||||||
}
|
}
|
||||||
element = document.getElementById(`div-${userId}`);
|
element = document.getElementById(`name-${userId}`);
|
||||||
if (!element) {
|
if (element) {
|
||||||
return;
|
element.style.display = "block";
|
||||||
}
|
}
|
||||||
element.style.borderStyle = "solid";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -248,11 +253,10 @@ export class MediaManager {
|
|||||||
if(element){
|
if(element){
|
||||||
element.style.opacity = "1";
|
element.style.opacity = "1";
|
||||||
}
|
}
|
||||||
element = document.getElementById(`div-${userId}`);
|
element = document.getElementById(`name-${userId}`);
|
||||||
if(!element){
|
if(element){
|
||||||
return;
|
element.style.display = "none";
|
||||||
}
|
}
|
||||||
element.style.borderStyle = "none";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -274,11 +278,7 @@ export class MediaManager {
|
|||||||
* @param userId
|
* @param userId
|
||||||
*/
|
*/
|
||||||
removeActiveVideo(userId : string){
|
removeActiveVideo(userId : string){
|
||||||
const element = document.getElementById(`div-${userId}`);
|
layoutManager.remove(userId);
|
||||||
if(!element){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
element.remove();
|
|
||||||
this.remoteVideo.delete(userId);
|
this.remoteVideo.delete(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,12 @@ export interface UserSimplePeer{
|
|||||||
initiator?: boolean;
|
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.
|
* This class manages connections to all the peers in the same group as me.
|
||||||
*/
|
*/
|
||||||
@ -24,6 +30,7 @@ export class SimplePeer {
|
|||||||
|
|
||||||
private PeerConnectionArray: Map<string, SimplePeerNamespace.Instance> = new Map<string, SimplePeerNamespace.Instance>();
|
private PeerConnectionArray: Map<string, SimplePeerNamespace.Instance> = new Map<string, SimplePeerNamespace.Instance>();
|
||||||
private readonly updateLocalStreamCallback: (media: MediaStream) => void;
|
private readonly updateLocalStreamCallback: (media: MediaStream) => void;
|
||||||
|
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
|
||||||
|
|
||||||
constructor(Connection: Connection, WebRtcRoomId: string = "test-webrtc") {
|
constructor(Connection: Connection, WebRtcRoomId: string = "test-webrtc") {
|
||||||
this.Connection = Connection;
|
this.Connection = Connection;
|
||||||
@ -34,6 +41,14 @@ export class SimplePeer {
|
|||||||
this.initialise();
|
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
|
* permit to listen when user could start visio
|
||||||
*/
|
*/
|
||||||
@ -182,6 +197,10 @@ export class SimplePeer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.addMedia(user.userId);
|
this.addMedia(user.userId);
|
||||||
|
|
||||||
|
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||||
|
peerConnectionListener.onConnect(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -203,6 +222,9 @@ export class SimplePeer {
|
|||||||
peer.destroy();
|
peer.destroy();
|
||||||
this.PeerConnectionArray.delete(userId)
|
this.PeerConnectionArray.delete(userId)
|
||||||
//console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size);
|
//console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size);
|
||||||
|
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||||
|
peerConnectionListener.onDisconnect(userId);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("closeConnection", err)
|
console.error("closeConnection", err)
|
||||||
}
|
}
|
||||||
|
@ -4,16 +4,21 @@ import {DEBUG_MODE, RESOLUTION} from "./Enum/EnvironmentVariable";
|
|||||||
import {cypressAsserter} from "./Cypress/CypressAsserter";
|
import {cypressAsserter} from "./Cypress/CypressAsserter";
|
||||||
import {LoginScene} from "./Phaser/Login/LoginScene";
|
import {LoginScene} from "./Phaser/Login/LoginScene";
|
||||||
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
|
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
|
||||||
import {gameManager} from "./Phaser/Game/GameManager";
|
|
||||||
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
|
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
|
||||||
import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene";
|
import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene";
|
||||||
import {FourOFourScene} from "./Phaser/Reconnecting/FourOFourScene";
|
import {FourOFourScene} from "./Phaser/Reconnecting/FourOFourScene";
|
||||||
import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
|
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 = {
|
const config: GameConfig = {
|
||||||
title: "Office game",
|
title: "WorkAdventure",
|
||||||
width: window.innerWidth / RESOLUTION,
|
width: width / RESOLUTION,
|
||||||
height: window.innerHeight / RESOLUTION,
|
height: height / RESOLUTION,
|
||||||
parent: "game",
|
parent: "game",
|
||||||
scene: [LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, FourOFourScene, CustomizeScene],
|
scene: [LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, FourOFourScene, CustomizeScene],
|
||||||
zoom: RESOLUTION,
|
zoom: RESOLUTION,
|
||||||
@ -30,5 +35,12 @@ cypressAsserter.gameStarted();
|
|||||||
const game = new Phaser.Game(config);
|
const game = new Phaser.Game(config);
|
||||||
|
|
||||||
window.addEventListener('resize', function (event) {
|
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);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user