Merge pull request #350 from thecodingmachine/develop

Release 2020-10-20 2
This commit is contained in:
David Négrier 2020-10-20 18:55:41 +02:00 committed by GitHub
commit 1dfc51ada6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 386 additions and 183 deletions

View File

@ -31,7 +31,6 @@ export class AuthenticateController extends BaseController {
res.onAborted(() => { res.onAborted(() => {
console.warn('Login request was aborted'); console.warn('Login request was aborted');
}) })
const host = req.getHeader('host');
const param = await res.json(); const param = await res.json();
//todo: what to do if the organizationMemberToken is already used? //todo: what to do if the organizationMemberToken is already used?
@ -45,6 +44,7 @@ export class AuthenticateController extends BaseController {
const worldSlug = data.worldSlug; const worldSlug = data.worldSlug;
const roomSlug = data.roomSlug; const roomSlug = data.roomSlug;
const mapUrlStart = data.mapUrlStart; const mapUrlStart = data.mapUrlStart;
const textures = data.textures;
const authToken = jwtTokenManager.createJWTToken(userUuid); const authToken = jwtTokenManager.createJWTToken(userUuid);
res.writeStatus("200 OK"); res.writeStatus("200 OK");
@ -56,6 +56,7 @@ export class AuthenticateController extends BaseController {
worldSlug, worldSlug,
roomSlug, roomSlug,
mapUrlStart, mapUrlStart,
textures
})); }));
} catch (e) { } catch (e) {

View File

@ -1,4 +1,4 @@
import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.."
import {GameRoomPolicyTypes} from "../Model/GameRoom"; import {GameRoomPolicyTypes} from "../Model/GameRoom";
import {PointInterface} from "../Model/Websocket/PointInterface"; import {PointInterface} from "../Model/Websocket/PointInterface";
import { import {
@ -18,10 +18,9 @@ import {UserMovesMessage} from "../Messages/generated/messages_pb";
import {TemplatedApp} from "uWebSockets.js" import {TemplatedApp} from "uWebSockets.js"
import {parse} from "query-string"; import {parse} from "query-string";
import {jwtTokenManager} from "../Services/JWTTokenManager"; import {jwtTokenManager} from "../Services/JWTTokenManager";
import {adminApi, fetchMemberDataByUuidResponse} from "../Services/AdminApi"; import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "../Services/AdminApi";
import {socketManager} from "../Services/SocketManager"; import {SocketManager, socketManager} from "../Services/SocketManager";
import {emitInBatch, resetPing} from "../Services/IoSocketHelpers"; import {emitInBatch, resetPing} from "../Services/IoSocketHelpers";
import Jwt from "jsonwebtoken";
import {clientEventsEmitter} from "../Services/ClientEventsEmitter"; import {clientEventsEmitter} from "../Services/ClientEventsEmitter";
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable"; import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
@ -159,25 +158,28 @@ export class IoSocketController {
characterLayers = [ characterLayers ]; characterLayers = [ characterLayers ];
} }
const userUuid = await jwtTokenManager.getUserUuidFromToken(token); const userUuid = await jwtTokenManager.getUserUuidFromToken(token);
let memberTags: string[] = []; let memberTags: string[] = [];
let memberTextures: CharacterTexture[] = [];
const room = await socketManager.getOrCreateRoom(roomId); const room = await socketManager.getOrCreateRoom(roomId);
if (!room.anonymous && room.policyType !== GameRoomPolicyTypes.ANONYMUS_POLICY) { try {
try { const userData = await adminApi.fetchMemberDataByUuid(userUuid);
const userData = await adminApi.fetchMemberDataByUuid(userUuid); //console.log('USERDATA', userData)
memberTags = userData.tags; memberTags = userData.tags;
if (room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && !room.canAccess(memberTags)) { memberTextures = userData.textures;
throw new Error('No correct tags') if (!room.anonymous && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && !room.canAccess(memberTags)) {
} throw new Error('No correct tags')
console.log('access granted for user '+userUuid+' and room '+roomId);
} catch (e) {
console.log('access not granted for user '+userUuid+' and room '+roomId);
throw new Error('Client cannot acces this ressource.')
} }
//console.log('access granted for user '+userUuid+' and room '+roomId);
} catch (e) {
console.log('access not granted for user '+userUuid+' and room '+roomId);
throw new Error('Client cannot acces this ressource.')
} }
// Generate characterLayers objects from characterLayers string[]
const characterLayerObjs: CharacterLayer[] = SocketManager.mergeCharacterLayersAndCustomTextures(characterLayers, memberTextures);
if (upgradeAborted.aborted) { if (upgradeAborted.aborted) {
console.log("Ouch! Client disconnected before we could upgrade it!"); console.log("Ouch! Client disconnected before we could upgrade it!");
/* You must not upgrade now */ /* You must not upgrade now */
@ -192,8 +194,9 @@ export class IoSocketController {
userUuid, userUuid,
roomId, roomId,
name, name,
characterLayers, characterLayers: characterLayerObjs,
tags: memberTags, tags: memberTags,
textures: memberTextures,
position: { position: {
x: x, x: x,
y: y, y: y,
@ -233,7 +236,7 @@ export class IoSocketController {
resetPing(client); resetPing(client);
//get data information and shwo messages //get data information and shwo messages
adminApi.fetchMemberDataByUuid(client.userUuid).then((res: fetchMemberDataByUuidResponse) => { adminApi.fetchMemberDataByUuid(client.userUuid).then((res: FetchMemberDataByUuidResponse) => {
if (!res.messages) { if (!res.messages) {
return; return;
} }
@ -311,6 +314,7 @@ export class IoSocketController {
client.name = ws.name; client.name = ws.name;
client.tags = ws.tags; client.tags = ws.tags;
client.textures = ws.textures;
client.characterLayers = ws.characterLayers; client.characterLayers = ws.characterLayers;
client.roomId = ws.roomId; client.roomId = ws.roomId;
return client; return client;

View File

@ -3,6 +3,12 @@ import {Identificable} from "./Identificable";
import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
import {BatchMessage, SubMessage} from "../../Messages/generated/messages_pb"; import {BatchMessage, SubMessage} from "../../Messages/generated/messages_pb";
import {WebSocket} from "uWebSockets.js" import {WebSocket} from "uWebSockets.js"
import {CharacterTexture} from "../../Services/AdminApi";
export interface CharacterLayer {
name: string,
url: string|undefined
}
export interface ExSocketInterface extends WebSocket, Identificable { export interface ExSocketInterface extends WebSocket, Identificable {
token: string; token: string;
@ -10,7 +16,7 @@ export interface ExSocketInterface extends WebSocket, Identificable {
//userId: number; // A temporary (autoincremented) identifier for this user //userId: number; // A temporary (autoincremented) identifier for this user
userUuid: string; // A unique identifier for this user userUuid: string; // A unique identifier for this user
name: string; name: string;
characterLayers: string[]; characterLayers: CharacterLayer[];
position: PointInterface; position: PointInterface;
viewport: ViewportInterface; viewport: ViewportInterface;
/** /**
@ -21,5 +27,6 @@ export interface ExSocketInterface extends WebSocket, Identificable {
batchTimeout: NodeJS.Timeout|null; batchTimeout: NodeJS.Timeout|null;
pingTimeout: NodeJS.Timeout|null; pingTimeout: NodeJS.Timeout|null;
disconnecting: boolean, disconnecting: boolean,
tags: string[] tags: string[],
textures: CharacterTexture[],
} }

View File

@ -1,6 +1,11 @@
import {PointInterface} from "./PointInterface"; import {PointInterface} from "./PointInterface";
import {ItemEventMessage, PointMessage, PositionMessage} from "../../Messages/generated/messages_pb"; import {
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; CharacterLayerMessage,
ItemEventMessage,
PointMessage,
PositionMessage
} from "../../Messages/generated/messages_pb";
import {CharacterLayer, ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
import Direction = PositionMessage.Direction; import Direction = PositionMessage.Direction;
import {ItemEventMessageInterface} from "_Model/Websocket/ItemEventMessage"; import {ItemEventMessageInterface} from "_Model/Websocket/ItemEventMessage";
import {PositionInterface} from "_Model/PositionInterface"; import {PositionInterface} from "_Model/PositionInterface";
@ -89,4 +94,15 @@ export class ProtobufUtils {
return itemEventMessage; return itemEventMessage;
} }
public static toCharacterLayerMessages(characterLayers: CharacterLayer[]): CharacterLayerMessage[] {
return characterLayers.map(function(characterLayer): CharacterLayerMessage {
const message = new CharacterLayerMessage();
message.setName(characterLayer.name);
if (characterLayer.url) {
message.setUrl(characterLayer.url);
}
return message;
});
}
} }

View File

@ -1,5 +1,6 @@
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable"; import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
import Axios from "axios"; import Axios from "axios";
import {v4} from "uuid";
export interface AdminApiData { export interface AdminApiData {
organizationSlug: string organizationSlug: string
@ -9,12 +10,21 @@ export interface AdminApiData {
tags: string[] tags: string[]
policy_type: number policy_type: number
userUuid: string userUuid: string
messages?: unknown[] messages?: unknown[],
textures: CharacterTexture[]
} }
export interface fetchMemberDataByUuidResponse { export interface CharacterTexture {
id: number,
level: number,
url: string,
rights: string
}
export interface FetchMemberDataByUuidResponse {
uuid: string; uuid: string;
tags: string[]; tags: string[];
textures: CharacterTexture[];
messages: unknown[]; messages: unknown[];
} }
@ -43,14 +53,29 @@ class AdminApi {
return res.data; return res.data;
} }
async fetchMemberDataByUuid(uuid: string): Promise<fetchMemberDataByUuidResponse> { async fetchMemberDataByUuid(uuid: string): Promise<FetchMemberDataByUuidResponse> {
if (!ADMIN_API_URL) { if (!ADMIN_API_URL) {
return Promise.reject('No admin backoffice set!'); return Promise.reject('No admin backoffice set!');
} }
const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid, try {
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} } const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid,
) { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
return res.data; )
return res.data;
} catch (e) {
if (e?.response?.status == 404) {
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
console.warn('Cannot find user with uuid "'+uuid+'". Performing an anonymous login instead.');
return {
uuid: v4(),
tags: [],
textures: [],
messages: [],
}
} else {
throw e;
}
}
} }
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> { async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {

View File

@ -1,5 +1,5 @@
import {GameRoom} from "../Model/GameRoom"; import {GameRoom} from "../Model/GameRoom";
import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface";
import { import {
GroupDeleteMessage, GroupDeleteMessage,
GroupUpdateMessage, GroupUpdateMessage,
@ -23,6 +23,7 @@ import {
WebRtcStartMessage, WebRtcStartMessage,
QueryJitsiJwtMessage, QueryJitsiJwtMessage,
SendJitsiJwtMessage, SendJitsiJwtMessage,
CharacterLayerMessage,
SendUserMessage SendUserMessage
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import {PointInterface} from "../Model/Websocket/PointInterface"; import {PointInterface} from "../Model/Websocket/PointInterface";
@ -34,7 +35,7 @@ import {isSetPlayerDetailsMessage} from "../Model/Websocket/SetPlayerDetailsMess
import {GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable"; import {GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
import {Movable} from "../Model/Movable"; import {Movable} from "../Model/Movable";
import {PositionInterface} from "../Model/PositionInterface"; import {PositionInterface} from "../Model/PositionInterface";
import {adminApi} from "./AdminApi"; import {adminApi, CharacterTexture} from "./AdminApi";
import Direction = PositionMessage.Direction; import Direction = PositionMessage.Direction;
import {Gauge} from "prom-client"; import {Gauge} from "prom-client";
import {emitError, emitInBatch} from "./IoSocketHelpers"; import {emitError, emitInBatch} from "./IoSocketHelpers";
@ -54,7 +55,7 @@ export interface AdminSocketData {
users: AdminSocketUsersList, users: AdminSocketUsersList,
} }
class SocketManager { export class SocketManager {
private Worlds: Map<string, GameRoom> = new Map<string, GameRoom>(); private Worlds: Map<string, GameRoom> = new Map<string, GameRoom>();
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>(); private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
private nbClientsGauge: Gauge<string>; private nbClientsGauge: Gauge<string>;
@ -125,7 +126,7 @@ class SocketManager {
const userJoinedMessage = new UserJoinedMessage(); const userJoinedMessage = new UserJoinedMessage();
userJoinedMessage.setUserid(thing.id); userJoinedMessage.setUserid(thing.id);
userJoinedMessage.setName(player.name); userJoinedMessage.setName(player.name);
userJoinedMessage.setCharacterlayersList(player.characterLayers); userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(player.characterLayers));
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(player.position)); userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(player.position));
roomJoinedMessage.addUser(userJoinedMessage); roomJoinedMessage.addUser(userJoinedMessage);
@ -251,8 +252,7 @@ class SocketManager {
return; return;
} }
client.name = playerDetails.name; client.name = playerDetails.name;
client.characterLayers = playerDetails.characterLayers; client.characterLayers = SocketManager.mergeCharacterLayersAndCustomTextures(playerDetails.characterLayers, client.textures);
} }
handleSilentMessage(client: ExSocketInterface, silentMessage: SilentMessage) { handleSilentMessage(client: ExSocketInterface, silentMessage: SilentMessage) {
@ -438,7 +438,7 @@ class SocketManager {
} }
userJoinedMessage.setUserid(clientUser.userId); userJoinedMessage.setUserid(clientUser.userId);
userJoinedMessage.setName(clientUser.name); userJoinedMessage.setName(clientUser.name);
userJoinedMessage.setCharacterlayersList(clientUser.characterLayers); userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(clientUser.characterLayers));
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position)); userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position));
const subMessage = new SubMessage(); const subMessage = new SubMessage();
@ -691,6 +691,33 @@ class SocketManager {
} }
return socket; return socket;
} }
/**
* Merges the characterLayers received from the front (as an array of string) with the custom textures from the back.
*/
static mergeCharacterLayersAndCustomTextures(characterLayers: string[], memberTextures: CharacterTexture[]): CharacterLayer[] {
const characterLayerObjs: CharacterLayer[] = [];
for (const characterLayer of characterLayers) {
if (characterLayer.startsWith('customCharacterTexture')) {
const customCharacterLayerId: number = +characterLayer.substr(22);
for (const memberTexture of memberTextures) {
if (memberTexture.id == customCharacterLayerId) {
characterLayerObjs.push({
name: characterLayer,
url: memberTexture.url
})
break;
}
}
} else {
characterLayerObjs.push({
name: characterLayer,
url: undefined
})
}
}
return characterLayerObjs;
}
} }
export const socketManager = new SocketManager(); export const socketManager = new SocketManager();

View File

@ -21,7 +21,7 @@ class ConnectionManager {
if(connexionType === GameConnexionTypes.register) { if(connexionType === GameConnexionTypes.register) {
const organizationMemberToken = urlManager.getOrganizationToken(); const organizationMemberToken = urlManager.getOrganizationToken();
const data = await Axios.post(`${API_URL}/register`, {organizationMemberToken}).then(res => res.data); const data = await Axios.post(`${API_URL}/register`, {organizationMemberToken}).then(res => res.data);
this.localUser = new LocalUser(data.userUuid, data.authToken); this.localUser = new LocalUser(data.userUuid, data.authToken, data.textures);
localUserStore.saveUser(this.localUser); localUserStore.saveUser(this.localUser);
const organizationSlug = data.organizationSlug; const organizationSlug = data.organizationSlug;
@ -34,7 +34,7 @@ class ConnectionManager {
} else if (connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) { } else if (connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
const localUser = localUserStore.getLocalUser(); const localUser = localUserStore.getLocalUser();
if (localUser && localUser.jwtToken && localUser.uuid) { if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) {
this.localUser = localUser; this.localUser = localUser;
try { try {
await this.verifyToken(localUser.jwtToken); await this.verifyToken(localUser.jwtToken);
@ -78,12 +78,12 @@ class ConnectionManager {
private async anonymousLogin(): Promise<void> { private async anonymousLogin(): Promise<void> {
const data = await Axios.post(`${API_URL}/anonymLogin`).then(res => res.data); const data = await Axios.post(`${API_URL}/anonymLogin`).then(res => res.data);
this.localUser = new LocalUser(data.userUuid, data.authToken); this.localUser = new LocalUser(data.userUuid, data.authToken, []);
localUserStore.saveUser(this.localUser); localUserStore.saveUser(this.localUser);
} }
public initBenchmark(): void { public initBenchmark(): void {
this.localUser = new LocalUser('', 'test'); this.localUser = new LocalUser('', 'test', []);
} }
public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface): Promise<RoomConnection> { public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface): Promise<RoomConnection> {

View File

@ -1,6 +1,7 @@
import {PlayerAnimationNames} from "../Phaser/Player/Animation"; import {PlayerAnimationNames} from "../Phaser/Player/Animation";
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
import {SignalData} from "simple-peer"; import {SignalData} from "simple-peer";
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/body_character";
export enum EventMessage{ export enum EventMessage{
WEBRTC_SIGNAL = "webrtc-signal", WEBRTC_SIGNAL = "webrtc-signal",
@ -49,7 +50,7 @@ export class Point implements PointInterface{
export interface MessageUserPositionInterface { export interface MessageUserPositionInterface {
userId: number; userId: number;
name: string; name: string;
characterLayers: string[]; characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface; position: PointInterface;
} }
@ -61,7 +62,7 @@ export interface MessageUserMovedInterface {
export interface MessageUserJoined { export interface MessageUserJoined {
userId: number; userId: number;
name: string; name: string;
characterLayers: string[]; characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface position: PointInterface
} }

View File

@ -1,9 +1,11 @@
export class LocalUser { export interface CharacterTexture {
public uuid: string; id: number,
public jwtToken: string; level: number,
url: string,
rights: string
}
constructor(uuid:string, jwtToken: string) { export class LocalUser {
this.uuid = uuid; constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) {
this.jwtToken = jwtToken;
} }
} }

View File

@ -6,7 +6,6 @@ class LocalUserStore {
saveUser(localUser: LocalUser) { saveUser(localUser: LocalUser) {
localStorage.setItem('localUser', JSON.stringify(localUser)); localStorage.setItem('localUser', JSON.stringify(localUser));
} }
getLocalUser(): LocalUser|null { getLocalUser(): LocalUser|null {
const data = localStorage.getItem('localUser'); const data = localStorage.getItem('localUser');
return data ? JSON.parse(data) : null; return data ? JSON.parse(data) : null;
@ -15,11 +14,23 @@ class LocalUserStore {
setName(name:string): void { setName(name:string): void {
window.localStorage.setItem('playerName', name); window.localStorage.setItem('playerName', name);
} }
getName(): string { getName(): string {
return window.localStorage.getItem('playerName') ?? ''; return window.localStorage.getItem('playerName') ?? '';
} }
setPlayerCharacterIndex(playerCharacterIndex: number): void {
window.localStorage.setItem('selectedPlayer', ''+playerCharacterIndex);
}
getPlayerCharacterIndex(): number {
return parseInt(window.localStorage.getItem('selectedPlayer') || '');
}
setCustomCursorPosition(activeRow:number, selectedLayers: number[]): void {
window.localStorage.setItem('customCursorPosition', JSON.stringify({activeRow, selectedLayers}));
}
getCustomCursorPosition(): {activeRow:number, selectedLayers:number[]}|null {
return JSON.parse(window.localStorage.getItem('customCursorPosition') || "null");
}
} }
export const localUserStore = new LocalUserStore(); export const localUserStore = new LocalUserStore();

View File

@ -22,7 +22,11 @@ import {
WebRtcSignalToServerMessage, WebRtcSignalToServerMessage,
WebRtcStartMessage, WebRtcStartMessage,
ReportPlayerMessage, ReportPlayerMessage,
TeleportMessageMessage, QueryJitsiJwtMessage, SendJitsiJwtMessage, SendUserMessage TeleportMessageMessage,
QueryJitsiJwtMessage,
SendJitsiJwtMessage,
CharacterLayerMessage,
SendUserMessage
} from "../Messages/generated/messages_pb" } from "../Messages/generated/messages_pb"
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
@ -36,6 +40,7 @@ import {
ViewportInterface, WebRtcDisconnectMessageInterface, ViewportInterface, WebRtcDisconnectMessageInterface,
WebRtcSignalReceivedMessageInterface, WebRtcSignalReceivedMessageInterface,
} from "./ConnexionModels"; } from "./ConnexionModels";
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/body_character";
export class RoomConnection implements RoomConnection { export class RoomConnection implements RoomConnection {
private readonly socket: WebSocket; private readonly socket: WebSocket;
@ -169,10 +174,10 @@ export class RoomConnection implements RoomConnection {
} }
} }
public emitPlayerDetailsMessage(userName: string, characterLayersSelected: string[]) { public emitPlayerDetailsMessage(userName: string, characterLayersSelected: BodyResourceDescriptionInterface[]) {
const message = new SetPlayerDetailsMessage(); const message = new SetPlayerDetailsMessage();
message.setName(userName); message.setName(userName);
message.setCharacterlayersList(characterLayersSelected); message.setCharacterlayersList(characterLayersSelected.map((characterLayer) => characterLayer.name));
const clientToServerMessage = new ClientToServerMessage(); const clientToServerMessage = new ClientToServerMessage();
clientToServerMessage.setSetplayerdetailsmessage(message); clientToServerMessage.setSetplayerdetailsmessage(message);
@ -277,10 +282,18 @@ export class RoomConnection implements RoomConnection {
if (position === undefined) { if (position === undefined) {
throw new Error('Invalid JOIN_ROOM message'); throw new Error('Invalid JOIN_ROOM message');
} }
const characterLayers = message.getCharacterlayersList().map((characterLayer: CharacterLayerMessage): BodyResourceDescriptionInterface => {
return {
name: characterLayer.getName(),
img: characterLayer.getUrl()
}
})
return { return {
userId: message.getUserid(), userId: message.getUserid(),
name: message.getName(), name: message.getName(),
characterLayers: message.getCharacterlayersList(), characterLayers,
position: ProtobufClientUtils.toPointInterface(position) position: ProtobufClientUtils.toPointInterface(position)
} }
} }

View File

@ -61,22 +61,7 @@ export abstract class Character extends Container {
this.sprites = new Map<string, Sprite>(); this.sprites = new Map<string, Sprite>();
for (const texture of textures) { this.addTextures(textures, frame);
const sprite = new Sprite(scene, 0, 0, texture, frame);
sprite.setInteractive({useHandCursor: true});
this.add(sprite);
this.getPlayerAnimations(texture).forEach(d => {
this.scene.anims.create({
key: d.key,
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}),
frameRate: d.frameRate,
repeat: d.repeat
});
})
// Needed, otherwise, animations are not handled correctly.
this.scene.sys.updateList.add(sprite);
this.sprites.set(texture, sprite);
}
/*this.teleportation = new Sprite(scene, -20, -10, 'teleportation', 3); /*this.teleportation = new Sprite(scene, -20, -10, 'teleportation', 3);
this.teleportation.setInteractive(); this.teleportation.setInteractive();
@ -107,6 +92,25 @@ export abstract class Character extends Container {
this.playAnimation(direction, moving); this.playAnimation(direction, moving);
} }
public addTextures(textures: string[], frame?: string | number): void {
for (const texture of textures) {
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
sprite.setInteractive({useHandCursor: true});
this.add(sprite);
this.getPlayerAnimations(texture).forEach(d => {
this.scene.anims.create({
key: d.key,
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}),
frameRate: d.frameRate,
repeat: d.repeat
});
})
// Needed, otherwise, animations are not handled correctly.
this.scene.sys.updateList.add(sprite);
this.sprites.set(texture, sprite);
}
}
private getPlayerAnimations(name: string): AnimationData[] { private getPlayerAnimations(name: string): AnimationData[] {
return [{ return [{
key: `${name}-${PlayerAnimationNames.WalkDown}`, key: `${name}-${PlayerAnimationNames.WalkDown}`,

View File

@ -1,5 +1,6 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin; import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "./Character"; import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "./Character";
import {CharacterTexture} from "../../Connexion/LocalUser";
export interface BodyResourceDescriptionInterface { export interface BodyResourceDescriptionInterface {
name: string, name: string,
@ -312,6 +313,15 @@ export const loadAllLayers = (load: LoaderPlugin) => {
} }
} }
export const loadCustomTexture = (load: LoaderPlugin, texture: CharacterTexture) => {
const name = 'customCharacterTexture'+texture.id;
load.spritesheet(
name,
texture.url,
{frameWidth: 32, frameHeight: 32}
);
}
export const OBJECTS: Array<PlayerResourceDescriptionInterface> = [ export const OBJECTS: Array<PlayerResourceDescriptionInterface> = [
{name:'layout_modes', img:'resources/objects/layout_modes.png'}, {name:'layout_modes', img:'resources/objects/layout_modes.png'},
{name:'teleportation', img:'resources/objects/teleportation.png'}, {name:'teleportation', img:'resources/objects/teleportation.png'},

View File

@ -1,8 +1,9 @@
import {PointInterface} from "../../Connexion/ConnexionModels"; import {PointInterface} from "../../Connexion/ConnexionModels";
import {BodyResourceDescriptionInterface} from "../Entity/body_character";
export interface AddPlayerInterface { export interface AddPlayerInterface {
userId: number; userId: number;
name: string; name: string;
characterLayers: string[]; characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface; position: PointInterface;
} }

View File

@ -1,7 +1,6 @@
import {GameScene} from "./GameScene"; import {GameScene} from "./GameScene";
import {connectionManager} from "../../Connexion/ConnectionManager"; import {connectionManager} from "../../Connexion/ConnectionManager";
import {Room} from "../../Connexion/Room"; import {Room} from "../../Connexion/Room";
import {FourOFourSceneName} from "../Reconnecting/FourOFourScene";
export interface HasMovedEvent { export interface HasMovedEvent {
direction: string; direction: string;
@ -24,11 +23,7 @@ export class GameManager {
this.playerName = name; this.playerName = name;
} }
public setCharacterUserSelected(characterUserSelected : string): void { public setCharacterLayers(layers: string[]): void {
this.characterLayers = [characterUserSelected];
}
public setCharacterLayers(layers: string[]) {
this.characterLayers = layers; this.characterLayers = layers;
} }
@ -54,13 +49,6 @@ export class GameManager {
} }
} }
public getMapKeyByUrl(mapUrlStart: string) : string {
// FIXME: the key should be computed from the full URL of the map.
const startPos = mapUrlStart.indexOf('://')+3;
const endPos = mapUrlStart.indexOf(".json");
return mapUrlStart.substring(startPos, endPos);
}
public async goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin) { public async goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin) {
const url = await this.startRoom.getMapUrl(); const url = await this.startRoom.getMapUrl();
console.log('Starting scene '+url); console.log('Starting scene '+url);

View File

@ -32,7 +32,7 @@ import {RemotePlayer} from "../Entity/RemotePlayer";
import {Queue} from 'queue-typescript'; import {Queue} from 'queue-typescript';
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer"; import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
import {loadAllLayers, loadObject, loadPlayerCharacters} from "../Entity/body_character"; import {loadAllLayers, loadCustomTexture, loadObject, loadPlayerCharacters} from "../Entity/body_character";
import {CenterListener, layoutManager, LayoutMode} from "../../WebRtc/LayoutManager"; import {CenterListener, layoutManager, LayoutMode} from "../../WebRtc/LayoutManager";
import Texture = Phaser.Textures.Texture; import Texture = Phaser.Textures.Texture;
import Sprite = Phaser.GameObjects.Sprite; import Sprite = Phaser.GameObjects.Sprite;
@ -475,7 +475,6 @@ export class GameScene extends ResizableScene implements CenterListener {
if (newValue === undefined) { if (newValue === undefined) {
this.stopJitsi(); this.stopJitsi();
} else { } else {
console.log("JITSI_PRIVATE_MODE", JITSI_PRIVATE_MODE);
if (JITSI_PRIVATE_MODE) { if (JITSI_PRIVATE_MODE) {
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined; const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
@ -1022,7 +1021,7 @@ export class GameScene extends ResizableScene implements CenterListener {
/** /**
* Create new player * Create new player
*/ */
private doAddPlayer(addPlayerData : AddPlayerInterface) : void { private async doAddPlayer(addPlayerData : AddPlayerInterface) : Promise<void> {
//check if exist player, if exist, move position //check if exist player, if exist, move position
if(this.MapPlayersByKey.has(addPlayerData.userId)){ if(this.MapPlayersByKey.has(addPlayerData.userId)){
this.updatePlayerPosition({ this.updatePlayerPosition({
@ -1031,6 +1030,20 @@ export class GameScene extends ResizableScene implements CenterListener {
}); });
return; return;
} }
// Load textures (in case it is a custom texture)
const characterLayerList: string[] = [];
const loadPromises: Promise<void>[] = [];
for (const characterLayer of addPlayerData.characterLayers) {
characterLayerList.push(characterLayer.name);
if (characterLayer.img) {
console.log('LOADING ', characterLayer.name, characterLayer.img)
loadPromises.push(this.loadSpritesheet(characterLayer.name, characterLayer.img));
}
}
if (loadPromises.length > 0) {
this.load.start();
}
//initialise player //initialise player
const player = new RemotePlayer( const player = new RemotePlayer(
addPlayerData.userId, addPlayerData.userId,
@ -1038,7 +1051,7 @@ export class GameScene extends ResizableScene implements CenterListener {
addPlayerData.position.x, addPlayerData.position.x,
addPlayerData.position.y, addPlayerData.position.y,
addPlayerData.name, addPlayerData.name,
addPlayerData.characterLayers, [], // Let's go with no textures and let's load textures when promises have returned.
addPlayerData.position.direction, addPlayerData.position.direction,
addPlayerData.position.moving addPlayerData.position.moving
); );
@ -1046,10 +1059,15 @@ export class GameScene extends ResizableScene implements CenterListener {
this.MapPlayersByKey.set(player.userId, player); this.MapPlayersByKey.set(player.userId, player);
player.updatePosition(addPlayerData.position); player.updatePosition(addPlayerData.position);
await Promise.all(loadPromises);
player.addTextures(characterLayerList, 1);
//init collision //init collision
/*this.physics.add.collider(this.CurrentPlayer, player, (CurrentPlayer: CurrentGamerInterface, MapPlayer: GamerInterface) => { /*this.physics.add.collider(this.CurrentPlayer, player, (CurrentPlayer: CurrentGamerInterface, MapPlayer: GamerInterface) => {
CurrentPlayer.say("Hello, how are you ? "); CurrentPlayer.say("Hello, how are you ? ");
});*/ });*/
} }
/** /**
@ -1245,4 +1263,18 @@ export class GameScene extends ResizableScene implements CenterListener {
CoWebsiteManager.closeCoWebsite(); CoWebsiteManager.closeCoWebsite();
mediaManager.showGameOverlay(); mediaManager.showGameOverlay();
} }
private loadSpritesheet(name: string, url: string): Promise<void> {
return new Promise<void>(((resolve, reject) => {
this.load.spritesheet(
name,
url,
{frameWidth: 32, frameHeight: 32}
);
this.load.on('filecomplete-spritesheet-'+name, () => {
console.log('RESOURCE LOADED!');
resolve();
});
}))
}
} }

View File

@ -2,11 +2,13 @@ import {EnableCameraSceneName} from "./EnableCameraScene";
import {TextField} from "../Components/TextField"; import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image; import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle; import Rectangle = Phaser.GameObjects.Rectangle;
import {LAYERS, loadAllLayers} from "../Entity/body_character"; import {BodyResourceDescriptionInterface, LAYERS, loadAllLayers, loadCustomTexture} from "../Entity/body_character";
import Sprite = Phaser.GameObjects.Sprite; import Sprite = Phaser.GameObjects.Sprite;
import Container = Phaser.GameObjects.Container; import Container = Phaser.GameObjects.Container;
import {gameManager} from "../Game/GameManager"; import {gameManager} from "../Game/GameManager";
import {ResizableScene} from "./ResizableScene"; import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {PlayerResourceDescriptionInterface} from "../Entity/Character";
export const CustomizeSceneName = "CustomizeScene"; export const CustomizeSceneName = "CustomizeScene";
@ -32,9 +34,10 @@ export class CustomizeScene extends ResizableScene {
private logo!: Image; private logo!: Image;
private selectedLayers: Array<number> = [0]; private selectedLayers: number[] = [0];
private containersRow: Array<Array<Container>> = new Array<Array<Container>>(); private containersRow: Container[][] = [];
private activeRow = 0; private activeRow:number = 0;
private layers: BodyResourceDescriptionInterface[][] = [];
constructor() { constructor() {
super({ super({
@ -50,6 +53,23 @@ export class CustomizeScene extends ResizableScene {
//load all the png files //load all the png files
loadAllLayers(this.load); loadAllLayers(this.load);
// load custom layers
this.layers = LAYERS;
const localUser = localUserStore.getLocalUser();
const textures = localUser?.textures;
if (textures) {
for (const texture of textures) {
loadCustomTexture(this.load, texture);
const name = 'customCharacterTexture'+texture.id;
this.layers[texture.level].unshift({
name,
img: texture.url
});
}
}
} }
create() { create() {
@ -93,7 +113,7 @@ export class CustomizeScene extends ResizableScene {
let i = 0; let i = 0;
for (const layerItem of this.selectedLayers) { for (const layerItem of this.selectedLayers) {
if (layerItem !== undefined) { if (layerItem !== undefined) {
layers.push(LAYERS[i][layerItem].name); layers.push(this.layers[i][layerItem].name);
} }
i++; i++;
} }
@ -103,43 +123,47 @@ export class CustomizeScene extends ResizableScene {
return this.scene.start(EnableCameraSceneName); return this.scene.start(EnableCameraSceneName);
}); });
this.input.keyboard.on('keydown-RIGHT', () => { this.input.keyboard.on('keydown-RIGHT', () => this.moveCursorHorizontally(1));
if (this.selectedLayers[this.activeRow] === undefined) { this.input.keyboard.on('keydown-LEFT', () => this.moveCursorHorizontally(-1));
this.selectedLayers[this.activeRow] = 0; this.input.keyboard.on('keydown-DOWN', () => this.moveCursorVertically(1));
} this.input.keyboard.on('keydown-UP', () => this.moveCursorVertically(-1));
if (this.selectedLayers[this.activeRow] < LAYERS[this.activeRow].length - 1) {
this.selectedLayers[this.activeRow]++;
this.moveLayers();
this.updateSelectedLayer();
}
});
this.input.keyboard.on('keydown-LEFT', () => { const customCursorPosition = localUserStore.getCustomCursorPosition();
if (this.selectedLayers[this.activeRow] > 0) { if (customCursorPosition) {
if (this.selectedLayers[this.activeRow] === 0) { this.activeRow = customCursorPosition.activeRow;
delete this.selectedLayers[this.activeRow]; this.selectedLayers = customCursorPosition.selectedLayers;
} else { this.moveLayers();
this.selectedLayers[this.activeRow]--; this.updateSelectedLayer();
} }
this.moveLayers();
this.updateSelectedLayer();
}
});
this.input.keyboard.on('keydown-DOWN', () => {
if (this.activeRow < LAYERS.length - 1) {
this.activeRow++;
this.moveLayers();
}
});
this.input.keyboard.on('keydown-UP', () => {
if (this.activeRow > 0) {
this.activeRow--;
this.moveLayers();
}
});
} }
private moveCursorHorizontally(index: number): void {
this.selectedLayers[this.activeRow] += index;
if (this.selectedLayers[this.activeRow] < 0) {
this.selectedLayers[this.activeRow] = 0
} else if(this.selectedLayers[this.activeRow] > this.layers[this.activeRow].length - 1) {
this.selectedLayers[this.activeRow] = this.layers[this.activeRow].length - 1
}
this.moveLayers();
this.updateSelectedLayer();
this.saveInLocalStorage();
}
private moveCursorVertically(index:number): void {
this.activeRow += index;
if (this.activeRow < 0) {
this.activeRow = 0
} else if (this.activeRow > this.layers.length - 1) {
this.activeRow = this.layers.length - 1
}
this.moveLayers();
this.saveInLocalStorage();
}
private saveInLocalStorage() {
localUserStore.setCustomCursorPosition(this.activeRow, this.selectedLayers);
}
update(time: number, delta: number): void { update(time: number, delta: number): void {
super.update(time, delta); super.update(time, delta);
this.enterField.setVisible(!!(Math.floor(time / 500) % 2)); this.enterField.setVisible(!!(Math.floor(time / 500) % 2));
@ -148,14 +172,15 @@ export class CustomizeScene extends ResizableScene {
/** /**
* @param x, the layer's vertical position * @param x, the layer's vertical position
* @param y, the layer's horizontal position * @param y, the layer's horizontal position
* @param layerNumber, index of the LAYERS array * @param layerNumber, index of the this.layers array
* create the layer and display it on the scene * create the layer and display it on the scene
*/ */
private createCustomizeLayer(x: number, y: number, layerNumber: number): void { private createCustomizeLayer(x: number, y: number, layerNumber: number): void {
this.containersRow[layerNumber] = new Array<Container>(); this.containersRow[layerNumber] = [];
this.selectedLayers[layerNumber] = 0;
let alpha = 0; let alpha = 0;
let layerPosX = 0; let layerPosX = 0;
for (let i = 0; i < LAYERS[layerNumber].length; i++) { for (let i = 0; i < this.layers[layerNumber].length; i++) {
const container = this.generateCharacter(300 + x + layerPosX, y, layerNumber, i); const container = this.generateCharacter(300 + x + layerPosX, y, layerNumber, i);
this.containersRow[layerNumber][i] = container; this.containersRow[layerNumber][i] = container;
@ -184,13 +209,13 @@ export class CustomizeScene extends ResizableScene {
const children: Array<Sprite> = new Array<Sprite>(); const children: Array<Sprite> = new Array<Sprite>();
for (let j = 0; j <= layerNumber; j++) { for (let j = 0; j <= layerNumber; j++) {
if (j === layerNumber) { if (j === layerNumber) {
children.push(this.generateLayers(0, 0, LAYERS[j][selectedItem].name)); children.push(this.generateLayers(0, 0, this.layers[j][selectedItem].name));
} else { } else {
const layer = this.selectedLayers[j]; const layer = this.selectedLayers[j];
if (layer === undefined) { if (layer === undefined) {
continue; continue;
} }
children.push(this.generateLayers(0, 0, LAYERS[j][layer].name)); children.push(this.generateLayers(0, 0, this.layers[j][layer].name));
} }
} }
return children; return children;

View File

@ -6,6 +6,7 @@ import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Ch
import {EnableCameraSceneName} from "./EnableCameraScene"; import {EnableCameraSceneName} from "./EnableCameraScene";
import {CustomizeSceneName} from "./CustomizeScene"; import {CustomizeSceneName} from "./CustomizeScene";
import {ResizableScene} from "./ResizableScene"; import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
//todo: put this constants in a dedicated file //todo: put this constants in a dedicated file
@ -99,12 +100,14 @@ export class SelectCharacterScene extends ResizableScene {
/*create user*/ /*create user*/
this.createCurrentPlayer(); this.createCurrentPlayer();
if (window.localStorage) { const playerNumber = localUserStore.getPlayerCharacterIndex();
const playerNumberStr: string = window.localStorage.getItem('selectedPlayer') ?? '0'; if (playerNumber && playerNumber !== -1) {
const playerNumber: number = Number(playerNumberStr);
this.selectedRectangleXPos = playerNumber % this.nbCharactersPerRow; this.selectedRectangleXPos = playerNumber % this.nbCharactersPerRow;
this.selectedRectangleYPos = Math.floor(playerNumber / this.nbCharactersPerRow); this.selectedRectangleYPos = Math.floor(playerNumber / this.nbCharactersPerRow);
this.updateSelectedPlayer(); this.updateSelectedPlayer();
} else if (playerNumber === -1) {
this.selectedRectangleYPos = Math.ceil(PLAYER_RESOURCES.length / this.nbCharactersPerRow);
this.updateSelectedPlayer();
} }
} }
@ -113,33 +116,14 @@ export class SelectCharacterScene extends ResizableScene {
} }
private nextScene(): void { private nextScene(): void {
if (this.selectedPlayer !== null) { if (this.selectedPlayer !== null) {
gameManager.setCharacterUserSelected(this.selectedPlayer.texture.key); gameManager.setCharacterLayers([this.selectedPlayer.texture.key]);
this.scene.start(EnableCameraSceneName); this.scene.start(EnableCameraSceneName);
} else { } else {
this.scene.start(CustomizeSceneName); this.scene.start(CustomizeSceneName);
} }
} }
/**
* Returns the map URL and the instance from the current URL
*/
private findMapUrl(): [string, string]|null {
const path = window.location.pathname;
if (!path.startsWith('/_/')) {
return null;
}
const instanceAndMap = path.substr(3);
const firstSlash = instanceAndMap.indexOf('/');
if (firstSlash === -1) {
return null;
}
const instance = instanceAndMap.substr(0, firstSlash);
return [window.location.protocol+'//'+instanceAndMap.substr(firstSlash+1), instance];
}
createCurrentPlayer(): void { createCurrentPlayer(): void {
for (let i = 0; i <PLAYER_RESOURCES.length; i++) { for (let i = 0; i <PLAYER_RESOURCES.length; i++) {
const playerResource = PLAYER_RESOURCES[i]; const playerResource = PLAYER_RESOURCES[i];
@ -200,6 +184,7 @@ export class SelectCharacterScene extends ResizableScene {
this.selectedRectangle.setVisible(false); this.selectedRectangle.setVisible(false);
this.customizeButtonSelected.setVisible(true); this.customizeButtonSelected.setVisible(true);
this.customizeButton.setVisible(false); this.customizeButton.setVisible(false);
localUserStore.setPlayerCharacterIndex(-1);
return; return;
} }
this.customizeButtonSelected.setVisible(false); this.customizeButtonSelected.setVisible(false);
@ -213,9 +198,7 @@ export class SelectCharacterScene extends ResizableScene {
const player = this.players[playerNumber]; const player = this.players[playerNumber];
player.play(PLAYER_RESOURCES[playerNumber].name); player.play(PLAYER_RESOURCES[playerNumber].name);
this.selectedPlayer = player; this.selectedPlayer = player;
if (window.localStorage) { localUserStore.setPlayerCharacterIndex(playerNumber);
window.localStorage.setItem('selectedPlayer', String(playerNumber));
}
} }
public onResize(ev: UIEvent): void { public onResize(ev: UIEvent): void {

View File

@ -455,8 +455,7 @@ export class MediaManager {
addStreamRemoteVideo(userId: string, stream : MediaStream){ addStreamRemoteVideo(userId: string, stream : MediaStream){
const remoteVideo = this.remoteVideo.get(userId); const remoteVideo = this.remoteVideo.get(userId);
if (remoteVideo === undefined) { if (remoteVideo === undefined) {
console.error('Unable to find video for ', userId); throw `Unable to find video for ${userId}`;
return;
} }
remoteVideo.srcObject = stream; remoteVideo.srcObject = stream;
} }

View File

@ -126,10 +126,19 @@ export class SimplePeer {
* create peer connection to bind users * create peer connection to bind users
*/ */
private createPeerConnection(user : UserSimplePeerInterface) : VideoPeer | null{ private createPeerConnection(user : UserSimplePeerInterface) : VideoPeer | null{
if( const peerConnection = this.PeerConnectionArray.get(user.userId)
this.PeerConnectionArray.has(user.userId) if(peerConnection){
){ if(peerConnection.destroyed){
console.log('Peer connection already exists to user '+user.userId) peerConnection.toClose = true;
peerConnection.destroy();
const peerConnexionDeleted = this.PeerConnectionArray.delete(user.userId);
if(!peerConnexionDeleted){
throw 'Error to delete peer connection';
}
this.createPeerConnection(user);
}else {
peerConnection.toClose = false;
}
return null; return null;
} }
@ -150,6 +159,7 @@ export class SimplePeer {
mediaManager.addActiveVideo("" + user.userId, reportCallback, name); mediaManager.addActiveVideo("" + user.userId, reportCallback, name);
const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
peer.toClose = false;
// When a connection is established to a video stream, and if a screen sharing is taking place, // 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! // the user sharing screen should also initiate a connection to the remote user!
peer.on('connect', () => { peer.on('connect', () => {
@ -200,16 +210,17 @@ export class SimplePeer {
//mediaManager.removeActiveVideo(userId); //mediaManager.removeActiveVideo(userId);
const peer = this.PeerConnectionArray.get(userId); const peer = this.PeerConnectionArray.get(userId);
if (peer === undefined) { if (peer === undefined) {
console.warn("Tried to close connection for user "+userId+" but could not find user") console.warn("closeConnection => Tried to close connection for user "+userId+" but could not find user");
return; return;
} }
//create temp perr to close
peer.toClose = true;
peer.destroy(); peer.destroy();
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
//console.log('Closing connection with '+userId);
this.PeerConnectionArray.delete(userId);
this.closeScreenSharingConnection(userId); this.closeScreenSharingConnection(userId);
//console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size);
for (const peerConnectionListener of this.peerConnectionListeners) { for (const peerConnectionListener of this.peerConnectionListeners) {
peerConnectionListener.onDisconnect(userId); peerConnectionListener.onDisconnect(userId);
} }
@ -228,14 +239,16 @@ export class SimplePeer {
mediaManager.removeActiveScreenSharingVideo("" + userId); mediaManager.removeActiveScreenSharingVideo("" + userId);
const peer = this.PeerScreenSharingConnectionArray.get(userId); const peer = this.PeerScreenSharingConnectionArray.get(userId);
if (peer === undefined) { if (peer === undefined) {
console.warn("Tried to close connection for user "+userId+" but could not find user") console.warn("closeScreenSharingConnection => Tried to close connection for user "+userId+" but could not find user")
return; return;
} }
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
//console.log('Closing connection with '+userId); //console.log('Closing connection with '+userId);
peer.destroy(); peer.destroy();
this.PeerScreenSharingConnectionArray.delete(userId) if(!this.PeerScreenSharingConnectionArray.delete(userId)){
throw 'Couln\'t delete peer screen sharing connexion';
}
//console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size); //console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size);
} catch (err) { } catch (err) {
console.error("closeConnection", err) console.error("closeConnection", err)
@ -292,6 +305,9 @@ export class SimplePeer {
} }
} catch (e) { } catch (e) {
console.error(`receiveWebrtcSignal => ${data.userId}`, e); console.error(`receiveWebrtcSignal => ${data.userId}`, e);
//force delete and recreate peer connexion
this.PeerScreenSharingConnectionArray.delete(data.userId);
this.receiveWebrtcScreenSharingSignal(data);
} }
} }

View File

@ -9,7 +9,10 @@ const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
* A peer connection used to transmit video / audio signals between 2 peers. * A peer connection used to transmit video / audio signals between 2 peers.
*/ */
export class VideoPeer extends Peer { export class VideoPeer extends Peer {
constructor(private userId: number, initiator: boolean, private connection: RoomConnection) { public toClose: boolean = false;
public _connected: boolean = false;
constructor(public userId: number, initiator: boolean, private connection: RoomConnection) {
super({ super({
initiator: initiator ? initiator : false, initiator: initiator ? initiator : false,
reconnectTimer: 10000, reconnectTimer: 10000,
@ -57,6 +60,8 @@ export class VideoPeer extends Peer {
});*/ });*/
this.on('close', () => { this.on('close', () => {
this._connected = false;
this.toClose = true;
this.destroy(); this.destroy();
}); });
@ -67,6 +72,7 @@ export class VideoPeer extends Peer {
}); });
this.on('connect', () => { this.on('connect', () => {
this._connected = true;
mediaManager.isConnected("" + this.userId); mediaManager.isConnected("" + this.userId);
console.info(`connect => ${this.userId}`); console.info(`connect => ${this.userId}`);
}); });
@ -88,6 +94,10 @@ export class VideoPeer extends Peer {
} }
}); });
this.once('finish', () => {
this._onFinish();
});
this.pushVideoToRemoteUser(); this.pushVideoToRemoteUser();
} }
@ -108,7 +118,15 @@ export class VideoPeer extends Peer {
mediaManager.disabledVideoByUserId(this.userId); mediaManager.disabledVideoByUserId(this.userId);
mediaManager.disabledMicrophoneByUserId(this.userId); mediaManager.disabledMicrophoneByUserId(this.userId);
} else { } else {
mediaManager.addStreamRemoteVideo("" + this.userId, stream); try {
mediaManager.addStreamRemoteVideo("" + this.userId, stream);
}catch (err){
console.error(err);
//Force add streem video
setTimeout(() => {
this.stream(stream);
}, 500);
}
} }
} }
@ -117,16 +135,31 @@ export class VideoPeer extends Peer {
*/ */
public destroy(error?: Error): void { public destroy(error?: Error): void {
try { try {
this._connected = false
if(!this.toClose){
return;
}
mediaManager.removeActiveVideo("" + this.userId); mediaManager.removeActiveVideo("" + this.userId);
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
//console.log('Closing connection with '+userId);
super.destroy(error); super.destroy(error);
} catch (err) { } catch (err) {
console.error("VideoPeer::destroy", err) console.error("VideoPeer::destroy", err)
} }
} }
_onFinish () {
if (this.destroyed) return
const destroySoon = () => {
this.destroy();
}
if (this._connected) {
destroySoon();
} else {
this.once('connect', destroySoon);
}
}
private pushVideoToRemoteUser() { private pushVideoToRemoteUser() {
try { try {
const localStream: MediaStream | null = mediaManager.localStream; const localStream: MediaStream | null = mediaManager.localStream;

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
maps/characters/tenue-f.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
maps/characters/tenue-m.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -31,6 +31,11 @@ message SilentMessage {
bool silent = 1; bool silent = 1;
} }
message CharacterLayerMessage {
string url = 1;
string name = 2;
}
/*********** CLIENT TO SERVER MESSAGES *************/ /*********** CLIENT TO SERVER MESSAGES *************/
message SetPlayerDetailsMessage { message SetPlayerDetailsMessage {
@ -129,7 +134,7 @@ message GroupDeleteMessage {
message UserJoinedMessage { message UserJoinedMessage {
int32 userId = 1; int32 userId = 1;
string name = 2; string name = 2;
repeated string characterLayers = 3; repeated CharacterLayerMessage characterLayers = 3;
PositionMessage position = 4; PositionMessage position = 4;
} }