diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 23a238e6..7db6b059 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -4,30 +4,33 @@ import * as http from "http"; import {MessageUserPosition} from "../Model/Websocket/MessageUserPosition"; //TODO fix import by "_Model/.." import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." import Jwt, {JsonWebTokenError} from "jsonwebtoken"; -import {SECRET_KEY} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." +import {SECRET_KEY, MINIMUM_DISTANCE, GROUP_RADIUS} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." import {ExtRooms, RefreshUserPositionFunction} from "../Model/Websocket/ExtRoom"; import {ExtRoomsInterface} from "../Model/Websocket/ExtRoomsInterface"; import {World} from "../Model/World"; -import { uuid } from 'uuidv4'; +import {Group} from "_Model/Group"; enum SockerIoEvent { CONNECTION = "connection", - DISCONNECTION = "disconnect", + DISCONNECT = "disconnect", JOIN_ROOM = "join-room", USER_POSITION = "user-position", WEBRTC_SIGNAL = "webrtc-signal", + WEBRTC_OFFER = "webrtc-offer", WEBRTC_START = "webrtc-start", + WEBRTC_DISCONNECT = "webrtc-disconect", MESSAGE_ERROR = "message-error", } -export class IoSocketController{ +export class IoSocketController { Io: socketIO.Server; World: World; - constructor(server : http.Server) { + + constructor(server: http.Server) { this.Io = socketIO(server); // Authentication with token. it will be decoded and stored in the socket. - this.Io.use( (socket: Socket, next) => { + this.Io.use((socket: Socket, next) => { if (!socket.handshake.query || !socket.handshake.query.token) { return next(new Error('Authentication error')); } @@ -44,15 +47,15 @@ export class IoSocketController{ this.shareUsersPosition(); //don't send only function because the context will be not this - this.World = new World((user1 : string, user2 : string) => { - this.connectedUser(user1, user2); - }, (user1 : string, user2 : string) => { - this.disConnectedUser(user1, user2); - }); + this.World = new World((user1: string, group: Group) => { + this.connectedUser(user1, group); + }, (user1: string, group: Group) => { + this.disConnectedUser(user1, group); + }, MINIMUM_DISTANCE, GROUP_RADIUS); } ioConnection() { - this.Io.on(SockerIoEvent.CONNECTION, (socket: Socket) => { + this.Io.on(SockerIoEvent.CONNECTION, (socket: Socket) => { /*join-rom event permit to join one room. message : userId : user identification @@ -61,9 +64,9 @@ export class IoSocketController{ x: user x position on map y: user y position on map */ - socket.on(SockerIoEvent.JOIN_ROOM, (message : string) => { + socket.on(SockerIoEvent.JOIN_ROOM, (message: string) => { let messageUserPosition = this.hydrateMessageReceive(message); - if(messageUserPosition instanceof Error){ + if (messageUserPosition instanceof Error) { return socket.emit(SockerIoEvent.MESSAGE_ERROR, JSON.stringify({message: messageUserPosition.message})) } @@ -77,14 +80,12 @@ export class IoSocketController{ this.saveUserInformation((socket as ExSocketInterface), messageUserPosition); //add function to refresh position user in real time. - let rooms = (this.Io.sockets.adapter.rooms as ExtRoomsInterface); - rooms.refreshUserPosition = RefreshUserPositionFunction; - rooms.refreshUserPosition(rooms, this.Io); + this.refreshUserPosition(); socket.to(messageUserPosition.roomId).emit(SockerIoEvent.JOIN_ROOM, messageUserPosition.toString()); }); - socket.on(SockerIoEvent.USER_POSITION, (message : string) => { + socket.on(SockerIoEvent.USER_POSITION, (message: string) => { let messageUserPosition = this.hydrateMessageReceive(message); if (messageUserPosition instanceof Error) { return socket.emit(SockerIoEvent.MESSAGE_ERROR, JSON.stringify({message: messageUserPosition.message})); @@ -97,30 +98,39 @@ export class IoSocketController{ this.saveUserInformation((socket as ExSocketInterface), messageUserPosition); //refresh position of all user in all rooms in real time - let rooms = (this.Io.sockets.adapter.rooms as ExtRoomsInterface); - if(!rooms.refreshUserPosition){ - rooms.refreshUserPosition = RefreshUserPositionFunction; - } - rooms.refreshUserPosition(rooms, this.Io); + this.refreshUserPosition(); }); - socket.on(SockerIoEvent.WEBRTC_SIGNAL, (message : string) => { - let data : any = JSON.parse(message); + socket.on(SockerIoEvent.WEBRTC_SIGNAL, (message: string) => { + let data: any = JSON.parse(message); + //send only at user + let client = this.searchClientById(data.receiverId); + if (!client) { + console.error("client doesn't exist for ", data.receiverId); + return; + } + return client.emit(SockerIoEvent.WEBRTC_SIGNAL, message); + }); + + socket.on(SockerIoEvent.WEBRTC_OFFER, (message: string) => { + let data: any = JSON.parse(message); //send only at user - let clients: Array = Object.values(this.Io.sockets.sockets); - for(let i = 0; i < clients.length; i++){ - let client : ExSocketInterface = clients[i]; - if(client.userId !== data.receiverId){ - continue - } - client.emit(SockerIoEvent.WEBRTC_SIGNAL, message); - break; + let client = this.searchClientById(data.receiverId); + if (!client) { + console.error("client doesn't exist for ", data.receiverId); + return; } + client.emit(SockerIoEvent.WEBRTC_OFFER, message); }); - socket.on(SockerIoEvent.DISCONNECTION, (reason : string) => { + socket.on(SockerIoEvent.DISCONNECT, () => { let Client = (socket as ExSocketInterface); + this.sendDisconnectedEvent(Client); + + //refresh position of all user in all rooms in real time + this.refreshUserPosition(); + //leave group of user this.World.leave(Client); @@ -138,13 +148,39 @@ export class IoSocketController{ }); } + /** + * + * @param userId + */ + searchClientById(userId: string): ExSocketInterface | null { + let clients: Array = Object.values(this.Io.sockets.sockets); + for (let i = 0; i < clients.length; i++) { + let client: ExSocketInterface = clients[i]; + if (client.userId !== userId) { + continue + } + return client; + } + return null; + } + + /** + * + * @param Client: ExSocketInterface + */ + sendDisconnectedEvent(Client: ExSocketInterface) { + Client.broadcast.emit(SockerIoEvent.WEBRTC_DISCONNECT, JSON.stringify({ + userId: Client.userId + })); + } + /** * * @param socket * @param roomId */ - joinWebRtcRoom(socket : ExSocketInterface, roomId : string) { - if(socket.webRtcRoomId === roomId){ + joinWebRtcRoom(socket: ExSocketInterface, roomId: string) { + if (socket.webRtcRoomId === roomId) { return; } socket.join(roomId); @@ -175,27 +211,36 @@ export class IoSocketController{ } //permit to save user position in socket - saveUserInformation(socket : ExSocketInterface, message : MessageUserPosition){ + saveUserInformation(socket: ExSocketInterface, message: MessageUserPosition) { socket.position = message.position; socket.roomId = message.roomId; socket.userId = message.userId; } + refreshUserPosition() { + //refresh position of all user in all rooms in real time + let rooms = (this.Io.sockets.adapter.rooms as ExtRoomsInterface); + if (!rooms.refreshUserPosition) { + rooms.refreshUserPosition = RefreshUserPositionFunction; + } + rooms.refreshUserPosition(rooms, this.Io); + } + //Hydrate and manage error - hydrateMessageReceive(message : string) : MessageUserPosition | Error{ + hydrateMessageReceive(message: string): MessageUserPosition | Error { try { return new MessageUserPosition(JSON.parse(message)); - }catch (err) { + } catch (err) { //TODO log error return new Error(err); } } /** permit to share user position - ** users position will send in event 'user-position' - ** The data sent is an array with information for each user : - [ - { + ** users position will send in event 'user-position' + ** The data sent is an array with information for each user : + [ + { userId: , roomId: , position: { @@ -204,25 +249,26 @@ export class IoSocketController{ direction: } }, - ... - ] + ... + ] **/ - seTimeOutInProgress : any = null; - shareUsersPosition(){ - if(this.seTimeOutInProgress){ + seTimeOutInProgress: any = null; + + shareUsersPosition() { + if (this.seTimeOutInProgress) { clearTimeout(this.seTimeOutInProgress); } //send for each room, all data of position user let arrayMap = (this.Io.sockets.adapter.rooms as ExtRooms).userPositionMapByRoom; - if(!arrayMap){ + if (!arrayMap) { this.seTimeOutInProgress = setTimeout(() => { this.shareUsersPosition(); }, 10); return; } - arrayMap.forEach((value : any) => { + arrayMap.forEach((value: any) => { let roomId = value[0]; - this.Io.in(roomId).emit('user-position', JSON.stringify(arrayMap)); + this.Io.in(roomId).emit(SockerIoEvent.USER_POSITION, JSON.stringify(arrayMap)); }); this.seTimeOutInProgress = setTimeout(() => { this.shareUsersPosition(); @@ -230,24 +276,20 @@ export class IoSocketController{ } //connected user - connectedUser(user1 : string, user2 : string){ - /* TODO manager room and group user to enter and leave */ - let roomId = uuid(); - let clients : Array = Object.values(this.Io.sockets.sockets); - let User1 = clients.find((user : ExSocketInterface) => user.userId === user1); - let User2 = clients.find((user : ExSocketInterface) => user.userId === user2); - - if(User1) { - this.joinWebRtcRoom(User1, roomId); - } - if(User2) { - this.joinWebRtcRoom(User2, roomId); + connectedUser(userId: string, group: Group) { + let Client = this.searchClientById(userId); + if (!Client) { + return; } + this.joinWebRtcRoom(Client, group.getId()); } //connected user - disConnectedUser(user1 : string, user2 : string){ - console.log("disConnectedUser => user1", user1); - console.log("disConnectedUser => user2", user2); + disConnectedUser(userId: string, group: Group) { + let Client = this.searchClientById(userId); + if (!Client) { + return; + } + this.sendDisconnectedEvent(Client) } } diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts index f795bf90..cfd98fcd 100644 --- a/back/src/Enum/EnvironmentVariable.ts +++ b/back/src/Enum/EnvironmentVariable.ts @@ -1,7 +1,11 @@ const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY"; const ROOM = process.env.ROOM || "THECODINGMACHINE"; +const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64; +const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48; export { SECRET_KEY, - ROOM -} \ No newline at end of file + ROOM, + MINIMUM_DISTANCE, + GROUP_RADIUS +} diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index eacd2d03..d71a0585 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -1,10 +1,12 @@ import { World, ConnectCallback, DisconnectCallback } from "./World"; import { UserInterface } from "./UserInterface"; import {PositionInterface} from "_Model/PositionInterface"; +import {uuid} from "uuidv4"; export class Group { static readonly MAX_PER_GROUP = 4; + private id: string; private users: UserInterface[]; private connectCallback: ConnectCallback; private disconnectCallback: DisconnectCallback; @@ -14,6 +16,7 @@ export class Group { this.users = []; this.connectCallback = connectCallback; this.disconnectCallback = disconnectCallback; + this.id = uuid(); users.forEach((user: UserInterface) => { this.join(user); @@ -24,6 +27,10 @@ export class Group { return this.users; } + getId() : string{ + return this.id; + } + /** * Returns the barycenter of all users (i.e. the center of the group) */ @@ -64,23 +71,6 @@ export class Group { return this.users.indexOf(user) !== -1; } - isStillIn(user: UserInterface): boolean - { - if(!this.isPartOfGroup(user)) { - return false; - } - let stillIn = true; - for(let i = 0; i <= this.users.length; i++) { - let userInGroup = this.users[i]; - let distance = World.computeDistance(user, userInGroup); - if(distance > World.MIN_DISTANCE) { - stillIn = false; - break; - } - } - return stillIn; - } - /*removeFromGroup(users: UserInterface[]): void { for(let i = 0; i < users.length; i++){ diff --git a/back/src/Model/World.ts b/back/src/Model/World.ts index b04b34ee..19eb8ed8 100644 --- a/back/src/Model/World.ts +++ b/back/src/Model/World.ts @@ -10,7 +10,8 @@ export type ConnectCallback = (user: string, group: Group) => void; export type DisconnectCallback = (user: string, group: Group) => void; export class World { - static readonly MIN_DISTANCE = 160; + private minDistance: number; + private groupRadius: number; // Users, sorted by ID private users: Map; @@ -19,12 +20,17 @@ export class World { private connectCallback: ConnectCallback; private disconnectCallback: DisconnectCallback; - constructor(connectCallback: ConnectCallback, disconnectCallback: DisconnectCallback) + constructor(connectCallback: ConnectCallback, + disconnectCallback: DisconnectCallback, + minDistance: number, + groupRadius: number) { this.users = new Map(); this.groups = []; this.connectCallback = connectCallback; this.disconnectCallback = disconnectCallback; + this.minDistance = minDistance; + this.groupRadius = groupRadius; } public join(userPosition: MessageUserPosition): void { @@ -76,7 +82,7 @@ export class World { // If the user is part of a group: // should he leave the group? let distance = World.computeDistanceBetweenPositions(user.position, user.group.getPosition()); - if (distance > World.MIN_DISTANCE) { + if (distance > this.groupRadius) { this.leaveGroup(user); } } @@ -106,15 +112,16 @@ export class World { /** * Looks for the closest user that is: - * - close enough (distance <= MIN_DISTANCE) - * - not in a group OR in a group that is not full + * - close enough (distance <= minDistance) + * - not in a group + * OR + * - close enough to a group (distance <= groupRadius) */ private searchClosestAvailableUserOrGroup(user: UserInterface): UserInterface|Group|null { - let usersToBeGroupedWith: Distance[] = []; - let minimumDistanceFound: number = World.MIN_DISTANCE; + let minimumDistanceFound: number = Math.max(this.minDistance, this.groupRadius); let matchingItem: UserInterface | Group | null = null; - this.users.forEach(function(currentUser, userId) { + this.users.forEach((currentUser, userId) => { // Let's only check users that are not part of a group if (typeof currentUser.group !== 'undefined') { return; @@ -125,7 +132,7 @@ export class World { let distance = World.computeDistance(user, currentUser); // compute distance between peers. - if(distance <= minimumDistanceFound) { + if(distance <= minimumDistanceFound && distance <= this.minDistance) { minimumDistanceFound = distance; matchingItem = currentUser; } @@ -165,12 +172,12 @@ export class World { */ }); - this.groups.forEach(function(group: Group) { + this.groups.forEach((group: Group) => { if (group.isFull()) { return; } let distance = World.computeDistanceBetweenPositions(user.position, group.getPosition()); - if(distance <= minimumDistanceFound) { + if(distance <= minimumDistanceFound && distance <= this.groupRadius) { minimumDistanceFound = distance; matchingItem = group; } diff --git a/back/tests/WorldTest.ts b/back/tests/WorldTest.ts index b0b1a8c6..d14aaeb0 100644 --- a/back/tests/WorldTest.ts +++ b/back/tests/WorldTest.ts @@ -15,7 +15,7 @@ describe("World", () => { } - let world = new World(connect, disconnect); + let world = new World(connect, disconnect, 160, 160); world.join(new MessageUserPosition({ userId: "foo", @@ -62,7 +62,7 @@ describe("World", () => { } - let world = new World(connect, disconnect); + let world = new World(connect, disconnect, 160, 160); world.join(new MessageUserPosition({ userId: "foo", @@ -107,7 +107,7 @@ describe("World", () => { disconnectCallNumber++; } - let world = new World(connect, disconnect); + let world = new World(connect, disconnect, 160, 160); world.join(new MessageUserPosition({ userId: "foo", diff --git a/front/dist/index.html b/front/dist/index.html index a9f51d4d..85aced1e 100644 --- a/front/dist/index.html +++ b/front/dist/index.html @@ -12,11 +12,9 @@
+
-
- -
-
+
@@ -25,13 +23,10 @@
-
+
-
- -
diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 9e2d2daa..58c32bb7 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -1,77 +1,50 @@ +video{ + -webkit-transform: scaleX(-1); + transform: scaleX(-1); +} .webrtc{ display: none; + position: absolute; + right: 0px; + height: 100%; + width: 300px; } .webrtc.active{ display: block; } -.webrtc, .activeCam{ - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - background: black; -} +.webrtc, .activeCam{} .activeCam video{ position: absolute; - width: 100%; - height: 100%; + height: 25%; + top: 10px; + margin: 5px; + right: -100px; + transition: all 0.2s ease; +} +.webrtc:hover .activeCam video{ + right: 10px; +} +.activeCam video#myCamVideo{ + width: 200px; + height: 113px; } /*CSS size for 2 - 3 elements*/ -video:nth-child(1):nth-last-child(3), -video:nth-child(2):nth-last-child(2), -video:nth-child(3):nth-last-child(1), -video:nth-child(1):nth-last-child(2), -video:nth-child(2):nth-last-child(1){ - width: 50%; +.activeCam video:nth-child(1){ + /*this is for camera of user*/ + top: 75%; } -video:nth-child(1):nth-last-child(3), -video:nth-child(2):nth-last-child(2), -video:nth-child(3):nth-last-child(1){ - height: 50%; +.activeCam video:nth-child(2){ + top: 0%; } - -/*CSS position for 2 elements*/ -video:nth-child(1):nth-last-child(2){ - left: 0; +.activeCam video:nth-child(3){ + top: 25%; } -video:nth-child(2):nth-last-child(1){ - left: 50%; -} - -/*CSS position for 3 elements*/ -video:nth-child(1):nth-last-child(3){ - top: 0; - left: 0; -} -video:nth-child(2):nth-last-child(2){ - top: 0; - left: 50%; -} -video:nth-child(3):nth-last-child(1) { +.activeCam video:nth-child(4) { top: 50%; - left: 25%; } -.myCam{ - height: 200px; - width: 300px; - position: absolute; - right: 10px; - background: black; - border: none; - bottom: 20px; - max-height: 17%; - max-width: 17%; - opacity: 1; - display: block; - transition: opacity 1s; -} -.myCam video{ - width: 100%; - height: 100%; -} +/*btn animation*/ .btn-cam-action div{ cursor: pointer; position: absolute; @@ -79,14 +52,14 @@ video:nth-child(3):nth-last-child(1) { width: 64px; height: 64px; background: #666; - left: 6vw; box-shadow: 2px 2px 24px #444; border-radius: 48px; - transform: translateX(calc(-6vw - 96px)); + transform: translateY(12vw); transition-timing-function: ease-in-out; + bottom: 20px; } -.webrtc:hover .btn-cam-action.active div{ - transform: translateX(0); +.webrtc:hover .btn-cam-action div{ + transform: translateY(0); } .btn-cam-action div:hover{ background: #407cf7; @@ -94,60 +67,21 @@ video:nth-child(3):nth-last-child(1) { transition: 280ms; } .btn-micro{ - bottom: 277px; transition: all .3s; + right: 10px; } .btn-video{ - bottom: 177px; transition: all .2s; + right: 114px; } -.btn-call{ - bottom: 77px; +/*.btn-call{ transition: all .1s; -} + left: 0px; +}*/ .btn-cam-action div img{ height: 32px; width: 40px; top: calc(48px - 32px); left: calc(48px - 35px); position: relative; -} -.phone-open{ - position: absolute; - border-radius: 50%; - width: 50px; - height: 50px; - left: calc(50% - 70px); - padding: 20px; - bottom: 20px; - box-shadow: 2px 2px 24px #444; - background-color: green; - opacity: 0; - transition: all .4s ease-in-out; -} -.phone-open.active{ - opacity: 1; - animation-name: phone-move; - animation-duration: 0.4s; - animation-iteration-count: infinite; - animation-timing-function: linear; -} -.phone-open:hover{ - animation: none; - cursor: pointer; -} - -@keyframes phone-move { - 0% { - left: calc(50% - 70px); - bottom: 20px; - } - 25% { - left: calc(50% - 65px); - bottom: 15px; - } - 25% { - left: calc(50% - 75px); - bottom: 25px; - } } \ No newline at end of file diff --git a/front/src/Connexion.ts b/front/src/Connexion.ts index dc48833c..56531b67 100644 --- a/front/src/Connexion.ts +++ b/front/src/Connexion.ts @@ -7,9 +7,11 @@ import {API_URL, ROOM} from "./Enum/EnvironmentVariable"; enum EventMessage{ WEBRTC_SIGNAL = "webrtc-signal", WEBRTC_START = "webrtc-start", + WEBRTC_JOIN_ROOM = "webrtc-join-room", JOIN_ROOM = "join-room", USER_POSITION = "user-position", - MESSAGE_ERROR = "message-error" + MESSAGE_ERROR = "message-error", + WEBRTC_DISCONNECT = "webrtc-disconect" } class Message { @@ -131,6 +133,8 @@ export interface ConnexionInterface { receiveWebrtcSignal(callBack: Function): void; receiveWebrtcStart(callBack: Function): void; + + disconnectMessage(callBack: Function): void; } export class Connexion implements ConnexionInterface { @@ -227,7 +231,7 @@ export class Connexion implements ConnexionInterface { } sendWebrtcSignal(signal: any, roomId: string, userId? : string, receiverId? : string) { - this.socket.emit(EventMessage.WEBRTC_SIGNAL, JSON.stringify({ + return this.socket.emit(EventMessage.WEBRTC_SIGNAL, JSON.stringify({ userId: userId ? userId : this.userId, receiverId: receiverId ? receiverId : this.userId, roomId: roomId, @@ -240,7 +244,7 @@ export class Connexion implements ConnexionInterface { } receiveWebrtcSignal(callback: Function) { - this.socket.on(EventMessage.WEBRTC_SIGNAL, callback); + return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback); } errorMessage(): void { @@ -248,4 +252,8 @@ export class Connexion implements ConnexionInterface { console.error(EventMessage.MESSAGE_ERROR, message); }) } + + disconnectMessage(callback: Function): void { + this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback); + } } \ No newline at end of file diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 8764e959..9666ff9c 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -26,8 +26,8 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface{ Layers : Array; Objects : Array; map: ITiledMap; - startX = (window.innerWidth / 2) / RESOLUTION; - startY = (window.innerHeight / 2) / RESOLUTION; + startX = 704;// 22 case + startY = 32; // 1 case constructor() { diff --git a/front/src/Phaser/Login/LogincScene.ts b/front/src/Phaser/Login/LogincScene.ts index 15ead519..c9c22f0f 100644 --- a/front/src/Phaser/Login/LogincScene.ts +++ b/front/src/Phaser/Login/LogincScene.ts @@ -3,6 +3,8 @@ import {TextField} from "../Components/TextField"; import {TextInput} from "../Components/TextInput"; import {ClickButton} from "../Components/ClickButton"; import {GameSceneName} from "../Game/GameScene"; +import {SimplePeer} from "../../WebRtc/SimplePeer"; +import {Connexion} from "../../Connexion"; //todo: put this constants in a dedicated file export const LoginSceneName = "LoginScene"; diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 4f5e790a..8c4688b8 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -26,6 +26,7 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G userId: string; PlayerValue: string; userInputManager: UserInputManager; + previousMove: string; constructor( userId: string, @@ -89,7 +90,7 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G direction = PlayerAnimationNames.None; this.stop(); } - + this.emit(hasMovedEventName, {direction, x: this.x, y: this.y}); } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 5b8d0076..5ebbc0c7 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -1,3 +1,8 @@ +const videoConstraint: {width : any, height: any, facingMode : string} = { + width: { ideal: 1280 }, + height: { ideal: 720 }, + facingMode: "user" +}; export class MediaManager { localStream: MediaStream; remoteVideo: Array = new Array(); @@ -6,13 +11,20 @@ export class MediaManager { cinema: any = null; microphoneClose: any = null; microphone: any = null; - constraintsMedia = {audio: false, video: true}; + constraintsMedia : {audio : any, video : any} = { + audio: true, + video: videoConstraint + }; getCameraPromise : Promise = null; + updatedLocalStreamCallBack : Function; + + constructor(updatedLocalStreamCallBack : Function) { + this.updatedLocalStreamCallBack = updatedLocalStreamCallBack; - constructor() { this.myCamVideo = document.getElementById('myCamVideo'); - this.microphoneClose = document.getElementById('microphone-close'); + this.microphoneClose = document.getElementById('microphone-close'); + this.microphoneClose.style.display = "none"; this.microphoneClose.addEventListener('click', (e: any) => { e.preventDefault(); this.enabledMicrophone(); @@ -26,6 +38,7 @@ export class MediaManager { }); this.cinemaClose = document.getElementById('cinema-close'); + this.cinemaClose.style.display = "none"; this.cinemaClose.addEventListener('click', (e: any) => { e.preventDefault(); this.enabledCamera(); @@ -37,9 +50,6 @@ export class MediaManager { this.disabledCamera(); //update tracking }); - - this.enabledCamera(); - this.enabledMicrophone(); } activeVisio(){ @@ -50,9 +60,12 @@ export class MediaManager { enabledCamera() { this.cinemaClose.style.display = "none"; this.cinema.style.display = "block"; - this.constraintsMedia.video = true; + this.constraintsMedia.video = videoConstraint; this.localStream = null; this.myCamVideo.srcObject = null; + this.getCamera().then((stream) => { + this.updatedLocalStreamCallBack(stream); + }); } disabledCamera() { @@ -70,12 +83,18 @@ export class MediaManager { } this.localStream = null; this.myCamVideo.srcObject = null; + this.getCamera().then((stream) => { + this.updatedLocalStreamCallBack(stream); + }); } enabledMicrophone() { this.microphoneClose.style.display = "none"; this.microphone.style.display = "block"; this.constraintsMedia.audio = true; + this.getCamera().then((stream) => { + this.updatedLocalStreamCallBack(stream); + }); } disabledMicrophone() { @@ -89,18 +108,9 @@ export class MediaManager { } }); } - } - - getElementActivePhone(){ - return document.getElementById('phone-open'); - } - - activePhoneOpen(){ - return this.getElementActivePhone().classList.add("active"); - } - - disablePhoneOpen(){ - return this.getElementActivePhone().classList.remove("active"); + this.getCamera().then((stream) => { + this.updatedLocalStreamCallBack(stream); + }); } //get camera @@ -109,6 +119,13 @@ export class MediaManager { .then((stream: MediaStream) => { this.localStream = stream; this.myCamVideo.srcObject = this.localStream; + + //TODO resize remote cam + /*console.log(this.localStream.getTracks()); + let videoMediaStreamTrack = this.localStream.getTracks().find((media : MediaStreamTrack) => media.kind === "video"); + let {width, height} = videoMediaStreamTrack.getSettings(); + console.info(`${width}x${height}`); // 6*/ + return stream; }).catch((err) => { console.error(err); @@ -127,6 +144,15 @@ export class MediaManager { this.remoteVideo[(userId as any)] = document.getElementById(userId); } + /** + * + * @param userId + * @param stream + */ + addStreamRemoteVideo(userId : string, stream : MediaStream){ + this.remoteVideo[(userId as any)].srcObject = stream; + } + /** * * @param userId diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 3c94bbaf..3bff9580 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -6,53 +6,51 @@ export interface SimplePeerInterface { } export class SimplePeer { - Connexion: ConnexionInterface; - MediaManager: MediaManager; - WebRtcRoomId: string; - Users: Array; + private Connexion: ConnexionInterface; + private WebRtcRoomId: string; + private Users: Array; - PeerConnexionArray: Array = new Array(); + private MediaManager: MediaManager; + + private PeerConnexionArray: Array = new Array(); constructor(Connexion: ConnexionInterface, WebRtcRoomId: string = "test-webrtc") { this.Connexion = Connexion; this.WebRtcRoomId = WebRtcRoomId; - this.MediaManager = new MediaManager(); + this.MediaManager = new MediaManager((stream : MediaStream) => { + this.updatedLocalStream(); + }); + this.PeerConnexionArray = new Array(); + this.initialise(); } /** * permit to listen when user could start visio */ - private initialise(){ + private initialise() { - //receive message start - this.Connexion.receiveWebrtcStart((message: string) => { - this.receiveWebrtcStart(message); + //receive signal by gemer + this.Connexion.receiveWebrtcSignal((message: string) => { + this.receiveWebrtcSignal(message); }); - //when button to call is clicked, start video - this.MediaManager.getElementActivePhone().addEventListener("click", () => { - this.startWebRtc(); - this.disablePhone(); - }); - } - /** - * server has two person connected, start the meet - */ - startWebRtc() { this.MediaManager.activeVisio(); - return this.MediaManager.getCamera().then((stream: MediaStream) => { - this.MediaManager.localStream = stream; + this.MediaManager.getCamera().then(() => { - //create pear connexion - this.createPeerConnexion(); - - //receive signal by gemer - this.Connexion.receiveWebrtcSignal((message: string) => { - this.receiveWebrtcSignal(message); + //receive message start + this.Connexion.receiveWebrtcStart((message: string) => { + this.receiveWebrtcStart(message); }); + }).catch((err) => { - console.error(err); + console.error("err", err); + }); + + //receive signal by gemer + this.Connexion.disconnectMessage((message: string) => { + let data = JSON.parse(message); + this.closeConnexion(data.userId); }); } @@ -60,46 +58,97 @@ export class SimplePeer { * * @param message */ - receiveWebrtcStart(message: string) { + private receiveWebrtcStart(message: string) { let data = JSON.parse(message); this.WebRtcRoomId = data.roomId; this.Users = data.clients; - //active button for player - this.activePhone(); + //start connexion + this.startWebRtc(); } - - createPeerConnexion() { + /** + * server has two person connected, start the meet + */ + private startWebRtc() { this.Users.forEach((user: any) => { - if(this.PeerConnexionArray[user.userId]){ + //if it's not an initiator, peer connexion will be created when gamer will receive offer signal + if(!user.initiator){ return; } - this.MediaManager.addActiveVideo(user.userId); - - this.PeerConnexionArray[user.userId] = new Peer({initiator: user.initiator}); - - this.PeerConnexionArray[user.userId].on('signal', (data: any) => { - this.sendWebrtcSignal(data, user.userId); - }); - - this.PeerConnexionArray[user.userId].on('stream', (stream: MediaStream) => { - this.stream(user.userId, stream); - }); - - this.PeerConnexionArray[user.userId].on('close', () => { - this.closeConnexion(user.userId); - }); - - this.addMedia(user.userId); + this.createPeerConnexion(user); }); - } - closeConnexion(userId : string){ - // @ts-ignore - this.PeerConnexionArray[userId] = null; - this.MediaManager.removeActiveVideo(userId) + /** + * create peer connexion to bind users + */ + private createPeerConnexion(user : any) { + if(this.PeerConnexionArray[user.userId]) { + return; + } + + this.MediaManager.removeActiveVideo(user.userId); + this.MediaManager.addActiveVideo(user.userId); + + this.PeerConnexionArray[user.userId] = new Peer({ + initiator: user.initiator ? user.initiator : false, + reconnectTimer: 10000, + config: { + iceServers: [ + { + urls: 'stun:stun.l.google.com:19302' + }, + { + urls: 'turn:numb.viagenie.ca', + username: 'g.parant@thecodingmachine.com', + credential: 'itcugcOHxle9Acqi$' + }, + ] + }, + }); + + //start listen signal for the peer connexion + this.PeerConnexionArray[user.userId].on('signal', (data: any) => { + this.sendWebrtcSignal(data, user.userId); + }); + + this.PeerConnexionArray[user.userId].on('stream', (stream: MediaStream) => { + this.stream(user.userId, stream); + }); + + this.PeerConnexionArray[user.userId].on('track', (track: MediaStreamTrack, stream: MediaStream) => { + this.stream(user.userId, stream); + }); + + this.PeerConnexionArray[user.userId].on('close', () => { + this.closeConnexion(user.userId); + }); + + this.PeerConnexionArray[user.userId].on('error', (err: any) => { + console.error(`error => ${user.userId} => ${err.code}`, err); + }); + + this.PeerConnexionArray[user.userId].on('connect', () => { + console.info(`connect => ${user.userId}`); + }); + + this.addMedia(user.userId); + } + + private closeConnexion(userId : string) { + try { + this.MediaManager.removeActiveVideo(userId); + if (!this.PeerConnexionArray[(userId as any)]) { + return; + } + // @ts-ignore + this.PeerConnexionArray[(userId as any)].destroy(); + this.PeerConnexionArray[(userId as any)] = null; + delete this.PeerConnexionArray[(userId as any)]; + } catch (err) { + console.error("closeConnexion", err) + } } /** @@ -107,20 +156,29 @@ export class SimplePeer { * @param userId * @param data */ - sendWebrtcSignal(data: any, userId : string) { - this.Connexion.sendWebrtcSignal(data, this.WebRtcRoomId, null, userId); + private sendWebrtcSignal(data: any, userId : string) { + try { + this.Connexion.sendWebrtcSignal(data, this.WebRtcRoomId, null, userId); + }catch (e) { + console.error(`sendWebrtcSignal => ${userId}`, e); + } } /** * * @param message */ - receiveWebrtcSignal(message: string) { + private receiveWebrtcSignal(message: string) { let data = JSON.parse(message); - if(!this.PeerConnexionArray[data.userId]){ - return; + try { + //if offer type, create peer connexion + if(data.signal.type === "offer"){ + this.createPeerConnexion(data); + } + this.PeerConnexionArray[data.userId].signal(data.signal); + } catch (e) { + console.error(`receiveWebrtcSignal => ${data.userId}`, e); } - this.PeerConnexionArray[data.userId].signal(data.signal); } /** @@ -128,23 +186,28 @@ export class SimplePeer { * @param userId * @param stream */ - stream(userId : any, stream: MediaStream) { - this.MediaManager.remoteVideo[userId].srcObject = stream; + private stream(userId : any, stream: MediaStream) { + this.MediaManager.addStreamRemoteVideo(userId, stream); } /** * * @param userId */ - addMedia (userId : any) { - this.PeerConnexionArray[userId].addStream(this.MediaManager.localStream) // <- add streams to peer dynamically + private addMedia (userId : any = null) { + try { + let transceiver : any = null; + this.MediaManager.localStream.getTracks().forEach( + transceiver = (track: MediaStreamTrack) => this.PeerConnexionArray[userId].addTrack(track, this.MediaManager.localStream) + ) + }catch (e) { + console.error(`addMedia => addMedia => ${userId}`, e); + } } - activePhone(){ - this.MediaManager.activePhoneOpen(); - } - - disablePhone(){ - this.MediaManager.disablePhoneOpen(); + updatedLocalStream(){ + this.Users.forEach((user) => { + this.addMedia(user.userId); + }) } } \ No newline at end of file