diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 04dea735..7db6b059 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -8,26 +8,29 @@ import {SECRET_KEY, MINIMUM_DISTANCE, GROUP_RADIUS} from "../Enum/EnvironmentVar 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/Model/Group.ts b/back/src/Model/Group.ts index 259a0379..d71a0585 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -1,19 +1,22 @@ -import { World } from "./World"; +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: (user1: string, user2: string) => void; - private disconnectCallback: (user1: string, user2: string) => void; + private connectCallback: ConnectCallback; + private disconnectCallback: DisconnectCallback; - constructor(users: UserInterface[], connectCallback: (user1: string, user2: string) => void, disconnectCallback: (user1: string, user2: string) => void) { + constructor(users: UserInterface[], connectCallback: ConnectCallback, disconnectCallback: DisconnectCallback) { 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) */ @@ -54,9 +61,7 @@ export class Group { join(user: UserInterface): void { // Broadcast on the right event - this.users.forEach((groupUser: UserInterface) => { - this.connectCallback(user.id, groupUser.id); - }); + this.connectCallback(user.id, this); this.users.push(user); user.group = this; } @@ -88,9 +93,7 @@ export class Group { user.group = undefined; // Broadcast on the right event - this.users.forEach((groupUser: UserInterface) => { - this.disconnectCallback(user.id, groupUser.id); - }); + this.disconnectCallback(user.id, this); } /** diff --git a/back/src/Model/World.ts b/back/src/Model/World.ts index e05879c1..19eb8ed8 100644 --- a/back/src/Model/World.ts +++ b/back/src/Model/World.ts @@ -6,6 +6,9 @@ import {UserInterface} from "./UserInterface"; import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; import {PositionInterface} from "_Model/PositionInterface"; +export type ConnectCallback = (user: string, group: Group) => void; +export type DisconnectCallback = (user: string, group: Group) => void; + export class World { private minDistance: number; private groupRadius: number; @@ -14,10 +17,11 @@ export class World { private users: Map; private groups: Group[]; - private connectCallback: (user1: string, user2: string) => void; - private disconnectCallback: (user1: string, user2: string) => void; + private connectCallback: ConnectCallback; + private disconnectCallback: DisconnectCallback; - constructor(connectCallback: (user1: string, user2: string) => void, disconnectCallback: (user1: string, user2: string) => void, + constructor(connectCallback: ConnectCallback, + disconnectCallback: DisconnectCallback, minDistance: number, groupRadius: number) { diff --git a/back/tests/WorldTest.ts b/back/tests/WorldTest.ts index 994f1a4b..26bf0228 100644 --- a/back/tests/WorldTest.ts +++ b/back/tests/WorldTest.ts @@ -1,6 +1,6 @@ import "jasmine"; import {Message} from "../src/Model/Websocket/Message"; -import {World} from "../src/Model/World"; +import {World, ConnectCallback, DisconnectCallback } from "../src/Model/World"; import {MessageUserPosition, Point} from "../src/Model/Websocket/MessageUserPosition"; import { Group } from "../src/Model/Group"; import {Distance} from "../src/Model//Distance"; @@ -8,10 +8,10 @@ import {Distance} from "../src/Model//Distance"; describe("World", () => { it("should connect user1 and user2", () => { let connectCalled: boolean = false; - let connect = (user1: string, user2: string): void => { + let connect = (user: string, group: Group): void => { connectCalled = true; } - let disconnect = (user1: string, user2: string): void => { + let disconnect = (user: string, group: Group): void => { } @@ -56,10 +56,10 @@ describe("World", () => { it("should connect 3 users", () => { let connectCalled: boolean = false; - let connect = (user1: string, user2: string): void => { + let connect = (user: string, group: Group): void => { connectCalled = true; } - let disconnect = (user1: string, user2: string): void => { + let disconnect = (user: string, group: Group): void => { } @@ -101,10 +101,10 @@ describe("World", () => { it("should disconnect user1 and user2", () => { let connectCalled: boolean = false; let disconnectCalled: boolean = false; - let connect = (user1: string, user2: string): void => { + let connect = (user: string, group: Group): void => { connectCalled = true; } - let disconnect = (user1: string, user2: string): void => { + let disconnect = (user: string, group: Group): void => { disconnectCalled = true; } @@ -142,51 +142,4 @@ describe("World", () => { expect(disconnectCalled).toBe(false); }); - /** - it('Should return the distances between all users', () => { - let connectCalled: boolean = false; - let connect = (user1: string, user2: string): void => { - connectCalled = true; - } - let disconnect = (user1: string, user2: string): void => { - - } - - let world = new World(connect, disconnect); - let user1 = new MessageUserPosition({ - userId: "foo", - roomId: 1, - position: new Point(100, 100) - }); - - world.join(user1); - - let user2 = new MessageUserPosition({ - userId: "bar", - roomId: 1, - position: new Point(500, 100) - }); - world.join(user2); - - let user3 = new MessageUserPosition({ - userId: "baz", - roomId: 1, - position: new Point(101, 100) - }); - - let user4 = new MessageUserPosition({ - userId: "buz", - roomId: 1, - position: new Point(105, 100) - }) - - let group = new Group([user1, user2, user3, user4]); - - let distances = world.getDistancesBetweenGroupUsers(group) - - console.log(distances); - - //expect(distances).toBe([]); - }) - **/ }) 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