Merge branch 'develop' into windows-focus-blur-camera

# Conflicts:
#	front/src/WebRtc/MediaManager.ts
This commit is contained in:
Gregoire Parant 2020-11-21 15:57:55 +01:00
commit a6876658ab
26 changed files with 801 additions and 210 deletions

77
front/dist/resources/logos/boy.svg vendored Normal file
View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 188.149 188.149" style="enable-background:new 0 0 188.149 188.149;" xml:space="preserve">
<g>
<g>
<defs>
<circle id="SVGID_1_" cx="94.075" cy="94.075" r="94.074"/>
</defs>
<use xlink:href="#SVGID_1_" style="overflow:visible;fill:#4AC8EB;"/>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<g style="clip-path:url(#SVGID_2_);">
<path style="fill:#A57561;" d="M148.572,197.629v0.01H39.507v-0.01c0-15.124,6.147-28.809,16.09-38.679
c0.463-0.463,0.926-0.905,1.408-1.347c1.43-1.326,2.931-2.57,4.493-3.732c1.028-0.771,2.098-1.491,3.177-2.189
c1.08-0.699,2.19-1.357,3.331-1.975c0.021-0.021,0.042-0.021,0.042-0.021c0.441-0.247,0.873-0.493,1.306-0.761
c1.295-0.761,2.519-1.624,3.69-2.56c4.966-3.948,8.812-9.254,11.001-15.34v-0.011c1.306-3.629,2.016-7.546,2.016-11.639
l16.121,0.072c0,4.04,0.688,7.927,1.964,11.525c2.169,6.117,5.994,11.433,10.96,15.401c0.339,0.277,0.688,0.545,1.038,0.802
c0.483,0.36,0.977,0.71,1.47,1.039c0.37,0.246,0.751,0.493,1.132,0.72c0.421,0.257,0.853,0.514,1.285,0.75
c0.041,0.011,0.071,0.021,0.103,0.041c0.03,0.02,0.062,0.041,0.093,0.061c1.265,0.699,2.498,1.439,3.69,2.221
c0.401,0.258,0.802,0.524,1.192,0.803c0.515,0.37,1.039,0.74,1.543,1.131h0.021C139.966,163.875,148.572,179.729,148.572,197.629
z"/>
<path style="fill:#EB6D4A;" d="M148.572,197.629H39.507c0-15.124,6.147-28.809,16.09-38.679c0.463-0.463,0.926-0.905,1.408-1.347
c1.43-1.326,2.931-2.581,4.493-3.742c1.028-0.762,2.098-1.491,3.177-2.18c1.08-0.699,2.19-1.357,3.331-1.975
c0.021-0.021,0.042-0.021,0.042-0.021c0.441-0.247,0.873-0.493,1.306-0.761c1.295-0.761,2.519-1.624,3.69-2.56
c5.347,5.469,12.79,8.852,21.046,8.852c8.226,0,15.669-3.393,21.016-8.842c0.339,0.277,0.688,0.545,1.038,0.802
c0.483,0.36,0.977,0.71,1.47,1.039c0.37,0.246,0.751,0.493,1.132,0.72c0.421,0.267,0.853,0.514,1.285,0.75
c0.041,0.011,0.071,0.021,0.103,0.041c0.03,0.011,0.062,0.031,0.093,0.052c1.265,0.699,2.498,1.439,3.69,2.23
c0.401,0.258,0.802,0.524,1.192,0.803c0.515,0.37,1.039,0.74,1.543,1.131h0.021C139.966,163.875,148.572,179.729,148.572,197.629
z"/>
<path style="fill:#A57561;" d="M52.183,46.81v34.117c0,28.977,25.437,52.466,41.857,52.466c16.421,0,41.858-23.489,41.858-52.466
V46.81H52.183z"/>
<path style="fill:#141720;" d="M52.183,76.823L52.183,76.823c2.063,0,3.734-1.671,3.734-3.733V49.356h-3.734V76.823z"/>
<path style="fill:#141720;" d="M135.899,76.823L135.899,76.823V49.356h-3.733V73.09
C132.165,75.152,133.836,76.823,135.899,76.823z"/>
<path style="fill:#141720;" d="M135.893,48.33c0,4.884-0.328,6.061-3.734,5.801c-0.137-2.367-17.141-4.296-38.111-4.296
c-20.985,0-37.989,1.929-38.126,4.296c-3.406,0.26-3.734-0.917-3.734-5.801c0-0.479,0.014-0.984,0.027-1.519
c0.52-12.052,7.318-34.582,41.833-34.582c34.5,0,41.299,22.53,41.818,34.582C135.879,47.346,135.893,47.852,135.893,48.33z"/>
</g>
<path style="clip-path:url(#SVGID_2_);fill:#FFFFFF;" d="M115.106,146.119c-3.517,10.826-10.601,21.539-21.036,30.299
c-10.436-8.76-17.509-19.473-21.025-30.299c0.052,0.02,0.113,0.041,0.165,0.061c5.531,4.955,12.852,7.979,20.86,7.979
c8.009,0,15.319-3.013,20.851-7.968C114.982,146.17,115.044,146.14,115.106,146.119z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1 @@
<svg height="682pt" viewBox="-21 -47 682.66669 682" width="682pt" xmlns="http://www.w3.org/2000/svg"><path d="m640 86.65625v283.972656c0 48.511719-39.472656 87.988282-87.988281 87.988282h-279.152344l-185.183594 128.863281v-128.863281c-48.375-.164063-87.675781-39.574219-87.675781-87.988282v-283.972656c0-48.515625 39.472656-87.988281 87.988281-87.988281h464.023438c48.515625 0 87.988281 39.472656 87.988281 87.988281zm0 0" fill="#ffdb2d"/><path d="m640 86.65625v283.972656c0 48.511719-39.472656 87.988282-87.988281 87.988282h-232.109375v-459.949219h232.109375c48.515625 0 87.988281 39.472656 87.988281 87.988281zm0 0" fill="#ffaa20"/><g fill="#fff"><path d="m171.296875 131.167969h297.40625v37.5h-297.40625zm0 0"/><path d="m171.296875 211.167969h297.40625v37.5h-297.40625zm0 0"/><path d="m171.296875 291.167969h297.40625v37.5h-297.40625zm0 0"/></g><path d="m319.902344 131.167969h148.800781v37.5h-148.800781zm0 0" fill="#e1e1e3"/><path d="m319.902344 211.167969h148.800781v37.5h-148.800781zm0 0" fill="#e1e1e3"/><path d="m319.902344 291.167969h148.800781v37.5h-148.800781zm0 0" fill="#e1e1e3"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,6 +1,14 @@
*{
font-family: 'Open Sans', sans-serif;
}
body{
overflow: hidden;
}
body button:focus,
body img:focus,
body input:focus {
outline: -webkit-focus-ring-color auto 0;
}
body .message-info{
width: 20%;
height: auto;
@ -72,14 +80,16 @@ body .message-info.warning{
#div-myCamVideo {
position: absolute;
right: 0;
bottom: 0;
right: 15px;
bottom: 15px;
border-radius: 15px 15px 15px 15px;
}
video#myCamVideo{
width: 15vw;
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
border-radius: 15px 15px 15px 15px;
/*width: 200px;*/
/*height: 113px;*/
}
@ -372,6 +382,7 @@ body {
margin: 2%;
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, max-height 0.2s, max-width 0.2s;
cursor: pointer;
border-radius: 15px 15px 15px 15px;
}
.sidebar > div:hover {
@ -384,6 +395,7 @@ body {
justify-content: center;
flex-direction: column;
overflow: hidden;
border-radius: 15px;
}
.chat-mode {
@ -435,7 +447,7 @@ body {
max-height: 80%;
top: -80%;
left: 10%;
background: #000000a6;
background: #333333;
z-index: 200;
transition: all 0.1s ease-out;
}
@ -532,7 +544,7 @@ body {
border: 1px solid black;
background-color: #00000000;
color: #ffda01;
border-radius: 10px;
border-radius: 15px;
padding: 10px 30px;
transition: all .2s ease;
}
@ -627,6 +639,7 @@ div.modal-report-user{
left: calc(50% - 400px);
top: 100px;
background-color: #000000ad;
border-radius: 15px;
}
.modal-report-user textarea{
@ -638,6 +651,7 @@ div.modal-report-user{
color: white;
width: calc(100% - 60px);
margin: 30px;
border-radius: 15px;
}
.modal-report-user img{
@ -669,7 +683,7 @@ div.modal-report-user{
border: 1px solid black;
background-color: #00000000;
color: #ffda01;
border-radius: 10px;
border-radius: 15px;
padding: 10px 30px;
transition: all .2s ease;
}
@ -701,3 +715,189 @@ div.modal-report-user{
max-width: calc(800px - 60px); /* size of modal - padding*/
}
/*MESSAGE*/
.discussion{
position: fixed;
left: -300px;
top: 0px;
width: 220px;
height: 100%;
background-color: #333333;
padding: 20px;
transition: all 0.5s ease;
}
.discussion.active{
left: 0;
}
.discussion .active-btn{
display: none;
cursor: pointer;
height: 50px;
width: 50px;
background-color: #2d2d2dba;
position: absolute;
top: calc(50% - 25px);
margin-left: 315px;
border-radius: 50%;
border: none;
transition: all 0.5s ease;
}
.discussion .active-btn.active{
display: block;
}
.discussion .active-btn:hover {
transform: scale(1.1) rotateY(3.142rad);
}
.discussion .active-btn img{
width: 26px;
height: 26px;
margin: 13px 5px;
}
.discussion .close-btn{
position: absolute;
top: 0;
right: 10px;
background: none;
border: none;
cursor: pointer;
}
.discussion .close-btn img{
height: 15px;
right: 15px;
}
.discussion p{
color: white;
font-size: 22px;
padding-left: 10px;
margin: 0;
}
.discussion .participants{
height: 200px;
margin: 10px 0;
}
.discussion .participants .participant{
display: flex;
margin: 5px 10px;
background-color: #ffffff69;
padding: 5px;
border-radius: 15px;
cursor: pointer;
}
.discussion .participants .participant:hover{
background-color: #ffffff;
}
.discussion .participants .participant:hover p{
color: black;
}
.discussion .participants .participant:before {
content: '';
height: 10px;
width: 10px;
background-color: #1e7e34;
position: absolute;
margin-left: 18px;
border-radius: 50%;
margin-top: 18px;
}
.discussion .participants .participant img{
width: 26px;
height: 26px;
}
.discussion .participants .participant p{
font-size: 16px;
margin-left: 10px;
margin-top: 2px;
}
.discussion .participants .participant button.report-btn{
cursor: pointer;
position: absolute;
background-color: #2d2d2dba;
right: 34px;
margin: 0px;
padding: 6px 0px;
border-radius: 15px;
border: none;
color: white;
width: 0px;
overflow: hidden;
transition: all .5s ease;
}
.discussion .participants .participant:hover button.report-btn{
width: 70px;
}
.discussion .messages{
position: absolute;
height: calc(100% - 360px);
overflow-x: hidden;
overflow-y: auto;
max-width: calc(100% - 40px);
width: calc(100% - 40px);
}
.discussion .messages h2{
color: white;
}
.discussion .messages .message{
margin: 5px;
float: right;
text-align: right;
width: 100%;
}
.discussion .messages .message.me{
float: left;
text-align: left;
}
.discussion .messages .message p{
font-size: 12px;
}
.discussion .messages .message p.body{
font-size: 16px;
overflow: hidden;
white-space: pre-wrap;
word-wrap: break-word;
}
.discussion .send-message{
position: absolute;
bottom: 45px;
width: 220px;
height: 26px;
}
.discussion .send-message input{
position: absolute;
width: calc(100% - 10px);
height: 20px;
background-color: #171717;
color: white;
border-radius: 15px;
border: none;
padding: 6px;
}
.discussion .send-message img{
position: absolute;
margin-right: 10px;
width: 20px;
height: 20px;
background-color: #ffffff69;
}
.discussion .send-message img:hover{
background-color: #ffffff;
}

View file

@ -1,12 +1,12 @@
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
const API_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.API_URL || "api.workadventure.localhost");
const ADMIN_URL = API_URL.replace('api', 'admin');
const ADMIN_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.ADMIN_URL || "admin.workadventure.localhost");
const TURN_SERVER: string = process.env.TURN_SERVER || "turn:numb.viagenie.ca";
const TURN_USER: string = process.env.TURN_USER || 'g.parant@thecodingmachine.com';
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || 'itcugcOHxle9Acqi$';
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true";
const RESOLUTION = 3;
const RESOLUTION = 2;
const ZOOM_LEVEL = 1/*3/4*/;
const POSITION_DELAY = 200; // Wait 200ms between sending position events
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player

View file

@ -342,51 +342,17 @@ export class GameScene extends ResizableScene implements CenterListener {
throw new Error('Your map MUST contain a layer of type "objectgroup" whose name is "floorLayer" that represents the layer characters are drawn at.');
}
// If there is an init position passed
if (this.initPosition !== null) {
this.startX = this.initPosition.x;
this.startY = this.initPosition.y;
} else {
// Now, let's find the start layer
if (this.room.hash) {
for (const layer of this.mapFile.layers) {
if (this.room.hash === layer.name && layer.type === 'tilelayer' && this.isStartLayer(layer)) {
const startPosition = this.startUser(layer);
this.startX = startPosition.x;
this.startY = startPosition.y;
}
}
}
if (this.startX === undefined) {
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
for (const layer of this.mapFile.layers) {
if (layer.type === 'tilelayer' && layer.name === "start") {
const startPosition = this.startUser(layer);
this.startX = startPosition.x;
this.startY = startPosition.y;
}
}
}
}
// Still no start position? Something is wrong with the map, we need a "start" layer.
if (this.startX === undefined) {
console.warn('This map is missing a layer named "start" that contains the available default start positions.');
// Let's start in the middle of the map
this.startX = this.mapFile.width * 16;
this.startY = this.mapFile.height * 16;
}
this.initStartXAndStartY();
//add entities
this.Objects = new Array<Phaser.Physics.Arcade.Sprite>();
//init event click
this.EventToClickOnTile();
//initialise list of other player
this.MapPlayers = this.physics.add.group({immovable: true});
//create input to move
this.userInputManager = new UserInputManager(this);
mediaManager.setUserInputManager(this.userInputManager);
//notify game manager can to create currentUser in map
this.createCurrentPlayer();
@ -574,7 +540,7 @@ export class GameScene extends ResizableScene implements CenterListener {
});
// When connection is performed, let's connect SimplePeer
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic);
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.GameManager.getPlayerName());
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
this.UserMessageManager = new UserMessageManager(this.connection);
@ -640,6 +606,10 @@ export class GameScene extends ResizableScene implements CenterListener {
}
private switchLayoutMode(): void {
//if discussion is activated, this layout cannot be activated
if(mediaManager.activatedDiscussion){
return;
}
const mode = layoutManager.getLayoutMode();
if (mode === LayoutMode.Presentation) {
layoutManager.switchLayoutMode(LayoutMode.VideoChat);
@ -651,6 +621,40 @@ export class GameScene extends ResizableScene implements CenterListener {
this.chatModeSprite.setFrame(3);
}
}
private initStartXAndStartY() {
// If there is an init position passed
if (this.initPosition !== null) {
this.startX = this.initPosition.x;
this.startY = this.initPosition.y;
} else {
// Now, let's find the start layer
if (this.room.hash) {
this.initPositionFromLayerName(this.room.hash);
}
if (this.startX === undefined) {
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
this.initPositionFromLayerName("start");
}
}
// Still no start position? Something is wrong with the map, we need a "start" layer.
if (this.startX === undefined) {
console.warn('This map is missing a layer named "start" that contains the available default start positions.');
// Let's start in the middle of the map
this.startX = this.mapFile.width * 16;
this.startY = this.mapFile.height * 16;
}
}
private initPositionFromLayerName(layerName: string) {
for (const layer of this.mapFile.layers) {
if (layerName === layer.name && layer.type === 'tilelayer' && (layerName === "start" || this.isStartLayer(layer))) {
const startPosition = this.startUser(layer);
this.startX = startPosition.x;
this.startY = startPosition.y;
}
}
}
private getExitUrl(layer: ITiledMapLayer): string|undefined {
return this.getProperty(layer, "exitUrl") as string|undefined;
@ -690,9 +694,6 @@ export class GameScene extends ResizableScene implements CenterListener {
instance = this.instance;
}
//console.log('existSceneUrl', exitSceneUrl);
//console.log('existSceneInstance', instance);
const absoluteExitSceneUrl = new URL(exitSceneUrl, this.MapUrlFile).href;
const absoluteExitSceneUrlWithoutProtocol = absoluteExitSceneUrl.toString().substr(absoluteExitSceneUrl.toString().indexOf('://')+3);
const roomId = '_/'+instance+'/'+absoluteExitSceneUrlWithoutProtocol;
@ -710,11 +711,6 @@ export class GameScene extends ResizableScene implements CenterListener {
this.loadNextGame(layer, mapWidth, fullPath);
}
/**
*
* @param layer
* @param mapWidth
*/
//todo: push that into the gameManager
private loadNextGame(layer: ITiledMapLayer, mapWidth: number, roomId: string){
@ -750,9 +746,6 @@ export class GameScene extends ResizableScene implements CenterListener {
}
}
/**
* @param layer
*/
private startUser(layer: ITiledMapLayer): PositionInterface {
const tiles = layer.data;
if (typeof(tiles) === 'string') {
@ -908,17 +901,6 @@ export class GameScene extends ResizableScene implements CenterListener {
});
}
EventToClickOnTile(){
// debug code to get a tile properties by clicking on it
/*this.input.on("pointerdown", (pointer: Phaser.Input.Pointer)=>{
//pixel position toz tile position
const tile = this.Map.getTileAt(this.Map.worldToTileX(pointer.worldX), this.Map.worldToTileY(pointer.worldY));
if(tile){
this.CurrentPlayer.say("Your touch " + tile.layer.name);
}
});*/
}
/**
* @param time
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
@ -963,13 +945,19 @@ export class GameScene extends ResizableScene implements CenterListener {
});
const nextSceneKey = this.checkToExit();
if (nextSceneKey) {
if (!nextSceneKey) return;
if (nextSceneKey.key !== this.scene.key) {
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
this.connection.closeConnection();
this.simplePeer.unregister();
this.scene.stop();
this.scene.remove(this.scene.key);
this.scene.start(nextSceneKey.key);
} else {
//if the exit points to the current map, we simply teleport the user back to the startLayer
this.initPositionFromLayerName(this.room.hash || 'start');
this.CurrentPlayer.x = this.startX;
this.CurrentPlayer.y = this.startY;
}
mediaManager.setLastUpdateScene();
@ -1073,11 +1061,6 @@ export class GameScene extends ResizableScene implements CenterListener {
await Promise.all(loadPromises);
player.addTextures(characterLayerList, 1);
//init collision
/*this.physics.add.collider(this.CurrentPlayer, player, (CurrentPlayer: CurrentGamerInterface, MapPlayer: GamerInterface) => {
CurrentPlayer.say("Hello, how are you ? ");
});*/
}
/**
@ -1120,7 +1103,6 @@ export class GameScene extends ResizableScene implements CenterListener {
// We do not update the player position directly (because it is sent only every 200ms).
// Instead we use the PlayersPositionInterpolator that will do a smooth animation over the next 200ms.
const playerMovement = new PlayerMovement({ x: player.x, y: player.y }, this.currentTick, message.position, this.currentTick + POSITION_DELAY);
//console.log('Target position: ', player.x, player.y);
this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement);
}
@ -1169,11 +1151,6 @@ export class GameScene extends ResizableScene implements CenterListener {
/**
* Sends to the server an event emitted by one of the ActionableItems.
*
* @param itemId
* @param eventName
* @param state
* @param parameters
*/
emitActionableEvent(itemId: number, eventName: string, state: unknown, parameters: unknown) {
this.connection.emitActionableEvent(itemId, eventName, state, parameters);

View file

@ -7,6 +7,7 @@ import {mediaManager} from "../../WebRtc/MediaManager";
import {RESOLUTION} from "../../Enum/EnvironmentVariable";
import {SoundMeter} from "../Components/SoundMeter";
import {SoundMeterSprite} from "../Components/SoundMeterSprite";
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
export const EnableCameraSceneName = "EnableCameraScene";
enum LoginTextures {
@ -93,7 +94,7 @@ export class EnableCameraScene extends Phaser.Scene {
this.login();
});
this.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').classList.add('active');
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').classList.add('active');
const mediaPromise = mediaManager.getCamera();
mediaPromise.then(this.getDevices.bind(this));
@ -150,10 +151,10 @@ export class EnableCameraScene extends Phaser.Scene {
* Function called each time a camera is changed
*/
private setupStream(stream: MediaStream): void {
const img = this.getElementByIdOrFail<HTMLDivElement>('webRtcSetupNoVideo');
const img = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetupNoVideo');
img.style.display = 'none';
const div = this.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
div.srcObject = stream;
this.soundMeter.connectToSource(stream, new window.AudioContext());
@ -164,7 +165,7 @@ export class EnableCameraScene extends Phaser.Scene {
private updateWebCamName(): void {
if (this.camerasList.length > 1) {
const div = this.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
let label = this.camerasList[this.cameraSelected].label;
// remove text in parenthesis
@ -211,10 +212,10 @@ export class EnableCameraScene extends Phaser.Scene {
}
private reposition(): void {
let div = this.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
let div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
let bounds = div.getBoundingClientRect();
if (!div.srcObject) {
div = this.getElementByIdOrFail<HTMLVideoElement>('webRtcSetup');
div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('webRtcSetup');
bounds = div.getBoundingClientRect();
}
@ -255,7 +256,7 @@ export class EnableCameraScene extends Phaser.Scene {
}
private login(): void {
this.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
this.soundMeter.stop();
window.removeEventListener('resize', this.repositionCallback);
@ -276,13 +277,4 @@ export class EnableCameraScene extends Phaser.Scene {
}
this.updateWebCamName();
}
private 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;
}
}

View file

@ -20,32 +20,31 @@ class CoWebsiteManager {
* Quickly going in and out of an iframe trigger can create conflicts between the iframe states.
* So we use this promise to queue up every cowebsite state transition
*/
private currentOperationPromise: Promise<void> = Promise.resolve();
private currentOperationPromise: Promise<void> = Promise.resolve();
private cowebsiteDiv: HTMLDivElement;
private close(): HTMLDivElement {
const cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition
cowebsiteDiv.classList.add('hidden');
constructor() {
this.cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
}
private close(): void {
this.cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition
this.cowebsiteDiv.classList.add('hidden');
this.opened = iframeStates.closed;
return cowebsiteDiv;
}
private load(): HTMLDivElement {
const cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
cowebsiteDiv.classList.remove('hidden'); //edit the css class to trigger the transition
cowebsiteDiv.classList.add('loading');
private load(): void {
this.cowebsiteDiv.classList.remove('hidden'); //edit the css class to trigger the transition
this.cowebsiteDiv.classList.add('loading');
this.opened = iframeStates.loading;
return cowebsiteDiv;
}
private open(): HTMLDivElement {
const cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
cowebsiteDiv.classList.remove('loading', 'hidden'); //edit the css class to trigger the transition
private open(): void {
this.cowebsiteDiv.classList.remove('loading', 'hidden'); //edit the css class to trigger the transition
this.opened = iframeStates.opened;
return cowebsiteDiv;
}
public loadCoWebsite(url: string): void {
const cowebsiteDiv = this.load();
cowebsiteDiv.innerHTML = '';
this.load();
this.cowebsiteDiv.innerHTML = '';
const iframe = document.createElement('iframe');
iframe.id = 'cowebsite-iframe';
@ -53,7 +52,7 @@ class CoWebsiteManager {
const onloadPromise = new Promise((resolve) => {
iframe.onload = () => resolve();
});
cowebsiteDiv.appendChild(iframe);
this.cowebsiteDiv.appendChild(iframe);
const onTimeoutPromise = new Promise((resolve) => {
setTimeout(() => resolve(), 2000);
});
@ -69,23 +68,23 @@ class CoWebsiteManager {
* Just like loadCoWebsite but the div can be filled by the user.
*/
public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise<void>): void {
const cowebsiteDiv = this.load();
this.currentOperationPromise = this.currentOperationPromise.then(() => callback(cowebsiteDiv)).then(() => {
this.load();
this.currentOperationPromise = this.currentOperationPromise.then(() => callback(this.cowebsiteDiv)).then(() => {
this.open();
setTimeout(() => {
this.fire();
}, animationTime)
}, animationTime);
}).catch(() => this.closeCoWebsite());
}
public closeCoWebsite(): Promise<void> {
this.currentOperationPromise = this.currentOperationPromise.then(() => new Promise((resolve, reject) => {
if(this.opened === iframeStates.closed) resolve(); //this method may be called twice, in case of iframe error for example
const cowebsiteDiv = this.close();
this.close();
this.fire();
setTimeout(() => {
this.cowebsiteDiv.innerHTML = '';
resolve();
setTimeout(() => cowebsiteDiv.innerHTML = '', 500)
}, animationTime)
}));
return this.currentOperationPromise;
@ -111,6 +110,7 @@ class CoWebsiteManager {
}
}
//todo: is it still useful to allow any kind of observers?
public onStateChange(observer: CoWebsiteStateChangedCallback) {
this.observers.push(observer);
}

View file

@ -0,0 +1,231 @@
import {HtmlUtils} from "./HtmlUtils";
import {MediaManager, ReportCallback, UpdatedLocalStreamCallback} from "./MediaManager";
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
export type SendMessageCallback = (message:string) => void;
export class DiscussionManager {
private mainContainer: HTMLDivElement;
private divDiscuss?: HTMLDivElement;
private divParticipants?: HTMLDivElement;
private nbpParticipants?: HTMLParagraphElement;
private divMessages?: HTMLParagraphElement;
private buttonActiveDiscussion?: HTMLButtonElement;
private participants: Map<number|string, HTMLDivElement> = new Map<number|string, HTMLDivElement>();
private activeDiscussion: boolean = false;
private sendMessageCallBack : Map<number|string, SendMessageCallback> = new Map<number|string, SendMessageCallback>();
private userInputManager?: UserInputManager;
constructor(private mediaManager: MediaManager, name: string) {
this.mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
this.createDiscussPart(name);
}
private createDiscussPart(name: string) {
this.divDiscuss = document.createElement('div');
this.divDiscuss.classList.add('discussion');
const buttonCloseDiscussion: HTMLButtonElement = document.createElement('button');
this.buttonActiveDiscussion = document.createElement('button');
buttonCloseDiscussion.classList.add('close-btn');
buttonCloseDiscussion.innerHTML = `<img src="resources/logos/close.svg"/>`;
buttonCloseDiscussion.addEventListener('click', () => {
this.hideDiscussion();
this.showButtonDiscussionBtn();
});
this.buttonActiveDiscussion.classList.add('active-btn');
this.buttonActiveDiscussion.innerHTML = `<img src="resources/logos/discussion.svg"/>`;
this.buttonActiveDiscussion.addEventListener('click', () => {
this.showDiscussionPart();
});
this.divDiscuss.appendChild(buttonCloseDiscussion);
this.divDiscuss.appendChild(this.buttonActiveDiscussion);
const myName: HTMLParagraphElement = document.createElement('p');
myName.innerText = name.toUpperCase();
this.nbpParticipants = document.createElement('p');
this.nbpParticipants.innerText = 'PARTICIPANTS (1)';
this.divParticipants = document.createElement('div');
this.divParticipants.classList.add('participants');
this.divMessages = document.createElement('div');
this.divMessages.classList.add('messages');
this.divMessages.innerHTML = "<h2>Local messages</h2>"
this.divDiscuss.appendChild(myName);
this.divDiscuss.appendChild(this.nbpParticipants);
this.divDiscuss.appendChild(this.divParticipants);
this.divDiscuss.appendChild(this.divMessages);
const sendDivMessage: HTMLDivElement = document.createElement('div');
sendDivMessage.classList.add('send-message');
const inputMessage: HTMLInputElement = document.createElement('input');
inputMessage.type = "text";
inputMessage.addEventListener('keyup', (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault();
if(inputMessage.value === null
|| inputMessage.value === ''
|| inputMessage.value === undefined) {
return;
}
this.addMessage(name, inputMessage.value, true);
for(const callback of this.sendMessageCallBack.values()) {
callback(inputMessage.value);
}
inputMessage.value = "";
}
});
sendDivMessage.appendChild(inputMessage);
this.divDiscuss.appendChild(sendDivMessage);
//append in main container
this.mainContainer.appendChild(this.divDiscuss);
this.addParticipant('me', 'Moi', undefined, true);
}
public addParticipant(
userId: number|string,
name: string|undefined,
img?: string|undefined,
isMe: boolean = false,
reportCallback?: ReportCallback
) {
const divParticipant: HTMLDivElement = document.createElement('div');
divParticipant.classList.add('participant');
divParticipant.id = `participant-${userId}`;
const divImgParticipant: HTMLImageElement = document.createElement('img');
divImgParticipant.src = 'resources/logos/boy.svg';
if (img !== undefined) {
divImgParticipant.src = img;
}
const divPParticipant: HTMLParagraphElement = document.createElement('p');
if(!name){
name = 'Anonymous';
}
divPParticipant.innerText = name;
divParticipant.appendChild(divImgParticipant);
divParticipant.appendChild(divPParticipant);
if(!isMe) {
const reportBanUserAction: HTMLButtonElement = document.createElement('button');
reportBanUserAction.classList.add('report-btn')
reportBanUserAction.innerText = 'Report';
reportBanUserAction.addEventListener('click', () => {
if(reportCallback) {
this.mediaManager.showReportModal(`${userId}`, name ?? '', reportCallback);
}else{
console.info('report feature is not activated!');
}
});
divParticipant.appendChild(reportBanUserAction);
}
this.divParticipants?.appendChild(divParticipant);
this.participants.set(userId, divParticipant);
this.showButtonDiscussionBtn();
this.updateParticipant(this.participants.size);
}
public updateParticipant(nb: number) {
if (!this.nbpParticipants) {
return;
}
this.nbpParticipants.innerText = `PARTICIPANTS (${nb})`;
}
public addMessage(name: string, message: string, isMe: boolean = false) {
const divMessage: HTMLDivElement = document.createElement('div');
divMessage.classList.add('message');
if(isMe){
divMessage.classList.add('me');
}
const pMessage: HTMLParagraphElement = document.createElement('p');
const date = new Date();
if(isMe){
name = 'Moi';
}
pMessage.innerHTML = `<span style="font-weight: bold">${name}</span>
<span style="color:#bac2cc;display:inline-block;font-size:12px;">
${date.getHours()}:${date.getMinutes()}
</span>`;
divMessage.appendChild(pMessage);
const userMessage: HTMLParagraphElement = document.createElement('p');
userMessage.innerText = message;
userMessage.classList.add('body');
divMessage.appendChild(userMessage);
this.divMessages?.appendChild(divMessage);
}
public removeParticipant(userId: number|string){
const element = this.participants.get(userId);
if(element){
element.remove();
this.participants.delete(userId);
}
//if all participant leave, hide discussion button
if(this.participants.size === 1){
this.hideButtonDiscussionBtn();
}
this.sendMessageCallBack.delete(userId);
}
public onSendMessageCallback(userId: string|number, callback: SendMessageCallback): void {
this.sendMessageCallBack.set(userId, callback);
}
get activatedDiscussion(){
return this.activeDiscussion;
}
private showButtonDiscussionBtn(){
//if it's first participant, show discussion button
if(this.activatedDiscussion || this.participants.size === 1) {
return;
}
this.buttonActiveDiscussion?.classList.add('active');
}
private showDiscussion(){
this.activeDiscussion = true;
if(this.userInputManager) {
this.userInputManager.clearAllInputKeyboard();
}
this.divDiscuss?.classList.add('active');
}
private hideDiscussion(){
this.activeDiscussion = false;
if(this.userInputManager) {
this.userInputManager.initKeyBoardEvent();
}
this.divDiscuss?.classList.remove('active');
}
private hideButtonDiscussionBtn(){
this.buttonActiveDiscussion?.classList.remove('active');
}
public setUserInputManager(userInputManager : UserInputManager){
this.userInputManager = userInputManager;
}
public showDiscussionPart(){
this.showDiscussion();
this.hideButtonDiscussionBtn();
}
}

View file

@ -50,8 +50,9 @@ class JitsiFactory {
delete options.jwt;
}
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
options.onload = () => resolve(); //we want for the iframe to be loaded before triggering animations.
setTimeout(() => resolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
this.jitsiApi.executeCommand('displayName', playerName);

View file

@ -1,5 +1,7 @@
import {DivImportance, layoutManager} from "./LayoutManager";
import {HtmlUtils} from "./HtmlUtils";
import {DiscussionManager, SendMessageCallback} from "./DiscussionManager";
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
const videoConstraint: boolean|MediaTrackConstraints = {
@ -44,53 +46,57 @@ export class MediaManager {
private lastUpdateScene : Date = new Date();
private setTimeOutlastUpdateScene? : NodeJS.Timeout;
private discussionManager: DiscussionManager;
private userInputManager?: UserInputManager;
private hasCamera = true;
constructor() {
this.myCamVideo = this.getElementByIdOrFail<HTMLVideoElement>('myCamVideo');
this.webrtcInAudio = this.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-in');
this.myCamVideo = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideo');
this.webrtcInAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-in');
this.webrtcInAudio.volume = 0.2;
this.microphoneBtn = this.getElementByIdOrFail<HTMLDivElement>('btn-micro');
this.microphoneClose = this.getElementByIdOrFail<HTMLImageElement>('microphone-close');
this.microphoneBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-micro');
this.microphoneClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone-close');
this.microphoneClose.style.display = "none";
this.microphoneClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.enableMicrophone();
//update tracking
});
this.microphone = this.getElementByIdOrFail<HTMLImageElement>('microphone');
this.microphone = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone');
this.microphone.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.disableMicrophone();
//update tracking
});
this.cinemaBtn = this.getElementByIdOrFail<HTMLDivElement>('btn-video');
this.cinemaClose = this.getElementByIdOrFail<HTMLImageElement>('cinema-close');
this.cinemaBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-video');
this.cinemaClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema-close');
this.cinemaClose.style.display = "none";
this.cinemaClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.enableCamera();
//update tracking
});
this.cinema = this.getElementByIdOrFail<HTMLImageElement>('cinema');
this.cinema = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema');
this.cinema.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.disableCamera();
//update tracking
});
this.monitorBtn = this.getElementByIdOrFail<HTMLDivElement>('btn-monitor');
this.monitorClose = this.getElementByIdOrFail<HTMLImageElement>('monitor-close');
this.monitorBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-monitor');
this.monitorClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor-close');
this.monitorClose.style.display = "block";
this.monitorClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.enableScreenSharing();
//update tracking
});
this.monitor = this.getElementByIdOrFail<HTMLImageElement>('monitor');
this.monitor = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor');
this.monitor.style.display = "none";
this.monitor.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
@ -102,6 +108,8 @@ export class MediaManager {
this.pingCameraStatus();
this.checkActiveUser();
this.discussionManager = new DiscussionManager(this,'');
}
public setLastUpdateScene(){
@ -160,12 +168,12 @@ export class MediaManager {
}
public showGameOverlay(){
const gameOverlay = this.getElementByIdOrFail('game-overlay');
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
gameOverlay.classList.add('active');
}
public hideGameOverlay(){
const gameOverlay = this.getElementByIdOrFail('game-overlay');
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
gameOverlay.classList.remove('active');
}
@ -432,14 +440,17 @@ export class MediaManager {
layoutManager.add(DivImportance.Normal, userId, html);
if (reportCallBack) {
const reportBtn = this.getElementByIdOrFail<HTMLDivElement>(`report-${userId}`);
const reportBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(`report-${userId}`);
reportBtn.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
this.showReportModal(userId, userName, reportCallBack);
});
}
this.remoteVideo.set(userId, this.getElementByIdOrFail<HTMLVideoElement>(userId));
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
//permit to create participant in discussion part
this.addNewParticipant(userId, userName, undefined, reportCallBack);
}
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
@ -453,7 +464,7 @@ export class MediaManager {
layoutManager.add(divImportance, userId, html);
this.remoteVideo.set(userId, this.getElementByIdOrFail<HTMLVideoElement>(userId));
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
}
disabledMicrophoneByUserId(userId: number){
@ -514,6 +525,9 @@ export class MediaManager {
removeActiveVideo(userId: string){
layoutManager.remove(userId);
this.remoteVideo.delete(userId);
//permit to remove user in discussion part
this.removeParticipant(userId);
}
removeActiveScreenSharingVideo(userId: string) {
this.removeActiveVideo(`screen-sharing-${userId}`)
@ -576,18 +590,9 @@ export class MediaManager {
return color;
}
private 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;
}
public showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){
//create report text area
const mainContainer = this.getElementByIdOrFail<HTMLDivElement>('main-container');
const mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
const divReport = document.createElement('div');
divReport.classList.add('modal-report-user');
@ -642,6 +647,13 @@ export class MediaManager {
mainContainer.appendChild(divReport);
}
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, reportCallBack?: ReportCallback){
this.discussionManager.addParticipant(userId, name, img, false, reportCallBack);
}
public removeParticipant(userId: number|string){
this.discussionManager.removeParticipant(userId);
}
/**
* For some reasons, the microphone muted icon or the stream is not always up to date.
* Here, every 30 seconds, we are "reseting" the streams and sending again the constraints to the other peers via the data channel again (see SimplePeer::pushVideoToRemoteUser)
@ -656,6 +668,26 @@ export class MediaManager {
}, 30000);
}
public addNewMessage(name: string, message: string, isMe: boolean = false){
this.discussionManager.addMessage(name, message, isMe);
//when there are new message, show discussion
if(!this.discussionManager.activatedDiscussion) {
this.discussionManager.showDiscussionPart();
}
}
public addSendMessageCallback(userId: string|number, callback: SendMessageCallback){
this.discussionManager.onSendMessageCallback(userId, callback);
}
get activatedDiscussion(){
return this.discussionManager.activatedDiscussion;
}
public setUserInputManager(userInputManager : UserInputManager){
this.discussionManager.setUserInputManager(userInputManager);
}
//check if user is active
private checkActiveUser(){
if(this.setTimeOutlastUpdateScene){

View file

@ -2,6 +2,7 @@ import * as SimplePeerNamespace from "simple-peer";
import {mediaManager} from "./MediaManager";
import {TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
import {RoomConnection} from "../Connexion/RoomConnection";
import {MESSAGE_TYPE_CONSTRAINT} from "./VideoPeer";
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
@ -148,6 +149,6 @@ export class ScreenSharingPeer extends Peer {
public stopPushingScreenSharingToRemoteUser(stream: MediaStream) {
this.removeStream(stream);
this.write(new Buffer(JSON.stringify({streamEnded: true})));
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, streamEnded: true})));
}
}

View file

@ -10,7 +10,7 @@ import {
UpdatedLocalStreamCallback
} from "./MediaManager";
import {ScreenSharingPeer} from "./ScreenSharingPeer";
import {VideoPeer} from "./VideoPeer";
import {MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer";
import {RoomConnection} from "../Connexion/RoomConnection";
export interface UserSimplePeerInterface{
@ -38,7 +38,7 @@ export class SimplePeer {
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
constructor(private Connection: RoomConnection, private enableReporting: boolean) {
constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) {
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
this.sendLocalVideoStreamCallback = this.sendLocalVideoStream.bind(this);
this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this);
@ -145,6 +145,12 @@ export class SimplePeer {
mediaManager.addActiveVideo("" + user.userId, reportCallback, name);
const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
//permit to send message
mediaManager.addSendMessageCallback(user.userId,(message: string) => {
peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), message: message})));
});
peer.toClose = false;
// When a connection is established to a video stream, and if a screen sharing is taking place,
// the user sharing screen should also initiate a connection to the remote user!
@ -318,7 +324,7 @@ export class SimplePeer {
throw new Error('While adding media, cannot find user with ID ' + userId);
}
const localStream: MediaStream | null = mediaManager.localStream;
PeerConnection.write(new Buffer(JSON.stringify(mediaManager.constraintsMedia)));
PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...mediaManager.constraintsMedia})));
if(!localStream){
return;

View file

@ -5,6 +5,8 @@ import {RoomConnection} from "../Connexion/RoomConnection";
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
export const MESSAGE_TYPE_CONSTRAINT = 'constraint';
export const MESSAGE_TYPE_MESSAGE = 'message';
/**
* A peer connection used to transmit video / audio signals between 2 peers.
*/
@ -78,19 +80,27 @@ export class VideoPeer extends Peer {
});
this.on('data', (chunk: Buffer) => {
const constraint = JSON.parse(chunk.toString('utf8'));
console.log("data", constraint);
if (constraint.audio) {
mediaManager.enabledMicrophoneByUserId(this.userId);
} else {
mediaManager.disabledMicrophoneByUserId(this.userId);
const message = JSON.parse(chunk.toString('utf8'));
console.log("data", message);
if(message.type === MESSAGE_TYPE_CONSTRAINT) {
const constraint = message;
if (constraint.audio) {
mediaManager.enabledMicrophoneByUserId(this.userId);
} else {
mediaManager.disabledMicrophoneByUserId(this.userId);
}
if (constraint.video || constraint.screen) {
mediaManager.enabledVideoByUserId(this.userId);
} else {
this.stream(undefined);
mediaManager.disabledVideoByUserId(this.userId);
}
}
if (constraint.video || constraint.screen) {
mediaManager.enabledVideoByUserId(this.userId);
} else {
this.stream(undefined);
mediaManager.disabledVideoByUserId(this.userId);
if(message.type === 'message') {
mediaManager.addNewMessage(message.name, message.message);
}
});
@ -163,7 +173,7 @@ export class VideoPeer extends Peer {
private pushVideoToRemoteUser() {
try {
const localStream: MediaStream | null = mediaManager.localStream;
this.write(new Buffer(JSON.stringify(mediaManager.constraintsMedia)));
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...mediaManager.constraintsMedia})));
if(!localStream){
return;

View file

@ -45,7 +45,7 @@ module.exports = {
new webpack.ProvidePlugin({
Phaser: 'phaser'
}),
new webpack.EnvironmentPlugin(['API_URL', 'DEBUG_MODE', 'TURN_SERVER', 'TURN_USER', 'TURN_PASSWORD', 'JITSI_URL', 'JITSI_PRIVATE_MODE'])
new webpack.EnvironmentPlugin(['API_URL', 'ADMIN_URL', 'DEBUG_MODE', 'TURN_SERVER', 'TURN_USER', 'TURN_PASSWORD', 'JITSI_URL', 'JITSI_PRIVATE_MODE'])
],
};