Merge branch 'develop' of github.com:thecodingmachine/workadventure into iframe_api
# Conflicts: # front/dist/.gitignore # front/dist/index.tmpl.html # front/src/Phaser/Game/GameScene.ts # front/src/WebRtc/CoWebsiteManager.ts
This commit is contained in:
commit
aaaa192b71
46 changed files with 1223 additions and 403 deletions
|
@ -6,22 +6,25 @@ import {GameConnexionTypes, urlManager} from "../Url/UrlManager";
|
|||
import {localUserStore} from "./LocalUserStore";
|
||||
import {LocalUser} from "./LocalUser";
|
||||
import {Room} from "./Room";
|
||||
import {Subject} from "rxjs";
|
||||
|
||||
export enum ConnexionMessageEventTypes {
|
||||
worldFull = 1,
|
||||
}
|
||||
|
||||
export interface ConnexionMessageEvent {
|
||||
type: ConnexionMessageEventTypes,
|
||||
}
|
||||
|
||||
class ConnectionManager {
|
||||
private localUser!:LocalUser;
|
||||
|
||||
private connexionType?: GameConnexionTypes
|
||||
private reconnectingTimeout: NodeJS.Timeout|null = null;
|
||||
private _unloading:boolean = false;
|
||||
|
||||
public _connexionMessageStream:Subject<ConnexionMessageEvent> = new Subject();
|
||||
get unloading () {
|
||||
return this._unloading;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
window.addEventListener('beforeunload', () => {
|
||||
this._unloading = true;
|
||||
if (this.reconnectingTimeout) clearTimeout(this.reconnectingTimeout)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Tries to login to the node server and return the starting map url to be loaded
|
||||
*/
|
||||
|
@ -105,7 +108,7 @@ class ConnectionManager {
|
|||
}).catch((err) => {
|
||||
// Let's retry in 4-6 seconds
|
||||
return new Promise<OnConnectInterface>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
this.reconnectingTimeout = setTimeout(() => {
|
||||
//todo: allow a way to break recursion?
|
||||
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.
|
||||
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection));
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import {LocalUser} from "./LocalUser";
|
||||
|
||||
const characterLayersKey = 'characterLayers';
|
||||
const gameQualityKey = 'gameQuality';
|
||||
const videoQualityKey = 'videoQuality';
|
||||
const playerNameKey = 'playerName';
|
||||
const selectedPlayerKey = 'selectedPlayer';
|
||||
const customCursorPositionKey = 'customCursorPosition';
|
||||
const characterLayersKey = 'characterLayers';
|
||||
const gameQualityKey = 'gameQuality';
|
||||
const videoQualityKey = 'videoQuality';
|
||||
const audioPlayerVolumeKey = 'audioVolume';
|
||||
const audioPlayerMuteKey = 'audioMute';
|
||||
const helpCameraSettingsShown = 'helpCameraSettingsShown';
|
||||
|
||||
//todo: add localstorage fallback
|
||||
class LocalUserStore {
|
||||
|
||||
saveUser(localUser: LocalUser) {
|
||||
localStorage.setItem('localUser', JSON.stringify(localUser));
|
||||
}
|
||||
|
@ -14,48 +18,69 @@ class LocalUserStore {
|
|||
const data = localStorage.getItem('localUser');
|
||||
return data ? JSON.parse(data) : null;
|
||||
}
|
||||
|
||||
|
||||
setName(name:string): void {
|
||||
window.localStorage.setItem('playerName', name);
|
||||
localStorage.setItem(playerNameKey, name);
|
||||
}
|
||||
getName(): string {
|
||||
return window.localStorage.getItem('playerName') ?? '';
|
||||
return localStorage.getItem(playerNameKey) || '';
|
||||
}
|
||||
|
||||
setPlayerCharacterIndex(playerCharacterIndex: number): void {
|
||||
window.localStorage.setItem('selectedPlayer', ''+playerCharacterIndex);
|
||||
localStorage.setItem(selectedPlayerKey, ''+playerCharacterIndex);
|
||||
}
|
||||
getPlayerCharacterIndex(): number {
|
||||
return parseInt(window.localStorage.getItem('selectedPlayer') || '');
|
||||
return parseInt(localStorage.getItem(selectedPlayerKey) || '');
|
||||
}
|
||||
|
||||
setCustomCursorPosition(activeRow:number, selectedLayers: number[]): void {
|
||||
window.localStorage.setItem('customCursorPosition', JSON.stringify({activeRow, selectedLayers}));
|
||||
localStorage.setItem(customCursorPositionKey, JSON.stringify({activeRow, selectedLayers}));
|
||||
}
|
||||
getCustomCursorPosition(): {activeRow:number, selectedLayers:number[]}|null {
|
||||
return JSON.parse(window.localStorage.getItem('customCursorPosition') || "null");
|
||||
return JSON.parse(localStorage.getItem(customCursorPositionKey) || "null");
|
||||
}
|
||||
|
||||
setCharacterLayers(layers: string[]): void {
|
||||
window.localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
||||
localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
||||
}
|
||||
getCharacterLayers(): string[]|null {
|
||||
return JSON.parse(window.localStorage.getItem(characterLayersKey) || "null");
|
||||
}
|
||||
|
||||
getGameQualityValue(): number {
|
||||
return parseInt(window.localStorage.getItem(gameQualityKey) || '') || 60;
|
||||
return JSON.parse(localStorage.getItem(characterLayersKey) || "null");
|
||||
}
|
||||
|
||||
setGameQualityValue(value: number): void {
|
||||
localStorage.setItem(gameQualityKey, '' + value);
|
||||
}
|
||||
|
||||
getVideoQualityValue(): number {
|
||||
return parseInt(window.localStorage.getItem(videoQualityKey) || '') || 20;
|
||||
getGameQualityValue(): number {
|
||||
return parseInt(localStorage.getItem(gameQualityKey) || '60');
|
||||
}
|
||||
|
||||
setVideoQualityValue(value: number): void {
|
||||
localStorage.setItem(videoQualityKey, '' + value);
|
||||
}
|
||||
getVideoQualityValue(): number {
|
||||
return parseInt(localStorage.getItem(videoQualityKey) || '20');
|
||||
}
|
||||
|
||||
setAudioPlayerVolume(value: number): void {
|
||||
localStorage.setItem(audioPlayerVolumeKey, '' + value);
|
||||
}
|
||||
getAudioPlayerVolume(): number {
|
||||
return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || '1');
|
||||
}
|
||||
|
||||
setAudioPlayerMuted(value: boolean): void {
|
||||
localStorage.setItem(audioPlayerMuteKey, value.toString());
|
||||
}
|
||||
getAudioPlayerMuted(): boolean {
|
||||
return localStorage.getItem(audioPlayerMuteKey) === 'true';
|
||||
}
|
||||
|
||||
setHelpCameraSettingsShown(): void {
|
||||
localStorage.setItem(helpCameraSettingsShown, '1');
|
||||
}
|
||||
getHelpCameraSettingsShown(): boolean {
|
||||
return localStorage.getItem(helpCameraSettingsShown) === '1';
|
||||
}
|
||||
}
|
||||
|
||||
export const localUserStore = new LocalUserStore();
|
||||
export const localUserStore = new LocalUserStore();
|
||||
|
|
|
@ -43,7 +43,9 @@ import {
|
|||
} from "./ConnexionModels";
|
||||
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||
import {adminMessagesService} from "./AdminMessagesService";
|
||||
import {connectionManager, ConnexionMessageEventTypes} from "./ConnectionManager";
|
||||
import {worldFullMessageStream} from "./WorldFullMessageStream";
|
||||
import {worldFullWarningStream} from "./WorldFullWarningStream";
|
||||
import {connectionManager} from "./ConnectionManager";
|
||||
|
||||
const manualPingDelay = 20000;
|
||||
|
||||
|
@ -156,8 +158,8 @@ export class RoomConnection implements RoomConnection {
|
|||
items
|
||||
} as RoomJoinedMessageInterface
|
||||
});
|
||||
} else if (message.hasErrormessage()) {
|
||||
connectionManager._connexionMessageStream.next({type: ConnexionMessageEventTypes.worldFull}); //todo: generalize this behavior to all messages
|
||||
} else if (message.hasWorldfullmessage()) {
|
||||
worldFullMessageStream.onMessage();
|
||||
this.closed = true;
|
||||
} else if (message.hasWebrtcsignaltoclientmessage()) {
|
||||
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
|
||||
|
@ -179,6 +181,8 @@ export class RoomConnection implements RoomConnection {
|
|||
adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage);
|
||||
} else if (message.hasBanusermessage()) {
|
||||
adminMessagesService.onSendusermessage(message.getSendusermessage() as BanUserMessage);
|
||||
} else if (message.hasWorldfullwarningmessage()) {
|
||||
worldFullWarningStream.onMessage();
|
||||
} else {
|
||||
throw new Error('Unknown message received');
|
||||
}
|
||||
|
@ -377,10 +381,7 @@ export class RoomConnection implements RoomConnection {
|
|||
public onConnectError(callback: (error: Event) => void): void {
|
||||
this.socket.addEventListener('error', callback)
|
||||
}
|
||||
|
||||
/*public onConnect(callback: (e: Event) => void): void {
|
||||
this.socket.addEventListener('open', callback)
|
||||
}*/
|
||||
|
||||
public onConnect(callback: (roomConnection: OnConnectInterface) => void): void {
|
||||
//this.socket.addEventListener('open', callback)
|
||||
this.onMessage(EventMessage.CONNECT, callback);
|
||||
|
@ -449,9 +450,9 @@ export class RoomConnection implements RoomConnection {
|
|||
});
|
||||
}
|
||||
|
||||
public onServerDisconnected(callback: (event: CloseEvent) => void): void {
|
||||
public onServerDisconnected(callback: () => void): void {
|
||||
this.socket.addEventListener('close', (event) => {
|
||||
if (this.closed === true) {
|
||||
if (this.closed === true || connectionManager.unloading) {
|
||||
return;
|
||||
}
|
||||
console.log('Socket closed with code '+event.code+". Reason: "+event.reason);
|
||||
|
@ -459,7 +460,7 @@ export class RoomConnection implements RoomConnection {
|
|||
// Normal closure case
|
||||
return;
|
||||
}
|
||||
callback(event);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
14
front/src/Connexion/WorldFullMessageStream.ts
Normal file
14
front/src/Connexion/WorldFullMessageStream.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import {Subject} from "rxjs";
|
||||
|
||||
class WorldFullMessageStream {
|
||||
|
||||
private _stream:Subject<void> = new Subject();
|
||||
public stream = this._stream.asObservable();
|
||||
|
||||
|
||||
onMessage() {
|
||||
this._stream.next();
|
||||
}
|
||||
}
|
||||
|
||||
export const worldFullMessageStream = new WorldFullMessageStream();
|
14
front/src/Connexion/WorldFullWarningStream.ts
Normal file
14
front/src/Connexion/WorldFullWarningStream.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import {Subject} from "rxjs";
|
||||
|
||||
class WorldFullWarningStream {
|
||||
|
||||
private _stream:Subject<void> = new Subject();
|
||||
public stream = this._stream.asObservable();
|
||||
|
||||
|
||||
onMessage() {
|
||||
this._stream.next();
|
||||
}
|
||||
}
|
||||
|
||||
export const worldFullWarningStream = new WorldFullWarningStream();
|
14
front/src/Phaser/Components/WarningContainer.ts
Normal file
14
front/src/Phaser/Components/WarningContainer.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
export const warningContainerKey = 'warningContainer';
|
||||
export const warningContainerHtml = 'resources/html/warningContainer.html';
|
||||
|
||||
export class WarningContainer extends Phaser.GameObjects.DOMElement {
|
||||
|
||||
constructor(scene: Phaser.Scene) {
|
||||
super(scene, 100, 0);
|
||||
this.setOrigin(0, 0);
|
||||
this.createFromCache(warningContainerKey);
|
||||
this.scene.add.existing(this);
|
||||
}
|
||||
|
||||
}
|
|
@ -2,6 +2,7 @@ import {GameScene} from "./GameScene";
|
|||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {Room} from "../../Connexion/Room";
|
||||
import {MenuScene, MenuSceneName} from "../Menu/MenuScene";
|
||||
import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene";
|
||||
import {LoginSceneName} from "../Login/LoginScene";
|
||||
import {SelectCharacterSceneName} from "../Login/SelectCharacterScene";
|
||||
import {EnableCameraSceneName} from "../Login/EnableCameraScene";
|
||||
|
@ -78,6 +79,10 @@ export class GameManager {
|
|||
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
|
||||
scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
|
||||
scenePlugin.launch(MenuSceneName);
|
||||
|
||||
if (!localUserStore.getHelpCameraSettingsShown()) {
|
||||
scenePlugin.launch(HelpCameraSettingsSceneName);//700
|
||||
}
|
||||
}
|
||||
|
||||
public gameSceneIsCreated(scene: GameScene) {
|
||||
|
|
|
@ -29,7 +29,9 @@ import {
|
|||
ON_ACTION_TRIGGER_BUTTON,
|
||||
TRIGGER_JITSI_PROPERTIES,
|
||||
TRIGGER_WEBSITE_PROPERTIES,
|
||||
WEBSITE_MESSAGE_PROPERTIES
|
||||
WEBSITE_MESSAGE_PROPERTIES,
|
||||
AUDIO_VOLUME_PROPERTY,
|
||||
AUDIO_LOOP_PROPERTY
|
||||
} from "../../WebRtc/LayoutManager";
|
||||
import {GameMap} from "./GameMap";
|
||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||
|
@ -66,6 +68,7 @@ import GameObject = Phaser.GameObjects.GameObject;
|
|||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
||||
import DOMElement = Phaser.GameObjects.DOMElement;
|
||||
import {Subscription} from "rxjs";
|
||||
import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream";
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface|null,
|
||||
|
@ -315,7 +318,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
urlManager.pushRoomIdToUrl(this.room);
|
||||
this.startLayerName = urlManager.getStartLayerNameFromUrl();
|
||||
|
||||
this.messageSubscription = connectionManager._connexionMessageStream.subscribe((event) => this.onConnexionMessage(event))
|
||||
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError())
|
||||
|
||||
const playerName = gameManager.getPlayerName();
|
||||
if (!playerName) {
|
||||
|
@ -467,16 +470,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
});
|
||||
|
||||
this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
|
||||
audioManager.decreaseVolume();
|
||||
this.shareGroupPosition(groupPositionMessage);
|
||||
this.openChatIcon.setVisible(true);
|
||||
})
|
||||
|
||||
this.connection.onGroupDeleted((groupId: number) => {
|
||||
audioManager.restoreVolume();
|
||||
try {
|
||||
this.deleteGroup(groupId);
|
||||
this.openChatIcon.setVisible(false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
@ -484,9 +483,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
|
||||
this.connection.onServerDisconnected(() => {
|
||||
console.log('Player disconnected from server. Reloading scene.');
|
||||
|
||||
this.simplePeer.closeAllConnections();
|
||||
this.simplePeer.unregister();
|
||||
this.cleanupClosingScene();
|
||||
|
||||
const gameSceneKey = 'somekey' + Math.round(Math.random() * 10000);
|
||||
const game: Phaser.Scene = new GameScene(this.room, this.MapUrlFile, gameSceneKey);
|
||||
|
@ -529,11 +526,15 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
onConnect(user: UserSimplePeerInterface) {
|
||||
self.presentationModeSprite.setVisible(true);
|
||||
self.chatModeSprite.setVisible(true);
|
||||
self.openChatIcon.setVisible(true);
|
||||
audioManager.decreaseVolume();
|
||||
},
|
||||
onDisconnect(userId: number) {
|
||||
if (self.simplePeer.getNbConnections() === 0) {
|
||||
self.presentationModeSprite.setVisible(false);
|
||||
self.chatModeSprite.setVisible(false);
|
||||
self.openChatIcon.setVisible(false);
|
||||
audioManager.restoreVolume();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -559,6 +560,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
//todo: into dedicated classes
|
||||
private initCirclesCanvas(): void {
|
||||
// Let's generate the circle for the group delimiter
|
||||
|
@ -641,7 +643,8 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
}else{
|
||||
const openJitsiRoomFunction = () => {
|
||||
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance);
|
||||
if (JITSI_PRIVATE_MODE) {
|
||||
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined;
|
||||
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
|
||||
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
|
||||
|
||||
this.connection.emitQueryJitsiJwtMessage(roomName, adminTag);
|
||||
|
@ -672,12 +675,14 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
this.connection.setSilent(true);
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue) => {
|
||||
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl());
|
||||
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => {
|
||||
const volume = allProps.get(AUDIO_VOLUME_PROPERTY) as number|undefined;
|
||||
const loop = allProps.get(AUDIO_LOOP_PROPERTY) as boolean|undefined;
|
||||
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), volume, loop);
|
||||
});
|
||||
|
||||
// TODO: This legacy property should be removed at some point
|
||||
this.gameMap.onPropertyChange('playAudioLoop', (newValue, oldValue) => {
|
||||
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl());
|
||||
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), undefined, true);
|
||||
});
|
||||
|
||||
this.gameMap.onPropertyChange('zone', (newValue, oldValue) => {
|
||||
|
@ -816,6 +821,7 @@ ${escapedMessage}
|
|||
audioManager.unloadAudio();
|
||||
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
|
||||
this.connection?.closeConnection();
|
||||
this.simplePeer.closeAllConnections();
|
||||
this.simplePeer?.unregister();
|
||||
this.messageSubscription?.unsubscribe();
|
||||
}
|
||||
|
@ -1120,13 +1126,12 @@ ${escapedMessage}
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Let's move all users
|
||||
const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time);
|
||||
updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: number) => {
|
||||
const player : RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
|
||||
const player: RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
|
||||
if (player === undefined) {
|
||||
throw new Error('Cannot find player with ID "' + userId +'"');
|
||||
throw new Error('Cannot find player with ID "' + userId + '"');
|
||||
}
|
||||
player.updatePosition(moveEvent);
|
||||
});
|
||||
|
@ -1344,8 +1349,9 @@ ${escapedMessage}
|
|||
const allProps = this.gameMap.getCurrentProperties();
|
||||
const jitsiConfig = this.safeParseJSONstring(allProps.get("jitsiConfig") as string|undefined, 'jitsiConfig');
|
||||
const jitsiInterfaceConfig = this.safeParseJSONstring(allProps.get("jitsiInterfaceConfig") as string|undefined, 'jitsiInterfaceConfig');
|
||||
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined;
|
||||
|
||||
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig);
|
||||
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
|
||||
this.connection.setSilent(true);
|
||||
mediaManager.hideGameOverlay();
|
||||
|
||||
|
@ -1363,7 +1369,7 @@ ${escapedMessage}
|
|||
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
|
||||
}
|
||||
|
||||
//todo: into onConnexionMessage
|
||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||
private bannedUser(){
|
||||
this.cleanupClosingScene();
|
||||
this.userInputManager.disableControls();
|
||||
|
@ -1386,4 +1392,16 @@ ${escapedMessage}
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||
private showWorldFullError(): void {
|
||||
this.cleanupClosingScene();
|
||||
this.scene.stop(ReconnectingSceneName);
|
||||
this.userInputManager.disableControls();
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: 'Connection rejected',
|
||||
subTitle: 'The world you are trying to join is full. Try again later.',
|
||||
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import {gameManager} from "../Game/GameManager";
|
||||
import {TextField} from "../Components/TextField";
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import {GameSceneInitInterface} from "../Game/GameScene";
|
||||
import {StartMapInterface} from "../../Connexion/ConnexionModels";
|
||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {RESOLUTION} from "../../Enum/EnvironmentVariable";
|
||||
import {SoundMeter} from "../Components/SoundMeter";
|
||||
|
@ -18,6 +16,7 @@ enum LoginTextures {
|
|||
arrowUp = "arrow_up"
|
||||
}
|
||||
|
||||
|
||||
export class EnableCameraScene extends Phaser.Scene {
|
||||
private textField!: TextField;
|
||||
private pressReturnField!: TextField;
|
||||
|
@ -62,26 +61,22 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||
this.microphoneNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 40, '');
|
||||
|
||||
this.arrowRight = new Image(this, 0, 0, LoginTextures.arrowRight);
|
||||
this.arrowRight.setOrigin(0.5, 0.5);
|
||||
this.arrowRight.setVisible(false);
|
||||
this.arrowRight.setInteractive().on('pointerdown', this.nextCam.bind(this));
|
||||
this.add.existing(this.arrowRight);
|
||||
|
||||
this.arrowLeft = new Image(this, 0, 0, LoginTextures.arrowRight);
|
||||
this.arrowLeft.setOrigin(0.5, 0.5);
|
||||
this.arrowLeft.setVisible(false);
|
||||
this.arrowLeft.flipX = true;
|
||||
this.arrowLeft.setInteractive().on('pointerdown', this.previousCam.bind(this));
|
||||
this.add.existing(this.arrowLeft);
|
||||
|
||||
this.arrowUp = new Image(this, 0, 0, LoginTextures.arrowUp);
|
||||
this.arrowUp.setOrigin(0.5, 0.5);
|
||||
this.arrowUp.setVisible(false);
|
||||
this.arrowUp.setInteractive().on('pointerdown', this.previousMic.bind(this));
|
||||
this.add.existing(this.arrowUp);
|
||||
|
||||
this.arrowDown = new Image(this, 0, 0, LoginTextures.arrowUp);
|
||||
this.arrowDown.setOrigin(0.5, 0.5);
|
||||
this.arrowDown.setVisible(false);
|
||||
this.arrowDown.flipY = true;
|
||||
this.arrowDown.setInteractive().on('pointerdown', this.nextMic.bind(this));
|
||||
|
@ -165,8 +160,6 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||
|
||||
private updateWebCamName(): void {
|
||||
if (this.camerasList.length > 1) {
|
||||
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
|
||||
|
||||
let label = this.camerasList[this.cameraSelected].label;
|
||||
// remove text in parenthesis
|
||||
label = label.replace(/\([^()]*\)/g, '').trim();
|
||||
|
@ -174,17 +167,8 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||
label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||||
this.cameraNameField.text = label;
|
||||
|
||||
if (this.cameraSelected < this.camerasList.length - 1) {
|
||||
this.arrowRight.setVisible(true);
|
||||
} else {
|
||||
this.arrowRight.setVisible(false);
|
||||
}
|
||||
|
||||
if (this.cameraSelected > 0) {
|
||||
this.arrowLeft.setVisible(true);
|
||||
} else {
|
||||
this.arrowLeft.setVisible(false);
|
||||
}
|
||||
this.arrowRight.setVisible(this.cameraSelected < this.camerasList.length - 1);
|
||||
this.arrowLeft.setVisible(this.cameraSelected > 0);
|
||||
}
|
||||
if (this.microphonesList.length > 1) {
|
||||
let label = this.microphonesList[this.microphoneSelected].label;
|
||||
|
@ -195,17 +179,8 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||
|
||||
this.microphoneNameField.text = label;
|
||||
|
||||
if (this.microphoneSelected < this.microphonesList.length - 1) {
|
||||
this.arrowDown.setVisible(true);
|
||||
} else {
|
||||
this.arrowDown.setVisible(false);
|
||||
}
|
||||
|
||||
if (this.microphoneSelected > 0) {
|
||||
this.arrowUp.setVisible(true);
|
||||
} else {
|
||||
this.arrowUp.setVisible(false);
|
||||
}
|
||||
this.arrowDown.setVisible(this.microphoneSelected < this.microphonesList.length - 1);
|
||||
this.arrowUp.setVisible(this.microphoneSelected > 0);
|
||||
|
||||
}
|
||||
this.reposition();
|
||||
|
|
95
front/src/Phaser/Menu/HelpCameraSettingsScene.ts
Normal file
95
front/src/Phaser/Menu/HelpCameraSettingsScene.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
|
||||
export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
|
||||
const helpCameraSettings = 'helpCameraSettings';
|
||||
/**
|
||||
* The scene that show how to permit Camera and Microphone access if there are not already allowed
|
||||
*/
|
||||
export class HelpCameraSettingsScene extends Phaser.Scene {
|
||||
private helpCameraSettingsElement!: Phaser.GameObjects.DOMElement;
|
||||
private helpCameraSettingsOpened: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super({key: HelpCameraSettingsSceneName});
|
||||
}
|
||||
|
||||
preload() {
|
||||
this.load.html(helpCameraSettings, 'resources/html/helpCameraSettings.html');
|
||||
}
|
||||
|
||||
create(){
|
||||
localUserStore.setHelpCameraSettingsShown();
|
||||
this.createHelpCameraSettings();
|
||||
}
|
||||
|
||||
private createHelpCameraSettings() : void {
|
||||
const middleX = (window.innerWidth / 3) - (370*0.85);
|
||||
this.helpCameraSettingsElement = this.add.dom(middleX, -800, undefined, {overflow: 'scroll'}).createFromCache(helpCameraSettings);
|
||||
this.revealMenusAfterInit(this.helpCameraSettingsElement, helpCameraSettings);
|
||||
this.helpCameraSettingsElement.addListener('click');
|
||||
this.helpCameraSettingsElement.on('click', (event:MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormRefresh') {
|
||||
window.location.reload();
|
||||
}else if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormContinue') {
|
||||
this.closeHelpCameraSettingsOpened();
|
||||
}
|
||||
});
|
||||
|
||||
if(!mediaManager.constraintsMedia.audio || !mediaManager.constraintsMedia.video){
|
||||
this.openHelpCameraSettingsOpened();
|
||||
}
|
||||
}
|
||||
|
||||
private openHelpCameraSettingsOpened(): void{
|
||||
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
|
||||
this.helpCameraSettingsOpened = true;
|
||||
let middleY = (window.innerHeight / 3) - (495);
|
||||
if(middleY < 0){
|
||||
middleY = 0;
|
||||
}
|
||||
let middleX = (window.innerWidth / 3) - (370*0.85);
|
||||
if(middleX < 0){
|
||||
middleX = 0;
|
||||
}
|
||||
if(window.navigator.userAgent.includes('Firefox')){
|
||||
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-firefox.png"/>';
|
||||
}else if(window.navigator.userAgent.includes('Chrome')){
|
||||
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-chrome.png"/>';
|
||||
}
|
||||
this.tweens.add({
|
||||
targets: this.helpCameraSettingsElement,
|
||||
y: middleY,
|
||||
x: middleX,
|
||||
duration: 1000,
|
||||
ease: 'Power3',
|
||||
overflow: 'scroll'
|
||||
});
|
||||
}
|
||||
|
||||
private closeHelpCameraSettingsOpened(): void{
|
||||
const helpCameraSettingsInfo = this.helpCameraSettingsElement.getChildByID('helpCameraSettings') as HTMLParagraphElement;
|
||||
helpCameraSettingsInfo.innerText = '';
|
||||
helpCameraSettingsInfo.style.display = 'none';
|
||||
this.helpCameraSettingsOpened = false;
|
||||
this.tweens.add({
|
||||
targets: this.helpCameraSettingsElement,
|
||||
y: -400,
|
||||
duration: 1000,
|
||||
ease: 'Power3',
|
||||
overflow: 'scroll'
|
||||
});
|
||||
}
|
||||
|
||||
private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
|
||||
//Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect.
|
||||
//To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
|
||||
setTimeout(() => {
|
||||
(menuElement.getChildByID(rootDomId) as HTMLElement).hidden = false;
|
||||
}, 250);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,6 +6,8 @@ import {mediaManager} from "../../WebRtc/MediaManager";
|
|||
import {gameReportKey, gameReportRessource, ReportMenu} from "./ReportMenu";
|
||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {GameConnexionTypes} from "../../Url/UrlManager";
|
||||
import {WarningContainer, warningContainerHtml, warningContainerKey} from "../Components/WarningContainer";
|
||||
import {worldFullWarningStream} from "../../Connexion/WorldFullWarningStream";
|
||||
|
||||
export const MenuSceneName = 'MenuScene';
|
||||
const gameMenuKey = 'gameMenu';
|
||||
|
@ -30,6 +32,8 @@ export class MenuScene extends Phaser.Scene {
|
|||
private gameQualityValue: number;
|
||||
private videoQualityValue: number;
|
||||
private menuButton!: Phaser.GameObjects.DOMElement;
|
||||
private warningContainer: WarningContainer | null = null;
|
||||
private warningContainerTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
constructor() {
|
||||
super({key: MenuSceneName});
|
||||
|
@ -44,6 +48,7 @@ export class MenuScene extends Phaser.Scene {
|
|||
this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html');
|
||||
this.load.html(gameShare, 'resources/html/gameShare.html');
|
||||
this.load.html(gameReportKey, gameReportRessource);
|
||||
this.load.html(warningContainerKey, warningContainerHtml);
|
||||
}
|
||||
|
||||
create() {
|
||||
|
@ -85,6 +90,8 @@ export class MenuScene extends Phaser.Scene {
|
|||
|
||||
this.menuElement.addListener('click');
|
||||
this.menuElement.on('click', this.onMenuClick.bind(this));
|
||||
|
||||
worldFullWarningStream.stream.subscribe(() => this.showWorldCapacityWarning());
|
||||
}
|
||||
|
||||
//todo put this method in a parent menuElement class
|
||||
|
@ -121,6 +128,21 @@ export class MenuScene extends Phaser.Scene {
|
|||
ease: 'Power3'
|
||||
});
|
||||
}
|
||||
|
||||
private showWorldCapacityWarning() {
|
||||
if (!this.warningContainer) {
|
||||
this.warningContainer = new WarningContainer(this);
|
||||
}
|
||||
if (this.warningContainerTimeout) {
|
||||
clearTimeout(this.warningContainerTimeout);
|
||||
}
|
||||
this.warningContainerTimeout = setTimeout(() => {
|
||||
this.warningContainer?.destroy();
|
||||
this.warningContainer = null
|
||||
this.warningContainerTimeout = null
|
||||
}, 120000);
|
||||
|
||||
}
|
||||
|
||||
private closeSideMenu(): void {
|
||||
if (!this.sideMenuOpened) return;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {isUndefined} from "generic-type-guard";
|
||||
import {localUserStore} from "../Connexion/LocalUserStore";
|
||||
|
||||
enum audioStates {
|
||||
closed = 0,
|
||||
|
@ -9,6 +10,8 @@ enum audioStates {
|
|||
|
||||
const audioPlayerDivId = "audioplayer";
|
||||
const audioPlayerCtrlId = "audioplayerctrl";
|
||||
const audioPlayerVolId = "audioplayer_volume";
|
||||
const audioPlayerMuteId = "audioplayer_volume_icon_playing";
|
||||
const animationTime = 500;
|
||||
|
||||
class AudioManager {
|
||||
|
@ -17,6 +20,8 @@ class AudioManager {
|
|||
private audioPlayerDiv: HTMLDivElement;
|
||||
private audioPlayerCtrl: HTMLDivElement;
|
||||
private audioPlayerElem: HTMLAudioElement | undefined;
|
||||
private audioPlayerVol: HTMLInputElement;
|
||||
private audioPlayerMute: HTMLInputElement;
|
||||
|
||||
private volume = 1;
|
||||
private muted = false;
|
||||
|
@ -26,19 +31,19 @@ class AudioManager {
|
|||
constructor() {
|
||||
this.audioPlayerDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerDivId);
|
||||
this.audioPlayerCtrl = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerCtrlId);
|
||||
this.audioPlayerVol = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(audioPlayerVolId);
|
||||
this.audioPlayerMute = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(audioPlayerMuteId);
|
||||
|
||||
const storedVolume = localStorage.getItem('volume')
|
||||
if (storedVolume === null) {
|
||||
this.setVolume(1);
|
||||
} else {
|
||||
this.volume = parseFloat(storedVolume);
|
||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume').value = storedVolume;
|
||||
this.volume = localUserStore.getAudioPlayerVolume();
|
||||
this.audioPlayerVol.value = '' + this.volume;
|
||||
|
||||
this.muted = localUserStore.getAudioPlayerMuted();
|
||||
if (this.muted) {
|
||||
this.audioPlayerMute.classList.add('muted');
|
||||
}
|
||||
|
||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume').value = '' + this.volume;
|
||||
}
|
||||
|
||||
public playAudio(url: string|number|boolean, mapDirUrl: string, loop=false): void {
|
||||
public playAudio(url: string|number|boolean, mapDirUrl: string, volume: number|undefined, loop=false): void {
|
||||
const audioPath = url as string;
|
||||
let realAudioPath = '';
|
||||
|
||||
|
@ -50,7 +55,7 @@ class AudioManager {
|
|||
realAudioPath = mapDirUrl + '/' + url;
|
||||
}
|
||||
|
||||
this.loadAudio(realAudioPath);
|
||||
this.loadAudio(realAudioPath, volume);
|
||||
|
||||
if (loop) {
|
||||
this.loop();
|
||||
|
@ -75,26 +80,29 @@ class AudioManager {
|
|||
}
|
||||
|
||||
private changeVolume(talking = false): void {
|
||||
if (!isUndefined(this.audioPlayerElem)) {
|
||||
this.audioPlayerElem.volume = this.naturalVolume(talking && this.decreaseWhileTalking);
|
||||
this.audioPlayerElem.muted = this.muted;
|
||||
if (isUndefined(this.audioPlayerElem)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private naturalVolume(makeSofter: boolean = false): number {
|
||||
const volume = this.volume
|
||||
const retVol = makeSofter && !this.volumeReduced ? Math.pow(volume * 0.5, 3) : volume
|
||||
this.volumeReduced = makeSofter
|
||||
return retVol;
|
||||
const reduceVolume = talking && this.decreaseWhileTalking;
|
||||
if (reduceVolume && !this.volumeReduced) {
|
||||
this.volume *= 0.5;
|
||||
} else if (!reduceVolume && this.volumeReduced) {
|
||||
this.volume *= 2.0;
|
||||
}
|
||||
this.volumeReduced = reduceVolume;
|
||||
|
||||
this.audioPlayerElem.volume = this.volume;
|
||||
this.audioPlayerVol.value = '' + this.volume;
|
||||
this.audioPlayerElem.muted = this.muted;
|
||||
}
|
||||
|
||||
private setVolume(volume: number): void {
|
||||
this.volume = volume;
|
||||
localStorage.setItem('volume', '' + volume);
|
||||
localUserStore.setAudioPlayerVolume(volume);
|
||||
}
|
||||
|
||||
|
||||
private loadAudio(url: string): void {
|
||||
private loadAudio(url: string, volume: number|undefined): void {
|
||||
this.load();
|
||||
|
||||
/* Solution 1, remove whole audio player */
|
||||
|
@ -112,23 +120,24 @@ class AudioManager {
|
|||
this.audioPlayerElem.append(srcElem);
|
||||
|
||||
this.audioPlayerDiv.append(this.audioPlayerElem);
|
||||
this.volume = volume ? Math.min(volume, this.volume) : this.volume;
|
||||
this.changeVolume();
|
||||
this.audioPlayerElem.play();
|
||||
|
||||
const muteElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_mute');
|
||||
muteElem.onclick = (ev: Event)=> {
|
||||
muteElem.onclick = (ev: Event) => {
|
||||
this.muted = !this.muted;
|
||||
this.changeVolume();
|
||||
localUserStore.setAudioPlayerMuted(this.muted);
|
||||
|
||||
if (this.muted) {
|
||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume_icon_playing').classList.add('muted');
|
||||
this.audioPlayerMute.classList.add('muted');
|
||||
} else {
|
||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume_icon_playing').classList.remove('muted');
|
||||
this.audioPlayerMute.classList.remove('muted');
|
||||
}
|
||||
}
|
||||
|
||||
const volumeElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume');
|
||||
volumeElem.oninput = (ev: Event)=> {
|
||||
this.audioPlayerVol.oninput = (ev: Event)=> {
|
||||
this.setVolume(parseFloat((<HTMLInputElement>ev.currentTarget).value));
|
||||
this.changeVolume();
|
||||
|
||||
|
|
|
@ -1,37 +1,109 @@
|
|||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {Subject} from "rxjs";
|
||||
import {iframeListener} from "../Api/IframeListener";
|
||||
|
||||
export type CoWebsiteStateChangedCallback = () => void;
|
||||
|
||||
enum iframeStates {
|
||||
closed = 1,
|
||||
loading, // loading an iframe can be slow, so we show some placeholder until it is ready
|
||||
opened,
|
||||
}
|
||||
|
||||
const cowebsiteDivId = "cowebsite"; // the id of the parent div of the iframe.
|
||||
const cowebsiteDivId = 'cowebsite'; // the id of the whole container.
|
||||
const cowebsiteMainDomId = 'cowebsite-main'; // the id of the parent div of the iframe.
|
||||
const cowebsiteAsideDomId = 'cowebsite-aside'; // the id of the parent div of the iframe.
|
||||
const cowebsiteCloseButtonId = 'cowebsite-close';
|
||||
const cowebsiteFullScreenButtonId = 'cowebsite-fullscreen';
|
||||
const cowebsiteOpenFullScreenImageId = 'cowebsite-fullscreen-open';
|
||||
const cowebsiteCloseFullScreenImageId = 'cowebsite-fullscreen-close';
|
||||
const animationTime = 500; //time used by the css transitions, in ms.
|
||||
|
||||
class CoWebsiteManager {
|
||||
|
||||
private opened: iframeStates = iframeStates.closed;
|
||||
|
||||
private observers = new Array<CoWebsiteStateChangedCallback>();
|
||||
private _onResize: Subject<void> = new Subject();
|
||||
public onResize = this._onResize.asObservable();
|
||||
/**
|
||||
* Quickly going in and out of an iframe trigger can create conflicts between the iframe states.
|
||||
* So we use this promise to queue up every cowebsite state transition
|
||||
*/
|
||||
private currentOperationPromise: Promise<void> = Promise.resolve();
|
||||
private cowebsiteDiv: HTMLDivElement;
|
||||
private resizing: boolean = false;
|
||||
private cowebsiteMainDom: HTMLDivElement;
|
||||
private cowebsiteAsideDom: HTMLDivElement;
|
||||
|
||||
get width(): number {
|
||||
return this.cowebsiteDiv.clientWidth;
|
||||
}
|
||||
|
||||
set width(width: number) {
|
||||
this.cowebsiteDiv.style.width = width+'px';
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
return this.cowebsiteDiv.clientHeight;
|
||||
}
|
||||
|
||||
set height(height: number) {
|
||||
this.cowebsiteDiv.style.height = height+'px';
|
||||
}
|
||||
|
||||
get verticalMode(): boolean {
|
||||
return window.innerWidth < window.innerHeight;
|
||||
}
|
||||
|
||||
get isFullScreen(): boolean {
|
||||
return this.verticalMode ? this.height === window.innerHeight : this.width === window.innerWidth;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
|
||||
this.cowebsiteMainDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteMainDomId);
|
||||
this.cowebsiteAsideDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteAsideDomId);
|
||||
|
||||
this.initResizeListeners();
|
||||
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId).addEventListener('click', () => {
|
||||
this.closeCoWebsite();
|
||||
});
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId).addEventListener('click', () => {
|
||||
this.fullscreen();
|
||||
});
|
||||
}
|
||||
|
||||
private initResizeListeners() {
|
||||
const movecallback = (event:MouseEvent) => {
|
||||
this.verticalMode ? this.height -= event.movementY / this.getDevicePixelRatio() : this.width -= event.movementX / this.getDevicePixelRatio();
|
||||
this.fire();
|
||||
}
|
||||
|
||||
this.cowebsiteAsideDom.addEventListener('mousedown', (event) => {
|
||||
this.resizing = true;
|
||||
this.getIframeDom().style.display = 'none';
|
||||
|
||||
document.addEventListener('mousemove', movecallback);
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', (event) => {
|
||||
if (!this.resizing) return;
|
||||
document.removeEventListener('mousemove', movecallback);
|
||||
this.getIframeDom().style.display = 'block';
|
||||
this.resizing = false;
|
||||
});
|
||||
}
|
||||
|
||||
private getDevicePixelRatio(): number {
|
||||
//on chrome engines, movementX and movementY return global screens coordinates while other browser return pixels
|
||||
//so on chrome-based browser we need to adjust using 'devicePixelRatio'
|
||||
return window.navigator.userAgent.includes('Firefox') ? 1 : window.devicePixelRatio;
|
||||
}
|
||||
|
||||
private close(): void {
|
||||
this.cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition
|
||||
this.cowebsiteDiv.classList.add('hidden');
|
||||
this.opened = iframeStates.closed;
|
||||
this.resetStyle();
|
||||
}
|
||||
private load(): void {
|
||||
this.cowebsiteDiv.classList.remove('hidden'); //edit the css class to trigger the transition
|
||||
|
@ -41,18 +113,23 @@ class CoWebsiteManager {
|
|||
private open(): void {
|
||||
this.cowebsiteDiv.classList.remove('loading', 'hidden'); //edit the css class to trigger the transition
|
||||
this.opened = iframeStates.opened;
|
||||
this.resetStyle();
|
||||
}
|
||||
|
||||
public resetStyle() {
|
||||
this.cowebsiteDiv.style.width = '';
|
||||
this.cowebsiteDiv.style.height = '';
|
||||
}
|
||||
|
||||
private getIframeDom(): HTMLIFrameElement {
|
||||
const iframe = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId).querySelector('iframe');
|
||||
if (!iframe) throw new Error('Could not find iframe!');
|
||||
return iframe;
|
||||
}
|
||||
|
||||
public loadCoWebsite(url: string, base: string, allowApi?: boolean, allowPolicy?: string): void {
|
||||
this.load();
|
||||
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
||||
<img src="resources/logos/close.svg">
|
||||
</button>`;
|
||||
setTimeout(() => {
|
||||
HtmlUtils.getElementByIdOrFail('cowebsite-close').addEventListener('click', () => {
|
||||
this.closeCoWebsite();
|
||||
});
|
||||
}, 100);
|
||||
this.cowebsiteMainDom.innerHTML = ``;
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = 'cowebsite-iframe';
|
||||
|
@ -66,7 +143,7 @@ class CoWebsiteManager {
|
|||
if (allowApi) {
|
||||
iframeListener.registerIframe(iframe);
|
||||
}
|
||||
this.cowebsiteDiv.appendChild(iframe);
|
||||
this.cowebsiteMainDom.appendChild(iframe);
|
||||
const onTimeoutPromise = new Promise((resolve) => {
|
||||
setTimeout(() => resolve(), 2000);
|
||||
});
|
||||
|
@ -83,7 +160,8 @@ class CoWebsiteManager {
|
|||
*/
|
||||
public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise<void>): void {
|
||||
this.load();
|
||||
this.currentOperationPromise = this.currentOperationPromise.then(() => callback(this.cowebsiteDiv)).then(() => {
|
||||
this.cowebsiteMainDom.innerHTML = ``;
|
||||
this.currentOperationPromise = this.currentOperationPromise.then(() => callback(this.cowebsiteMainDom)).then(() => {
|
||||
this.open();
|
||||
setTimeout(() => {
|
||||
this.fire();
|
||||
|
@ -101,9 +179,7 @@ class CoWebsiteManager {
|
|||
iframeListener.unregisterIframe(iframe);
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
||||
<img src="resources/logos/close.svg">
|
||||
</button>`;
|
||||
this.cowebsiteMainDom.innerHTML = ``;
|
||||
resolve();
|
||||
}, animationTime)
|
||||
}));
|
||||
|
@ -117,27 +193,35 @@ class CoWebsiteManager {
|
|||
height: window.innerHeight
|
||||
}
|
||||
}
|
||||
if (window.innerWidth >= window.innerHeight) {
|
||||
if (!this.verticalMode) {
|
||||
return {
|
||||
width: window.innerWidth / 2,
|
||||
width: window.innerWidth - this.width,
|
||||
height: window.innerHeight
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight / 2
|
||||
height: window.innerHeight - this.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//todo: is it still useful to allow any kind of observers?
|
||||
public onStateChange(observer: CoWebsiteStateChangedCallback) {
|
||||
this.observers.push(observer);
|
||||
private fire(): void {
|
||||
this._onResize.next();
|
||||
}
|
||||
|
||||
private fire(): void {
|
||||
for (const callback of this.observers) {
|
||||
callback();
|
||||
private fullscreen(): void {
|
||||
if (this.isFullScreen) {
|
||||
this.resetStyle();
|
||||
this.fire();
|
||||
//we don't trigger a resize of the phaser game since it won't be visible anyway.
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId).style.display = 'inline';
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId).style.display = 'none';
|
||||
} else {
|
||||
this.verticalMode ? this.height = window.innerHeight : this.width = window.innerWidth;
|
||||
//we don't trigger a resize of the phaser game since it won't be visible anyway.
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId).style.display = 'none';
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId).style.display = 'inline';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ class JitsiFactory {
|
|||
private audioCallback = this.onAudioChange.bind(this);
|
||||
private videoCallback = this.onVideoChange.bind(this);
|
||||
private previousConfigMeet? : jitsiConfigInterface;
|
||||
private jitsiScriptLoaded: boolean = false;
|
||||
|
||||
/**
|
||||
* Slugifies the room name and prepends the room name with the instance
|
||||
|
@ -80,11 +81,11 @@ class JitsiFactory {
|
|||
return slugify(instance.replace('/', '-') + "-" + roomName);
|
||||
}
|
||||
|
||||
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object): void {
|
||||
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object, jitsiUrl?: string): void {
|
||||
//save previous config
|
||||
this.previousConfigMeet = getDefaultConfig();
|
||||
|
||||
coWebsiteManager.insertCoWebsite((cowebsiteDiv => {
|
||||
coWebsiteManager.insertCoWebsite((async cowebsiteDiv => {
|
||||
// Jitsi meet external API maintains some data in local storage
|
||||
// which is sent via the appData URL parameter when joining a
|
||||
// conference. Problem is that this data grows indefinitely. Thus
|
||||
|
@ -93,7 +94,12 @@ class JitsiFactory {
|
|||
// clear jitsi local storage before starting a new conference.
|
||||
window.localStorage.removeItem("jitsiLocalStorage");
|
||||
|
||||
const domain = JITSI_URL;
|
||||
const domain = jitsiUrl || JITSI_URL;
|
||||
if (domain === undefined) {
|
||||
throw new Error('Missing JITSI_URL environment variable or jitsiUrl parameter in the map.')
|
||||
}
|
||||
await this.loadJitsiScript(domain);
|
||||
|
||||
const options: any = { // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
roomName: roomName,
|
||||
jwt: jwt,
|
||||
|
@ -157,6 +163,32 @@ class JitsiFactory {
|
|||
mediaManager.enableCamera();
|
||||
}
|
||||
}
|
||||
|
||||
private async loadJitsiScript(domain: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (this.jitsiScriptLoaded) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.jitsiScriptLoaded = true;
|
||||
|
||||
// Load Jitsi if the environment variable is set.
|
||||
const jitsiScript = document.createElement('script');
|
||||
jitsiScript.src = 'https://' + domain + '/external_api.js';
|
||||
jitsiScript.onload = () => {
|
||||
resolve();
|
||||
}
|
||||
jitsiScript.onerror = () => {
|
||||
reject();
|
||||
}
|
||||
|
||||
document.head.appendChild(jitsiScript);
|
||||
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export const jitsiFactory = new JitsiFactory();
|
||||
|
|
|
@ -31,6 +31,9 @@ export const TRIGGER_JITSI_PROPERTIES = 'jitsiTrigger';
|
|||
export const WEBSITE_MESSAGE_PROPERTIES = 'openWebsiteTriggerMessage';
|
||||
export const JITSI_MESSAGE_PROPERTIES = 'jitsiTriggerMessage';
|
||||
|
||||
export const AUDIO_VOLUME_PROPERTY = 'audioVolume';
|
||||
export const AUDIO_LOOP_PROPERTY = 'audioLoop';
|
||||
|
||||
/**
|
||||
* This class is in charge of the video-conference layout.
|
||||
* It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode.
|
||||
|
|
|
@ -63,7 +63,7 @@ export class SimplePeer {
|
|||
}
|
||||
|
||||
public getNbConnections(): number {
|
||||
return this.PeerConnectionArray.size;
|
||||
return this.Users.length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,9 +230,6 @@ export class SimplePeer {
|
|||
|
||||
this.closeScreenSharingConnection(userId);
|
||||
|
||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||
peerConnectionListener.onDisconnect(userId);
|
||||
}
|
||||
const userIndex = this.Users.findIndex(user => user.userId === userId);
|
||||
if(userIndex < 0){
|
||||
throw 'Couln\'t delete user';
|
||||
|
@ -250,6 +247,10 @@ export class SimplePeer {
|
|||
this.PeerScreenSharingConnectionArray.delete(userId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||
peerConnectionListener.onDisconnect(userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,29 +1,23 @@
|
|||
import 'phaser';
|
||||
import GameConfig = Phaser.Types.Core.GameConfig;
|
||||
import "../dist/resources/style/index.scss";
|
||||
|
||||
import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable";
|
||||
import {LoginScene} from "./Phaser/Login/LoginScene";
|
||||
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
|
||||
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
|
||||
import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene";
|
||||
import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer;
|
||||
import {OutlinePipeline} from "./Phaser/Shaders/OutlinePipeline";
|
||||
import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
|
||||
import {ResizableScene} from "./Phaser/Login/ResizableScene";
|
||||
import {EntryScene} from "./Phaser/Login/EntryScene";
|
||||
import {coWebsiteManager} from "./WebRtc/CoWebsiteManager";
|
||||
import {MenuScene} from "./Phaser/Menu/MenuScene";
|
||||
import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene";
|
||||
import {localUserStore} from "./Connexion/LocalUserStore";
|
||||
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
|
||||
import {iframeListener} from "./Api/IframeListener";
|
||||
import {discussionManager} from "./WebRtc/DiscussionManager";
|
||||
|
||||
// Load Jitsi if the environment variable is set.
|
||||
if (JITSI_URL) {
|
||||
const jitsiScript = document.createElement('script');
|
||||
jitsiScript.src = 'https://' + JITSI_URL + '/external_api.js';
|
||||
document.head.appendChild(jitsiScript);
|
||||
}
|
||||
|
||||
const {width, height} = coWebsiteManager.getGameSize();
|
||||
|
||||
const valueGameQuality = localUserStore.getGameQualityValue();
|
||||
|
@ -80,7 +74,7 @@ const config: GameConfig = {
|
|||
width: width / RESOLUTION,
|
||||
height: height / RESOLUTION,
|
||||
parent: "game",
|
||||
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene],
|
||||
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene, HelpCameraSettingsScene],
|
||||
zoom: RESOLUTION,
|
||||
fps: fps,
|
||||
dom: {
|
||||
|
@ -111,6 +105,7 @@ const config: GameConfig = {
|
|||
const game = new Phaser.Game(config);
|
||||
|
||||
window.addEventListener('resize', function (event) {
|
||||
coWebsiteManager.resetStyle();
|
||||
const {width, height} = coWebsiteManager.getGameSize();
|
||||
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
|
||||
|
||||
|
@ -122,7 +117,7 @@ window.addEventListener('resize', function (event) {
|
|||
}
|
||||
});
|
||||
|
||||
coWebsiteManager.onStateChange(() => {
|
||||
coWebsiteManager.onResize.subscribe(() => {
|
||||
const {width, height} = coWebsiteManager.getGameSize();
|
||||
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue